1use codec::{Decode, Encode};
22use sc_client_api::backend::AuxStore;
23use sp_blockchain::{Error as ClientError, Result as ClientResult};
24use sp_consensus_slots::{EquivocationProof, Slot};
25use sp_runtime::traits::Header;
26
27const SLOT_HEADER_MAP_KEY: &[u8] = b"slot_header_map";
28const SLOT_HEADER_START: &[u8] = b"slot_header_start";
29
30pub const MAX_SLOT_CAPACITY: u64 = 1000;
32pub const PRUNING_BOUND: u64 = 2 * MAX_SLOT_CAPACITY;
34
35fn load_decode<C, T>(backend: &C, key: &[u8]) -> ClientResult<Option<T>>
36where
37 C: AuxStore,
38 T: Decode,
39{
40 match backend.get_aux(key)? {
41 None => Ok(None),
42 Some(t) => T::decode(&mut &t[..])
43 .map_err(|e| {
44 ClientError::Backend(format!("Slots DB is corrupted. Decode error: {}", e))
45 })
46 .map(Some),
47 }
48}
49
50pub fn check_equivocation<C, H, P>(
54 backend: &C,
55 slot_now: Slot,
56 slot: Slot,
57 header: &H,
58 signer: &P,
59) -> ClientResult<Option<EquivocationProof<H, P>>>
60where
61 H: Header,
62 C: AuxStore,
63 P: Clone + Encode + Decode + PartialEq,
64{
65 if slot_now.saturating_sub(*slot) > MAX_SLOT_CAPACITY {
67 return Ok(None)
68 }
69
70 let mut curr_slot_key = SLOT_HEADER_MAP_KEY.to_vec();
72 slot.using_encoded(|s| curr_slot_key.extend(s));
73
74 let mut headers_with_sig =
76 load_decode::<_, Vec<(H, P)>>(backend, &curr_slot_key[..])?.unwrap_or_else(Vec::new);
77
78 let slot_header_start = SLOT_HEADER_START.to_vec();
80 let first_saved_slot = load_decode::<_, Slot>(backend, &slot_header_start[..])?.unwrap_or(slot);
81
82 if slot_now < first_saved_slot {
83 return Ok(None)
85 }
86
87 for (prev_header, prev_signer) in headers_with_sig.iter() {
88 if prev_signer == signer {
91 return if header.hash() != prev_header.hash() {
93 Ok(Some(EquivocationProof {
94 slot,
95 offender: signer.clone(),
96 first_header: prev_header.clone(),
97 second_header: header.clone(),
98 }))
99 } else {
100 Ok(None)
104 }
105 }
106 }
107
108 let mut keys_to_delete = vec![];
109 let mut new_first_saved_slot = first_saved_slot;
110
111 if *slot_now - *first_saved_slot >= PRUNING_BOUND {
112 let prefix = SLOT_HEADER_MAP_KEY.to_vec();
113 new_first_saved_slot = slot_now.saturating_sub(MAX_SLOT_CAPACITY);
114
115 for s in u64::from(first_saved_slot)..new_first_saved_slot.into() {
116 let mut p = prefix.clone();
117 s.using_encoded(|s| p.extend(s));
118 keys_to_delete.push(p);
119 }
120 }
121
122 headers_with_sig.push((header.clone(), signer.clone()));
123
124 backend.insert_aux(
125 &[
126 (&curr_slot_key[..], headers_with_sig.encode().as_slice()),
127 (&slot_header_start[..], new_first_saved_slot.encode().as_slice()),
128 ],
129 &keys_to_delete.iter().map(|k| &k[..]).collect::<Vec<&[u8]>>()[..],
130 )?;
131
132 Ok(None)
133}
134
135#[cfg(test)]
136mod test {
137 use sp_core::{hash::H256, sr25519, Pair};
138 use sp_runtime::testing::{Digest as DigestTest, Header as HeaderTest};
139 use substrate_test_runtime_client;
140
141 use super::{check_equivocation, MAX_SLOT_CAPACITY, PRUNING_BOUND};
142
143 fn create_header(number: u64) -> HeaderTest {
144 let parent_hash = H256::random();
146
147 let header = HeaderTest {
148 parent_hash,
149 number,
150 state_root: Default::default(),
151 extrinsics_root: Default::default(),
152 digest: DigestTest { logs: vec![] },
153 };
154
155 header
156 }
157
158 #[test]
159 fn check_equivocation_works() {
160 let client = substrate_test_runtime_client::new();
161 let (pair, _seed) = sr25519::Pair::generate();
162 let public = pair.public();
163
164 let header1 = create_header(1); let header2 = create_header(2); let header3 = create_header(2); let header4 = create_header(3); let header5 = create_header(4); let header6 = create_header(3); assert!(check_equivocation(&client, 2.into(), 2.into(), &header1, &public)
173 .unwrap()
174 .is_none(),);
175
176 assert!(check_equivocation(&client, 3.into(), 2.into(), &header1, &public)
177 .unwrap()
178 .is_none(),);
179
180 assert!(check_equivocation(&client, 4.into(), 2.into(), &header2, &public)
182 .unwrap()
183 .is_some(),);
184
185 assert!(check_equivocation(&client, 5.into(), 4.into(), &header3, &public)
187 .unwrap()
188 .is_none(),);
189
190 assert!(check_equivocation(
192 &client,
193 (PRUNING_BOUND + 2).into(),
194 (MAX_SLOT_CAPACITY + 4).into(),
195 &header4,
196 &public,
197 )
198 .unwrap()
199 .is_none(),);
200
201 assert!(check_equivocation(
203 &client,
204 (PRUNING_BOUND + 3).into(),
205 (MAX_SLOT_CAPACITY + 4).into(),
206 &header5,
207 &public,
208 )
209 .unwrap()
210 .is_some(),);
211
212 assert!(check_equivocation(
214 &client,
215 (PRUNING_BOUND + 4).into(),
216 4.into(),
217 &header6,
218 &public,
219 )
220 .unwrap()
221 .is_none(),);
222 }
223}