|
14 | 14 | //
|
15 | 15 | #![allow(clippy::unwrap_used)]
|
16 | 16 | use super::mock::DKGProposalHandler;
|
17 |
| - |
18 | 17 | use crate::{mock::*, Error, SignedProposalBatchOf};
|
19 | 18 | use codec::Encode;
|
20 | 19 | use dkg_runtime_primitives::{
|
21 |
| - offchain::storage_keys::OFFCHAIN_SIGNED_PROPOSALS, ProposalHandlerTrait, TransactionV2, |
22 |
| - TypedChainId, |
| 20 | + keccak_256, offchain::storage_keys::OFFCHAIN_SIGNED_PROPOSALS, utils::ecdsa, |
| 21 | + AggregatedPublicKeys, MaxProposalLength, MisbehaviourType, ProposalHandlerTrait, |
| 22 | + ProposalHeader, TransactionV2, TypedChainId, KEY_TYPE, |
23 | 23 | };
|
24 | 24 | use frame_support::{
|
25 | 25 | assert_err, assert_noop, assert_ok,
|
26 | 26 | traits::{Hooks, OnFinalize},
|
27 | 27 | weights::constants::RocksDbWeight,
|
| 28 | + BoundedVec, |
28 | 29 | };
|
| 30 | +use pallet_dkg_metadata::DKGPublicKey; |
29 | 31 | use sp_core::sr25519;
|
30 |
| -use sp_runtime::offchain::storage::{StorageRetrievalError, StorageValueRef}; |
| 32 | +use sp_io::crypto::{ecdsa_generate, ecdsa_sign_prehashed}; |
| 33 | +use sp_runtime::{ |
| 34 | + app_crypto::ecdsa::Public, |
| 35 | + offchain::storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, |
| 36 | + RuntimeAppPublic, |
| 37 | +}; |
31 | 38 | use sp_std::vec::Vec;
|
32 |
| - |
33 |
| -use dkg_runtime_primitives::ProposalHeader; |
34 |
| -use sp_runtime::offchain::storage::MutateStorageError; |
35 | 39 | use webb_proposals::{Proposal, ProposalKind};
|
36 | 40 |
|
| 41 | +fn mock_pub_key() -> ecdsa::Public { |
| 42 | + ecdsa_generate(KEY_TYPE, None) |
| 43 | +} |
| 44 | + |
| 45 | +pub fn mock_signed_refresh_proposal_batch( |
| 46 | + pub_key: ecdsa::Public, |
| 47 | + batch_id: u32, |
| 48 | + unsigned_proposal: Proposal<MaxProposalLength>, |
| 49 | +) -> SignedProposalBatchOf<Test> { |
| 50 | + let data_to_sign = SignedProposalBatchOf::<Test> { |
| 51 | + proposals: vec![unsigned_proposal.clone()].try_into().unwrap(), |
| 52 | + batch_id, |
| 53 | + signature: vec![].try_into().unwrap(), |
| 54 | + }; |
| 55 | + |
| 56 | + let data_ser = data_to_sign.data(); |
| 57 | + |
| 58 | + let hash = keccak_256(&data_ser); |
| 59 | + let sig = mock_sign_msg(&hash).unwrap().unwrap(); |
| 60 | + |
| 61 | + let mut sig_vec: Vec<u8> = Vec::new(); |
| 62 | + let signature = ecdsa_sign_prehashed(KEY_TYPE, &pub_key, &hash).unwrap(); |
| 63 | + |
| 64 | + SignedProposalBatchOf::<Test> { |
| 65 | + proposals: vec![unsigned_proposal].try_into().unwrap(), |
| 66 | + batch_id, |
| 67 | + signature: sig_vec.try_into().unwrap(), |
| 68 | + } |
| 69 | +} |
| 70 | + |
37 | 71 | // *** Utility ***
|
38 | 72 |
|
39 | 73 | fn add_proposal_to_offchain_storage(prop: SignedProposalBatchOf<Test>) {
|
@@ -65,6 +99,10 @@ fn check_offchain_proposals_num_eq(num: usize) {
|
65 | 99 | assert_eq!(stored_props.unwrap().len(), num);
|
66 | 100 | }
|
67 | 101 |
|
| 102 | +pub fn mock_dkg_id(id: u8) -> DKGId { |
| 103 | + DKGId::from(Public::from_raw([id; 33])) |
| 104 | +} |
| 105 | + |
68 | 106 | // helper function to skip blocks
|
69 | 107 | pub fn run_n_blocks(n: u64) -> u64 {
|
70 | 108 | // lets leave enough weight to read a queue with length one and remove one item
|
@@ -267,41 +305,6 @@ fn submit_signed_proposal_batch_already_exists() {
|
267 | 305 | });
|
268 | 306 | }
|
269 | 307 |
|
270 |
| -#[test] |
271 |
| -fn submit_signed_proposal_fail_invalid_sig() { |
272 |
| - execute_test_with(|| { |
273 |
| - let tx_v_2 = TransactionV2::EIP2930(mock_eth_tx_eip2930(0)); |
274 |
| - |
275 |
| - assert_ok!(DKGProposalHandler::force_submit_unsigned_proposal( |
276 |
| - RuntimeOrigin::root(), |
277 |
| - Proposal::Unsigned { |
278 |
| - kind: ProposalKind::EVM, |
279 |
| - data: tx_v_2.encode().try_into().unwrap() |
280 |
| - }, |
281 |
| - )); |
282 |
| - |
283 |
| - let mut invalid_sig: Vec<u8> = Vec::new(); |
284 |
| - invalid_sig.extend_from_slice(&[0u8, 64]); |
285 |
| - |
286 |
| - let mut signed_proposal = mock_signed_proposal_batch(tx_v_2); |
287 |
| - signed_proposal.signature = invalid_sig.try_into().unwrap(); |
288 |
| - |
289 |
| - // it does not return an error, however the proposal is not added to the list. |
290 |
| - // This is because the signature is invalid, and we are batch processing. |
291 |
| - // we could check for the RuntimeEvent that is emitted. |
292 |
| - assert_ok!(DKGProposalHandler::submit_signed_proposals( |
293 |
| - RuntimeOrigin::signed(sr25519::Public::from_raw([1; 32])), |
294 |
| - vec![signed_proposal] |
295 |
| - )); |
296 |
| - |
297 |
| - assert!( |
298 |
| - DKGProposalHandler::signed_proposals(TypedChainId::Evm(0), 1).is_none(), |
299 |
| - "{}", |
300 |
| - true |
301 |
| - ); |
302 |
| - }); |
303 |
| -} |
304 |
| - |
305 | 308 | pub fn make_header(chain: TypedChainId) -> ProposalHeader {
|
306 | 309 | match chain {
|
307 | 310 | TypedChainId::Evm(_) => ProposalHeader::new(
|
@@ -769,3 +772,79 @@ fn offence_reporting_accepts_proposal_signed_not_in_queue() {
|
769 | 772 | );
|
770 | 773 | });
|
771 | 774 | }
|
| 775 | + |
| 776 | +#[test] |
| 777 | +fn submitting_a_refresh_proposal_will_remove_all_pending_refesh_proposals() { |
| 778 | + execute_test_with(|| { |
| 779 | + // The goal of this test is to ensure that with multiple refresh proposals in the unsigned |
| 780 | + // queue we do not want to sign them all, we should only sign the latest and remove all the |
| 781 | + // others This scenario can only happen when the DKG has stalled and we call force_rotate, |
| 782 | + // in that case the current pending refreshProposal and the newly generated refreshProposal |
| 783 | + // will be signed together resulting in double session rotation. |
| 784 | + |
| 785 | + // create refresh proposal for session 100 |
| 786 | + let next_dkg_key = ecdsa_generate(KEY_TYPE, None); |
| 787 | + let refresh_proposal_100 = pallet_dkg_metadata::Pallet::<Test>::create_refresh_proposal( |
| 788 | + next_dkg_key.0.to_vec(), |
| 789 | + 100, |
| 790 | + ) |
| 791 | + .unwrap(); |
| 792 | + assert_ok!(DKGProposalHandler::force_submit_unsigned_proposal( |
| 793 | + RuntimeOrigin::root(), |
| 794 | + refresh_proposal_100.clone() |
| 795 | + )); |
| 796 | + |
| 797 | + // lets time travel to 5 blocks later and ensure a batch is created |
| 798 | + run_n_blocks(5); |
| 799 | + |
| 800 | + // create refresh proposal for session 101 |
| 801 | + let refresh_proposal_101 = pallet_dkg_metadata::Pallet::<Test>::create_refresh_proposal( |
| 802 | + next_dkg_key.0.to_vec(), |
| 803 | + 101, |
| 804 | + ) |
| 805 | + .unwrap(); |
| 806 | + assert_ok!(DKGProposalHandler::force_submit_unsigned_proposal( |
| 807 | + RuntimeOrigin::root(), |
| 808 | + refresh_proposal_101.clone() |
| 809 | + )); |
| 810 | + |
| 811 | + // lets time travel to 5 blocks later and ensure a batch is created |
| 812 | + run_n_blocks(10); |
| 813 | + |
| 814 | + // there should be two batches with refresh for 100 and 101 |
| 815 | + let unsigned_proposal_batch_100 = |
| 816 | + crate::UnsignedProposalQueue::<Test>::get(TypedChainId::None, 0); |
| 817 | + assert!(unsigned_proposal_batch_100.is_some()); |
| 818 | + let unsigned_proposal_batch_101 = |
| 819 | + crate::UnsignedProposalQueue::<Test>::get(TypedChainId::None, 1); |
| 820 | + assert!(unsigned_proposal_batch_101.is_some()); |
| 821 | + |
| 822 | + // set the DKG key so our signature is accepted |
| 823 | + let mock_dkg_key = mock_pub_key(); |
| 824 | + let input: BoundedVec<_, _> = mock_dkg_key.0.to_vec().try_into().unwrap(); |
| 825 | + DKGPublicKey::<Test>::put((0, input)); |
| 826 | + |
| 827 | + // sign the refresh proposal for session 101 |
| 828 | + let signed_proposal = |
| 829 | + mock_signed_refresh_proposal_batch(mock_dkg_key, 1, refresh_proposal_101.clone()); |
| 830 | + |
| 831 | + assert_ok!(DKGProposalHandler::submit_signed_proposals( |
| 832 | + RuntimeOrigin::signed(sr25519::Public::from_raw([1; 32])), |
| 833 | + vec![signed_proposal.clone()] |
| 834 | + )); |
| 835 | + |
| 836 | + // both refresh for 100 and 101 should be removed from queue |
| 837 | + let unsigned_proposal_batch_100 = |
| 838 | + crate::UnsignedProposalQueue::<Test>::get(TypedChainId::None, 0); |
| 839 | + assert!(unsigned_proposal_batch_100.is_none()); |
| 840 | + let unsigned_proposal_batch_101 = |
| 841 | + crate::UnsignedProposalQueue::<Test>::get(TypedChainId::None, 1); |
| 842 | + assert!(unsigned_proposal_batch_101.is_none()); |
| 843 | + |
| 844 | + // sanity check, only 101 is signed, 100 is not |
| 845 | + let signed_proposal_batch_100 = crate::SignedProposals::<Test>::get(TypedChainId::None, 0); |
| 846 | + assert!(signed_proposal_batch_100.is_none()); |
| 847 | + let signed_proposal_batch_101 = crate::SignedProposals::<Test>::get(TypedChainId::None, 1); |
| 848 | + assert!(signed_proposal_batch_101.is_some()); |
| 849 | + }); |
| 850 | +} |
0 commit comments