finality_grandpa/round/
context.rs

1// Copyright 2019 Parity Technologies (UK) Ltd
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The context of a GRANDPA round tracks the set of voters
16//! and equivocations for the purpose of computing vote weights.
17
18use crate::{
19	bitfield::{Bit1, Bitfield},
20	std::ops::AddAssign,
21	voter_set::{VoterInfo, VoterSet},
22	weights::VoteWeight,
23};
24
25use super::Phase;
26
27/// The context of a `Round` in which vote weights are calculated.
28#[cfg_attr(any(feature = "std", test), derive(Debug))]
29#[cfg_attr(test, derive(Clone))]
30pub struct Context<T: Ord + Eq> {
31	voters: VoterSet<T>,
32	equivocations: Bitfield,
33}
34
35impl<T: Ord + Eq> Context<T> {
36	/// Create a new context for a round with the given set of voters.
37	pub fn new(voters: VoterSet<T>) -> Self {
38		Context { voters, equivocations: Bitfield::new() }
39	}
40
41	/// Get the set of voters.
42	pub fn voters(&self) -> &VoterSet<T> {
43		&self.voters
44	}
45
46	/// Get the weight of observed equivocations in phase `p`.
47	pub fn equivocation_weight(&self, p: Phase) -> VoteWeight {
48		match p {
49			Phase::Prevote => weight(self.equivocations.iter1s_even(), &self.voters),
50			Phase::Precommit => weight(self.equivocations.iter1s_odd(), &self.voters),
51		}
52	}
53
54	/// Record voter `v` as an equivocator in phase `p`.
55	pub fn equivocated(&mut self, v: &VoterInfo, p: Phase) {
56		self.equivocations.set_bit(Vote::new(v, p).bit.position);
57	}
58
59	/// Compute the vote weight on node `n` in phase `p`, taking into account
60	/// equivocations.
61	pub fn weight(&self, n: &VoteNode, p: Phase) -> VoteWeight {
62		if self.equivocations.is_blank() {
63			match p {
64				Phase::Prevote => weight(n.bits.iter1s_even(), &self.voters),
65				Phase::Precommit => weight(n.bits.iter1s_odd(), &self.voters),
66			}
67		} else {
68			match p {
69				Phase::Prevote => {
70					let bits = n.bits.iter1s_merged_even(&self.equivocations);
71					weight(bits, &self.voters)
72				},
73				Phase::Precommit => {
74					let bits = n.bits.iter1s_merged_odd(&self.equivocations);
75					weight(bits, &self.voters)
76				},
77			}
78		}
79	}
80}
81
82/// A single vote that can be incorporated into a `VoteNode`.
83pub struct Vote {
84	bit: Bit1,
85}
86
87impl Vote {
88	/// Create a new vote cast by voter `v` in phase `p`.
89	pub fn new(v: &VoterInfo, p: Phase) -> Vote {
90		Vote {
91			bit: Bit1 {
92				position: match p {
93					Phase::Prevote => v.position() * 2,
94					Phase::Precommit => v.position() * 2 + 1,
95				},
96			},
97		}
98	}
99
100	/// Get the voter who cast the vote from the given voter set,
101	/// if it is contained in that set.
102	fn voter<'a, Id>(&'a self, vs: &'a VoterSet<Id>) -> Option<(&'a Id, &'a VoterInfo)>
103	where
104		Id: Eq + Ord,
105	{
106		vs.nth(self.bit.position / 2)
107	}
108}
109
110/// A node on which `Vote`s can be accumulated, for use in a `VoteGraph`.
111///
112/// The weight of any `VoteNode` is always computed in a `Context`,
113/// taking into account equivocations. See [`Context::weight`].
114#[derive(Clone, Debug)]
115pub struct VoteNode {
116	bits: Bitfield,
117}
118
119impl Default for VoteNode {
120	fn default() -> Self {
121		Self { bits: Bitfield::new() }
122	}
123}
124
125impl AddAssign<&VoteNode> for VoteNode {
126	fn add_assign(&mut self, rhs: &VoteNode) {
127		self.bits.merge(&rhs.bits);
128	}
129}
130
131impl AddAssign<&Vote> for VoteNode {
132	fn add_assign(&mut self, rhs: &Vote) {
133		self.bits.set_bit(rhs.bit.position);
134	}
135}
136
137/// Compute the prevote and precommit weights of a sequence
138/// of vote-bits in the context of the given set of voters.
139fn weight<V, I>(bits: I, voters: &VoterSet<V>) -> VoteWeight
140where
141	V: Eq + Ord,
142	I: Iterator<Item = Bit1>,
143{
144	let mut total = VoteWeight(0);
145
146	for bit in bits {
147		let vote = Vote { bit };
148		if let Some((_id, v)) = vote.voter(voters) {
149			total = total + v.weight()
150		}
151	}
152
153	total
154}
155
156#[cfg(test)]
157mod tests {
158	use super::*;
159	use crate::std::vec::Vec;
160	use quickcheck::*;
161
162	impl Arbitrary for Phase {
163		fn arbitrary(g: &mut Gen) -> Self {
164			*g.choose(&[Phase::Prevote, Phase::Precommit]).unwrap()
165		}
166	}
167
168	impl Arbitrary for Context<usize> {
169		fn arbitrary(g: &mut Gen) -> Self {
170			let mut ctx = Context::new(VoterSet::arbitrary(g));
171
172			let n = usize::arbitrary(g) % ctx.voters.len().get();
173			let equivocators = (0..=n)
174				.map(|_| ctx.voters.nth_mod(usize::arbitrary(g)).1.clone())
175				.collect::<Vec<_>>();
176
177			for v in equivocators {
178				ctx.equivocated(&v, Phase::arbitrary(g))
179			}
180
181			ctx
182		}
183	}
184
185	#[test]
186	fn vote_voter() {
187		fn prop(vs: VoterSet<usize>, phase: Phase) {
188			for (id, v) in vs.iter() {
189				assert_eq!(Vote::new(v, phase).voter(&vs), Some((id, v)))
190			}
191		}
192
193		quickcheck(prop as fn(_, _))
194	}
195
196	#[test]
197	fn weights() {
198		fn prop(ctx: Context<usize>, phase: Phase, voters: Vec<usize>) {
199			let e = ctx.equivocation_weight(phase);
200			let t = ctx.voters.total_weight();
201
202			// The equivocation weight must never be larger than the total
203			// voter weight.
204			assert!(e <= t);
205
206			// Let a random subset of voters cast a vote, whether already
207			// an equivocator or not.
208			let mut n = VoteNode::default();
209			let mut expected = e;
210			for v in voters {
211				let (_id, v) = ctx.voters.nth_mod(v);
212				let vote = Vote::new(v, phase);
213
214				// We only expect the weight to increase if the voter did not
215				// start out as an equivocator and did not yet vote.
216				if !ctx.equivocations.test_bit(vote.bit.position) &&
217					!n.bits.test_bit(vote.bit.position)
218				{
219					expected = expected + v.weight();
220				}
221
222				n += &vote;
223			}
224
225			// Let the context compute the weight.
226			let w = ctx.weight(&n, phase);
227
228			// A vote-node weight must never be greater than the total voter weight.
229			assert!(w <= t);
230
231			assert_eq!(w, expected);
232		}
233
234		quickcheck(prop as fn(_, _, _))
235	}
236}