namada_node/shell/vote_extensions/
bridge_pool_vext.rs

1//! Extend Tendermint votes with signatures of the Ethereum
2//! bridge pool root and nonce seen by a quorum of validators.
3use 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    /// Takes an iterator over Bridge pool root vote extension instances,
13    /// and returns another iterator. The latter yields
14    /// valid Bridge pool root vote extensions, or the reason why these
15    /// are invalid, in the form of a `VoteExtensionError`.
16    #[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    /// Takes a list of signed Bridge pool root vote extensions,
38    /// and filters out invalid instances. This also de-duplicates
39    /// the iterator to be unique per validator address.
40    #[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    /// Make Bertha a validator.
87    fn add_validator(shell: &mut TestShell) {
88        // We make a change so that there Bertha is
89        // a validator in the next epoch
90        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        // change pipeline length to 1
98        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: &params,
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        // we advance forward to the next epoch
126        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            &params,
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        // Check that Bertha's vote extensions pass validation.
164        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 that the function crafting the bridge pool root
186    /// vext creates the expected payload. Check that this
187    /// payload passes validation.
188    #[test]
189    fn test_happy_flow() {
190        if !is_bridge_comptime_enabled() {
191            // NOTE: this test doesn't work if the ethereum bridge
192            // is disabled at compile time.
193            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 that we de-duplicate the bridge pool vexts
232    /// in a block proposal by validator address.
233    #[test]
234    fn test_vexts_are_de_duped() {
235        if !is_bridge_comptime_enabled() {
236            // NOTE: this test doesn't work if the ethereum bridge
237            // is disabled at compile time.
238            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 that Bridge pool roots signed by a non-validator are rejected
272    /// even if the vext is signed by a validator
273    #[test]
274    fn test_bp_roots_must_be_signed_by_validator() {
275        if !is_bridge_comptime_enabled() {
276            // NOTE: this test doesn't work if the ethereum bridge
277            // is disabled at compile time.
278            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 that Bridge pool root vext and inner signature
311    /// are from the same validator.
312    #[test]
313    fn test_bp_root_sigs_from_same_validator() {
314        if !is_bridge_comptime_enabled() {
315            // NOTE: this test doesn't work if the ethereum bridge
316            // is disabled at compile time.
317            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 that an [`bridge_pool_roots::Vext`] that labels its included
375    /// block height as greater than the latest block height is rejected.
376    #[test]
377    fn test_block_height_too_high() {
378        if !is_bridge_comptime_enabled() {
379            // NOTE: this test doesn't work if the ethereum bridge
380            // is disabled at compile time.
381            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 if we reject Bridge pool roots vote extensions
391    /// issued at genesis.
392    #[test]
393    fn test_reject_genesis_vexts() {
394        if !is_bridge_comptime_enabled() {
395            // NOTE: this test doesn't work if the ethereum bridge
396            // is disabled at compile time.
397            return;
398        }
399        let (shell, _, _, _) = setup();
400        reject_incorrect_block_number(0.into(), &shell);
401    }
402
403    /// Test that a bridge pool root vext is rejected
404    /// if the nonce is incorrect.
405    #[test]
406    fn test_incorrect_nonce() {
407        if !is_bridge_comptime_enabled() {
408            // NOTE: this test doesn't work if the ethereum bridge
409            // is disabled at compile time.
410            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 that a bridge pool root vext is rejected
437    /// if the root is incorrect.
438    #[test]
439    fn test_incorrect_root() {
440        if !is_bridge_comptime_enabled() {
441            // NOTE: this test doesn't work if the ethereum bridge
442            // is disabled at compile time.
443            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 that we can verify vext from several block heights
470    /// prior.
471    #[test]
472    fn test_vext_for_old_height() {
473        if !is_bridge_comptime_enabled() {
474            // NOTE: this test doesn't work if the ethereum bridge
475            // is disabled at compile time.
476            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 that if the wrong block height is given for the provided root,
550    /// we reject.
551    #[test]
552    fn test_wrong_height_for_root() {
553        if !is_bridge_comptime_enabled() {
554            // NOTE: this test doesn't work if the ethereum bridge
555            // is disabled at compile time.
556            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}