1use itertools::Itertools;
4
5use super::*;
6
7impl<D, H> Shell<D, H>
8where
9 D: DB + for<'iter> DBIter<'iter> + Sync + 'static,
10 H: StorageHasher + Sync + 'static,
11{
12 #[inline]
17 pub fn validate_bp_roots_vext_list<'iter>(
18 &'iter self,
19 vote_extensions: impl IntoIterator<Item = Signed<bridge_pool_roots::Vext>>
20 + 'iter,
21 ) -> impl Iterator<
22 Item = std::result::Result<
23 Signed<bridge_pool_roots::Vext>,
24 VoteExtensionError,
25 >,
26 > + 'iter {
27 vote_extensions.into_iter().map(|vote_extension| {
28 validate_bp_roots_vext::<_, _, governance::Store<_>>(
29 &self.state,
30 &vote_extension,
31 self.state.in_mem().get_last_block_height(),
32 )?;
33 Ok(vote_extension)
34 })
35 }
36
37 #[inline]
41 pub fn filter_invalid_bp_roots_vexts<'iter>(
42 &'iter self,
43 vote_extensions: impl IntoIterator<Item = Signed<bridge_pool_roots::Vext>>
44 + 'iter,
45 ) -> impl Iterator<Item = Signed<bridge_pool_roots::Vext>> + 'iter {
46 self.validate_bp_roots_vext_list(vote_extensions)
47 .filter_map(|ext| ext.ok())
48 .dedup_by(|ext_1, ext_2| {
49 ext_1.data.validator_addr == ext_2.data.validator_addr
50 })
51 }
52}
53
54#[allow(clippy::cast_possible_truncation)]
55#[cfg(test)]
56mod test_bp_vote_extensions {
57 use namada_apps_lib::wallet::defaults::{bertha_address, bertha_keypair};
58 use namada_sdk::chain::BlockHeight;
59 use namada_sdk::eth_bridge::protocol::validation::bridge_pool_roots::validate_bp_roots_vext;
60 use namada_sdk::eth_bridge::storage::bridge_pool::get_key_from_hash;
61 use namada_sdk::eth_bridge::storage::eth_bridge_queries::{
62 EthBridgeQueries, is_bridge_comptime_enabled,
63 };
64 use namada_sdk::ethereum_events::Uint;
65 use namada_sdk::keccak::{KeccakHash, keccak_hash};
66 use namada_sdk::key::*;
67 use namada_sdk::proof_of_stake::storage::{
68 consensus_validator_set_handle,
69 read_consensus_validator_set_addresses_with_stake, read_pos_params,
70 };
71 use namada_sdk::proof_of_stake::types::{
72 Position as ValidatorPosition, WeightedValidator,
73 };
74 use namada_sdk::proof_of_stake::{
75 BecomeValidator, Epoch, become_validator,
76 };
77 use namada_sdk::state::StorageWrite;
78 use namada_sdk::tendermint::abci::types::VoteInfo;
79 use namada_sdk::tx::Signed;
80 use namada_sdk::{governance, token};
81 use namada_vote_ext::bridge_pool_roots;
82
83 use crate::shell::test_utils::*;
84 use crate::shims::abcipp_shim_types::shim::request::FinalizeBlock;
85
86 fn add_validator(shell: &mut TestShell) {
88 let validators_handle = consensus_validator_set_handle();
91 validators_handle
92 .at(&1.into())
93 .at(&token::Amount::native_whole(100))
94 .insert(&mut shell.state, ValidatorPosition(1), bertha_address())
95 .expect("Test failed");
96
97 let mut params =
99 read_pos_params::<_, governance::Store<_>>(&shell.state).unwrap();
100 params.owned.pipeline_len = 1;
101
102 let consensus_key = gen_keypair();
103 let protocol_key = bertha_keypair();
104 let hot_key = gen_secp256k1_keypair();
105 let cold_key = gen_secp256k1_keypair();
106
107 become_validator::<_, governance::Store<_>>(
108 &mut shell.state,
109 BecomeValidator {
110 params: ¶ms,
111 address: &bertha_address(),
112 consensus_key: &consensus_key.ref_to(),
113 protocol_key: &protocol_key.ref_to(),
114 eth_hot_key: &hot_key.ref_to(),
115 eth_cold_key: &cold_key.ref_to(),
116 current_epoch: 0.into(),
117 commission_rate: Default::default(),
118 max_commission_rate_change: Default::default(),
119 metadata: Default::default(),
120 offset_opt: None,
121 },
122 )
123 .expect("Test failed");
124
125 let consensus_set: Vec<WeightedValidator> =
127 read_consensus_validator_set_addresses_with_stake(
128 &shell.state,
129 Epoch::default(),
130 )
131 .unwrap()
132 .into_iter()
133 .collect();
134
135 let val1 = consensus_set[0].clone();
136 let pkh1 = get_pkh_from_address(
137 &shell.state,
138 ¶ms,
139 val1.address.clone(),
140 Epoch::default(),
141 );
142 let votes = vec![VoteInfo {
143 validator: crate::tendermint::abci::types::Validator {
144 address: pkh1,
145 power: (u128::try_from(val1.bonded_stake).expect("Test failed")
146 as u64)
147 .try_into()
148 .unwrap(),
149 },
150 sig_info:
151 crate::tendermint::abci::types::BlockSignatureInfo::LegacySigned,
152 }];
153 let req = FinalizeBlock {
154 proposer_address: pkh1.to_vec(),
155 decided_last_commit: crate::tendermint::abci::types::CommitInfo {
156 round: 0u8.into(),
157 votes,
158 },
159 ..Default::default()
160 };
161 assert_eq!(shell.start_new_epoch(Some(req)).0, 1);
162
163 let to_sign = get_bp_bytes_to_sign();
165 let sig = Signed::<_, SignableEthMessage>::new(&hot_key, to_sign).sig;
166 let vote_ext = bridge_pool_roots::Vext {
167 block_height: shell.state.in_mem().get_last_block_height(),
168 validator_addr: bertha_address(),
169 sig,
170 }
171 .sign(&bertha_keypair());
172 shell.state.in_mem_mut().block.height =
173 shell.state.in_mem().get_last_block_height();
174 shell.commit();
175 assert!(
176 validate_bp_roots_vext::<_, _, governance::Store<_>>(
177 &shell.state,
178 &vote_ext.0,
179 shell.state.in_mem().get_last_block_height()
180 )
181 .is_ok()
182 );
183 }
184
185 #[test]
189 fn test_happy_flow() {
190 if !is_bridge_comptime_enabled() {
191 return;
194 }
195 let (mut shell, _broadcaster, _, _oracle_control_recv) =
196 setup_at_height(1u64);
197 let address = shell
198 .mode
199 .get_validator_address()
200 .expect("Test failed")
201 .clone();
202 shell.state.in_mem_mut().block.height =
203 shell.state.in_mem().get_last_block_height();
204 shell.commit();
205 let to_sign = get_bp_bytes_to_sign();
206 let sig = Signed::<_, SignableEthMessage>::new(
207 shell.mode.get_eth_bridge_keypair().expect("Test failed"),
208 to_sign,
209 )
210 .sig;
211 let vote_ext = bridge_pool_roots::Vext {
212 block_height: shell.state.in_mem().get_last_block_height(),
213 validator_addr: address,
214 sig,
215 }
216 .sign(shell.mode.get_protocol_key().expect("Test failed"));
217 assert_eq!(
218 vote_ext.0,
219 shell.extend_vote_with_bp_roots().expect("Test failed")
220 );
221 assert!(
222 validate_bp_roots_vext::<_, _, governance::Store<_>>(
223 &shell.state,
224 &vote_ext.0,
225 shell.state.in_mem().get_last_block_height(),
226 )
227 .is_ok()
228 )
229 }
230
231 #[test]
234 fn test_vexts_are_de_duped() {
235 if !is_bridge_comptime_enabled() {
236 return;
239 }
240 let (mut shell, _broadcaster, _, _oracle_control_recv) =
241 setup_at_height(1u64);
242 let address = shell
243 .mode
244 .get_validator_address()
245 .expect("Test failed")
246 .clone();
247 shell.state.in_mem_mut().block.height =
248 shell.state.in_mem().get_last_block_height();
249 shell.commit();
250 let to_sign = get_bp_bytes_to_sign();
251 let sig = Signed::<_, SignableEthMessage>::new(
252 shell.mode.get_eth_bridge_keypair().expect("Test failed"),
253 to_sign,
254 )
255 .sig;
256 let vote_ext = bridge_pool_roots::Vext {
257 block_height: shell.state.in_mem().get_last_block_height(),
258 validator_addr: address,
259 sig,
260 }
261 .sign(shell.mode.get_protocol_key().expect("Test failed"));
262 let valid = shell
263 .filter_invalid_bp_roots_vexts(vec![
264 vote_ext.0.clone(),
265 vote_ext.0.clone(),
266 ])
267 .collect::<Vec<_>>();
268 assert_eq!(valid, vec![vote_ext.0]);
269 }
270
271 #[test]
274 fn test_bp_roots_must_be_signed_by_validator() {
275 if !is_bridge_comptime_enabled() {
276 return;
279 }
280 let (mut shell, _broadcaster, _, _oracle_control_recv) =
281 setup_at_height(1u64);
282 let signing_key = gen_keypair();
283 let address = shell
284 .mode
285 .get_validator_address()
286 .expect("Test failed")
287 .clone();
288 shell.state.in_mem_mut().block.height =
289 shell.state.in_mem().get_last_block_height();
290 shell.commit();
291 let to_sign = get_bp_bytes_to_sign();
292 let sig =
293 Signed::<_, SignableEthMessage>::new(&signing_key, to_sign).sig;
294 let bp_root = bridge_pool_roots::Vext {
295 block_height: shell.state.in_mem().get_last_block_height(),
296 validator_addr: address,
297 sig,
298 }
299 .sign(shell.mode.get_protocol_key().expect("Test failed"));
300 assert!(
301 validate_bp_roots_vext::<_, _, governance::Store<_>>(
302 &shell.state,
303 &bp_root.0,
304 shell.get_current_decision_height(),
305 )
306 .is_err()
307 )
308 }
309
310 #[test]
313 fn test_bp_root_sigs_from_same_validator() {
314 if !is_bridge_comptime_enabled() {
315 return;
318 }
319 let (mut shell, _broadcaster, _, _oracle_control_recv) =
320 setup_at_height(3u64);
321 let address = shell
322 .mode
323 .get_validator_address()
324 .expect("Test failed")
325 .clone();
326 add_validator(&mut shell);
327 let to_sign = get_bp_bytes_to_sign();
328 let sig = Signed::<_, SignableEthMessage>::new(
329 shell.mode.get_eth_bridge_keypair().expect("Test failed"),
330 to_sign,
331 )
332 .sig;
333 let bp_root = bridge_pool_roots::Vext {
334 block_height: shell.state.in_mem().get_last_block_height(),
335 validator_addr: address,
336 sig,
337 }
338 .sign(&bertha_keypair());
339 assert!(
340 validate_bp_roots_vext::<_, _, governance::Store<_>>(
341 &shell.state,
342 &bp_root.0,
343 shell.state.in_mem().get_last_block_height()
344 )
345 .is_err()
346 )
347 }
348
349 fn reject_incorrect_block_number(height: BlockHeight, shell: &TestShell) {
350 let address = shell.mode.get_validator_address().unwrap().clone();
351 let to_sign = get_bp_bytes_to_sign();
352 let sig = Signed::<_, SignableEthMessage>::new(
353 shell.mode.get_eth_bridge_keypair().expect("Test failed"),
354 to_sign,
355 )
356 .sig;
357 let bp_root = bridge_pool_roots::Vext {
358 block_height: height,
359 validator_addr: address,
360 sig,
361 }
362 .sign(shell.mode.get_protocol_key().expect("Test failed"));
363
364 assert!(
365 validate_bp_roots_vext::<_, _, governance::Store<_>>(
366 &shell.state,
367 &bp_root.0,
368 shell.state.in_mem().get_last_block_height()
369 )
370 .is_err()
371 )
372 }
373
374 #[test]
377 fn test_block_height_too_high() {
378 if !is_bridge_comptime_enabled() {
379 return;
382 }
383 let (shell, _, _, _) = setup_at_height(3u64);
384 reject_incorrect_block_number(
385 shell.state.in_mem().get_last_block_height() + 1,
386 &shell,
387 );
388 }
389
390 #[test]
393 fn test_reject_genesis_vexts() {
394 if !is_bridge_comptime_enabled() {
395 return;
398 }
399 let (shell, _, _, _) = setup();
400 reject_incorrect_block_number(0.into(), &shell);
401 }
402
403 #[test]
406 fn test_incorrect_nonce() {
407 if !is_bridge_comptime_enabled() {
408 return;
411 }
412 let (shell, _, _, _) = setup();
413 let address = shell.mode.get_validator_address().unwrap().clone();
414 let to_sign = get_bp_bytes_to_sign();
415 let sig = Signed::<_, SignableEthMessage>::new(
416 shell.mode.get_eth_bridge_keypair().expect("Test failed"),
417 to_sign,
418 )
419 .sig;
420 let bp_root = bridge_pool_roots::Vext {
421 block_height: shell.state.in_mem().get_last_block_height(),
422 validator_addr: address,
423 sig,
424 }
425 .sign(shell.mode.get_protocol_key().expect("Test failed"));
426 assert!(
427 validate_bp_roots_vext::<_, _, governance::Store<_>>(
428 &shell.state,
429 &bp_root.0,
430 shell.state.in_mem().get_last_block_height()
431 )
432 .is_err()
433 )
434 }
435
436 #[test]
439 fn test_incorrect_root() {
440 if !is_bridge_comptime_enabled() {
441 return;
444 }
445 let (shell, _, _, _) = setup();
446 let address = shell.mode.get_validator_address().unwrap().clone();
447 let to_sign = get_bp_bytes_to_sign();
448 let sig = Signed::<_, SignableEthMessage>::new(
449 shell.mode.get_eth_bridge_keypair().expect("Test failed"),
450 to_sign,
451 )
452 .sig;
453 let bp_root = bridge_pool_roots::Vext {
454 block_height: shell.state.in_mem().get_last_block_height(),
455 validator_addr: address,
456 sig,
457 }
458 .sign(shell.mode.get_protocol_key().expect("Test failed"));
459 assert!(
460 validate_bp_roots_vext::<_, _, governance::Store<_>>(
461 &shell.state,
462 &bp_root.0,
463 shell.state.in_mem().get_last_block_height()
464 )
465 .is_err()
466 )
467 }
468
469 #[test]
472 fn test_vext_for_old_height() {
473 if !is_bridge_comptime_enabled() {
474 return;
477 }
478 let (mut shell, _recv, _, _oracle_control_recv) = setup_at_height(1u64);
479 let address = shell.mode.get_validator_address().unwrap().clone();
480 shell.state.in_mem_mut().block.height = 2.into();
481 let key = get_key_from_hash(&KeccakHash([1; 32]));
482 let height = shell.state.in_mem().block.height;
483 shell.state.write(&key, height).expect("Test failed");
484 shell.commit();
485 assert_eq!(
486 shell
487 .state
488 .ethbridge_queries()
489 .get_bridge_pool_root_at_height(2.into())
490 .unwrap(),
491 KeccakHash([1; 32])
492 );
493 shell.state.in_mem_mut().block.height = 3.into();
494 shell.state.delete(&key).expect("Test failed");
495 let key = get_key_from_hash(&KeccakHash([2; 32]));
496 let height = shell.state.in_mem().block.height;
497 shell.state.write(&key, height).expect("Test failed");
498 shell.commit();
499 assert_eq!(
500 shell
501 .state
502 .ethbridge_queries()
503 .get_bridge_pool_root_at_height(3.into())
504 .unwrap(),
505 KeccakHash([2; 32])
506 );
507 let to_sign = keccak_hash([[1; 32], Uint::from(0).to_bytes()].concat());
508 let sig = Signed::<_, SignableEthMessage>::new(
509 shell.mode.get_eth_bridge_keypair().expect("Test failed"),
510 to_sign,
511 )
512 .sig;
513 let bp_root = bridge_pool_roots::Vext {
514 block_height: 2.into(),
515 validator_addr: address.clone(),
516 sig,
517 }
518 .sign(shell.mode.get_protocol_key().expect("Test failed"));
519 assert!(
520 validate_bp_roots_vext::<_, _, governance::Store<_>>(
521 &shell.state,
522 &bp_root.0,
523 shell.get_current_decision_height()
524 )
525 .is_ok()
526 );
527 let to_sign = keccak_hash([[2; 32], Uint::from(0).to_bytes()].concat());
528 let sig = Signed::<_, SignableEthMessage>::new(
529 shell.mode.get_eth_bridge_keypair().expect("Test failed"),
530 to_sign,
531 )
532 .sig;
533 let bp_root = bridge_pool_roots::Vext {
534 block_height: 3.into(),
535 validator_addr: address,
536 sig,
537 }
538 .sign(shell.mode.get_protocol_key().expect("Test failed"));
539 assert!(
540 validate_bp_roots_vext::<_, _, governance::Store<_>>(
541 &shell.state,
542 &bp_root.0,
543 shell.get_current_decision_height()
544 )
545 .is_ok()
546 );
547 }
548
549 #[test]
552 fn test_wrong_height_for_root() {
553 if !is_bridge_comptime_enabled() {
554 return;
557 }
558 let (mut shell, _recv, _, _oracle_control_recv) = setup_at_height(1u64);
559 let address = shell.mode.get_validator_address().unwrap().clone();
560 shell.state.in_mem_mut().block.height = 2.into();
561 let key = get_key_from_hash(&KeccakHash([1; 32]));
562 let height = shell.state.in_mem().block.height;
563 shell.state.write(&key, height).expect("Test failed");
564 shell.commit();
565 assert_eq!(
566 shell
567 .state
568 .ethbridge_queries()
569 .get_bridge_pool_root_at_height(2.into())
570 .unwrap(),
571 KeccakHash([1; 32])
572 );
573 shell.state.in_mem_mut().block.height = 3.into();
574 shell.state.delete(&key).expect("Test failed");
575 let key = get_key_from_hash(&KeccakHash([2; 32]));
576 let height = shell.state.in_mem().block.height;
577 shell.state.write(&key, height).expect("Test failed");
578 shell.commit();
579 assert_eq!(
580 shell
581 .state
582 .ethbridge_queries()
583 .get_bridge_pool_root_at_height(3.into())
584 .unwrap(),
585 KeccakHash([2; 32])
586 );
587 let to_sign = keccak_hash([[1; 32], Uint::from(0).to_bytes()].concat());
588 let sig = Signed::<_, SignableEthMessage>::new(
589 shell.mode.get_eth_bridge_keypair().expect("Test failed"),
590 to_sign,
591 )
592 .sig;
593 let bp_root = bridge_pool_roots::Vext {
594 block_height: 3.into(),
595 validator_addr: address,
596 sig,
597 }
598 .sign(shell.mode.get_protocol_key().expect("Test failed"));
599 assert!(
600 validate_bp_roots_vext::<_, _, governance::Store<_>>(
601 &shell.state,
602 &bp_root.0,
603 shell.get_current_decision_height()
604 )
605 .is_err()
606 );
607 }
608}