From 76d87a2dcaefc65755e45e1c9d9e65abd5199714 Mon Sep 17 00:00:00 2001 From: Ivan Cholakov Date: Wed, 20 Mar 2024 16:58:16 +0200 Subject: [PATCH 01/25] Sys 3931 identify events (#368) --- .../src/ethereum_events_handler.rs | 138 ++++++++++++++++++ node/avn-service/src/lib.rs | 1 + 2 files changed, 139 insertions(+) create mode 100644 node/avn-service/src/ethereum_events_handler.rs diff --git a/node/avn-service/src/ethereum_events_handler.rs b/node/avn-service/src/ethereum_events_handler.rs new file mode 100644 index 000000000..8bf8a0455 --- /dev/null +++ b/node/avn-service/src/ethereum_events_handler.rs @@ -0,0 +1,138 @@ +use sp_avn_common::event_types::{ + AddedValidatorData, AvtGrowthLiftedData, Error, EthEvent, EthEventId, EventData, LiftedData, + NftCancelListingData, NftEndBatchListingData, NftMintData, NftTransferToData, ValidEvents, +}; +use web3::{ + types::{FilterBuilder, Log, H160, H256, U64}, + Web3, +}; + +#[derive(Debug)] +pub enum AppError { + ErrorGettingEventLogs, + MissingTransactionHash, + MissingBlockNumber, + ParsingError(Error) +} + +pub async fn identify_events( + web3: &Web3, + start_block: u64, + end_block: u64, + contract_addresses: Vec, + event_signatures: Vec, +) -> Result, AppError> { + let filter = FilterBuilder::default() + .address(contract_addresses) + .topics(Some(event_signatures), None, None, None) + .from_block(web3::types::BlockNumber::Number(U64::from(start_block))) + .to_block(web3::types::BlockNumber::Number(U64::from(end_block))) + .build(); + + let logs_result = web3.eth().logs(filter).await; + let logs = match logs_result { + Ok(logs) => logs, + Err(_) => return Err(AppError::ErrorGettingEventLogs), + }; + + let mut events = Vec::new(); + + for log in logs { + match parse_log(log) { + Ok(discovered_event) => events.push(discovered_event), + Err(err) => return Err(err), + } + } + + Ok(events) +} + +fn parse_log(log: Log) -> Result { + let web3_signature = log.topics[0]; + let signature = sp_core::H256::from(web3_signature.0); + + let transaction_hash = match log.transaction_hash { + Some(transaction_hash) => transaction_hash, + None => return Err(AppError::MissingTransactionHash), + }; + + let event_id = + EthEventId { signature, transaction_hash: sp_core::H256::from(transaction_hash.0) }; + + let event_data = match signature_to_event_type(signature) { + Some(event_type) => { + let topics: Vec> = log.topics.iter().map(|t| t.0.to_vec()).collect(); + match parse_event_data(event_type, log.data.0, topics) { + Ok(data) => data, + Err(err) => return Err(err), + } + }, + None => return Err(AppError::ErrorGettingEventLogs), + }; + + let block_number = log.block_number.ok_or(AppError::MissingBlockNumber)?; + + Ok(DiscoveredEvent { event: EthEvent { event_id, event_data }, block: block_number.as_u64() }) +} + +fn signature_to_event_type(signature: sp_core::H256) -> Option { + match signature { + signature if signature == ValidEvents::AddedValidator.signature() => + Some(ValidEvents::AddedValidator), + signature if signature == ValidEvents::Lifted.signature() => Some(ValidEvents::Lifted), + signature if signature == ValidEvents::NftMint.signature() => Some(ValidEvents::NftMint), + signature if signature == ValidEvents::NftTransferTo.signature() => + Some(ValidEvents::NftTransferTo), + signature if signature == ValidEvents::NftCancelListing.signature() => + Some(ValidEvents::NftCancelListing), + signature if signature == ValidEvents::NftEndBatchListing.signature() => + Some(ValidEvents::NftEndBatchListing), + signature if signature == ValidEvents::AvtGrowthLifted.signature() => + Some(ValidEvents::AvtGrowthLifted), + _ => None, + } +} + +fn parse_event_data( + event_type: ValidEvents, + data: Vec, + topics: Vec>, +) -> Result { + match event_type { + ValidEvents::AddedValidator => { + AddedValidatorData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogAddedValidator) + } + ValidEvents::Lifted => { + LiftedData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogLifted) + } + ValidEvents::NftMint => { + NftMintData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogNftMinted) + } + ValidEvents::NftTransferTo => { + NftTransferToData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err)) + .map(EventData::LogNftTransferTo) + } + ValidEvents::NftCancelListing => { + NftCancelListingData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogNftCancelListing) + } + ValidEvents::NftEndBatchListing => { + NftEndBatchListingData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogNftEndBatchListing) + } + ValidEvents::AvtGrowthLifted => { + AvtGrowthLiftedData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogAvtGrowthLifted) + } + } +} diff --git a/node/avn-service/src/lib.rs b/node/avn-service/src/lib.rs index becaa7901..117560555 100644 --- a/node/avn-service/src/lib.rs +++ b/node/avn-service/src/lib.rs @@ -21,6 +21,7 @@ use tide::{http::StatusCode, Error as TideError}; pub use web3Secp256k1::SecretKey as web3SecretKey; pub mod extrinsic_utils; +pub mod ethereum_events_handler; pub mod keystore_utils; pub mod merkle_tree_utils; pub mod summary_utils; From 22252f33c0fce92e9eaedbfe23f00c2f556b2a10 Mon Sep 17 00:00:00 2001 From: Thanos <56822898+thadouk@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:15:25 +0000 Subject: [PATCH 02/25] SYS-3635: extends the voting mechanism Introduces the data structures and the voting mechanism for accepting events ranges from ethereum. Ranges that are too big will be broken down into smaller partitions, voted upon individually, and accepted as one. Follow up commit will do the validate unsigned and processing of the partitions. Jira tickets: - SYS-3635 - SYS-3930 --- .../src/ethereum_events_handler.rs | 68 +++++----- node/avn-service/src/lib.rs | 2 +- pallets/eth-bridge/src/lib.rs | 62 ++++++++- primitives/avn-common/src/event_discovery.rs | 118 ++++++++++++++++++ primitives/avn-common/src/lib.rs | 1 + 5 files changed, 208 insertions(+), 43 deletions(-) create mode 100644 primitives/avn-common/src/event_discovery.rs diff --git a/node/avn-service/src/ethereum_events_handler.rs b/node/avn-service/src/ethereum_events_handler.rs index 8bf8a0455..27a9150da 100644 --- a/node/avn-service/src/ethereum_events_handler.rs +++ b/node/avn-service/src/ethereum_events_handler.rs @@ -1,6 +1,10 @@ -use sp_avn_common::event_types::{ - AddedValidatorData, AvtGrowthLiftedData, Error, EthEvent, EthEventId, EventData, LiftedData, - NftCancelListingData, NftEndBatchListingData, NftMintData, NftTransferToData, ValidEvents, +use sp_avn_common::{ + event_discovery::DiscoveredEvent, + event_types::{ + AddedValidatorData, AvtGrowthLiftedData, Error, EthEvent, EthEventId, EventData, + LiftedData, NftCancelListingData, NftEndBatchListingData, NftMintData, NftTransferToData, + ValidEvents, + }, }; use web3::{ types::{FilterBuilder, Log, H160, H256, U64}, @@ -12,7 +16,7 @@ pub enum AppError { ErrorGettingEventLogs, MissingTransactionHash, MissingBlockNumber, - ParsingError(Error) + ParsingError(Error), } pub async fn identify_events( @@ -99,40 +103,26 @@ fn parse_event_data( topics: Vec>, ) -> Result { match event_type { - ValidEvents::AddedValidator => { - AddedValidatorData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogAddedValidator) - } - ValidEvents::Lifted => { - LiftedData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogLifted) - } - ValidEvents::NftMint => { - NftMintData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogNftMinted) - } - ValidEvents::NftTransferTo => { - NftTransferToData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err)) - .map(EventData::LogNftTransferTo) - } - ValidEvents::NftCancelListing => { - NftCancelListingData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogNftCancelListing) - } - ValidEvents::NftEndBatchListing => { - NftEndBatchListingData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogNftEndBatchListing) - } - ValidEvents::AvtGrowthLifted => { - AvtGrowthLiftedData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogAvtGrowthLifted) - } + ValidEvents::AddedValidator => AddedValidatorData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogAddedValidator), + ValidEvents::Lifted => LiftedData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogLifted), + ValidEvents::NftMint => NftMintData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogNftMinted), + ValidEvents::NftTransferTo => NftTransferToData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err)) + .map(EventData::LogNftTransferTo), + ValidEvents::NftCancelListing => NftCancelListingData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogNftCancelListing), + ValidEvents::NftEndBatchListing => NftEndBatchListingData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogNftEndBatchListing), + ValidEvents::AvtGrowthLifted => AvtGrowthLiftedData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogAvtGrowthLifted), } } diff --git a/node/avn-service/src/lib.rs b/node/avn-service/src/lib.rs index 117560555..5e1eda970 100644 --- a/node/avn-service/src/lib.rs +++ b/node/avn-service/src/lib.rs @@ -20,8 +20,8 @@ use secp256k1::{Secp256k1, SecretKey}; use tide::{http::StatusCode, Error as TideError}; pub use web3Secp256k1::SecretKey as web3SecretKey; -pub mod extrinsic_utils; pub mod ethereum_events_handler; +pub mod extrinsic_utils; pub mod keystore_utils; pub mod merkle_tree_utils; pub mod summary_utils; diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 4d2626626..772dfd9eb 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -58,7 +58,9 @@ use alloc::{ }; use codec::{Decode, Encode, MaxEncodedLen}; use core::convert::TryInto; -use frame_support::{dispatch::DispatchResultWithPostInfo, log, traits::IsSubType, BoundedVec}; +use frame_support::{ + dispatch::DispatchResultWithPostInfo, log, traits::IsSubType, BoundedBTreeSet, BoundedVec, +}; use frame_system::{ ensure_none, ensure_root, offchain::{SendTransactionTypes, SubmitTransaction}, @@ -72,7 +74,7 @@ use pallet_session::historical::IdentificationTuple; use sp_staking::offence::ReportOffence; use sp_application_crypto::RuntimeAppPublic; -use sp_avn_common::event_types::Validator; +use sp_avn_common::{event_discovery::*, event_types::Validator}; use sp_core::{ecdsa, ConstU32, H256}; use sp_io::hashing::keccak_256; use sp_runtime::{scale_info::TypeInfo, traits::Dispatchable}; @@ -130,7 +132,11 @@ pub mod pallet { use crate::offence::CorroborationOffenceType; use super::*; - use frame_support::{pallet_prelude::*, traits::UnixTime, Blake2_128Concat}; + use frame_support::{ + pallet_prelude::{ValueQuery, *}, + traits::UnixTime, + Blake2_128Concat, + }; #[pallet::config] pub trait Config: @@ -220,6 +226,21 @@ pub mod pallet { #[pallet::storage] pub type ActiveRequest = StorageValue<_, ActiveRequestData, OptionQuery>; + #[pallet::storage] + #[pallet::getter(fn active_ethereum_range)] + pub type ActiveEthereumRange = StorageValue<_, EthBlockRange, OptionQuery>; + + #[pallet::storage] + pub type DiscoveredEventsShard = StorageDoubleMap< + _, + Blake2_128Concat, + EthBlockRange, + Blake2_128Concat, + DiscoveredEthEventsFraction, + BoundedBTreeSet, + ValueQuery, + >; + #[pallet::genesis_config] pub struct GenesisConfig { pub _phantom: sp_std::marker::PhantomData, @@ -279,6 +300,9 @@ pub mod pallet { LowerParamsError, CallerIdLengthExceeded, NoActiveRequest, + EventVotesFull, + EventVoteExists, + EventScanningDisabled, } #[pallet::call] @@ -477,6 +501,37 @@ pub mod pallet { Self::deposit_event(Event::::ActiveRequestRemoved { request_id }); Ok(().into()) } + + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::add_eth_tx_hash())] + pub fn submit_discovered_events( + origin: OriginFor, + author: Author, + range: EthBlockRange, + events_fraction: DiscoveredEthEventsFraction, + _signature: ::Signature, + ) -> DispatchResultWithPostInfo { + ensure_none(origin)?; + + // Validate that the range is the current one. + let active_range = + Self::active_ethereum_range().ok_or(Error::::EventScanningDisabled)?; + ensure!(range == active_range, Error::::EventScanningDisabled); + + // Check if already vote has been casted + let votes = DiscoveredEventsShard::::get(&range, &events_fraction); + ensure!(votes.contains(&author.account_id) == false, Error::::EventVoteExists); + ensure!(votes.len() as u32 >= AVN::::quorum(), Error::::EventVotesFull); + + DiscoveredEventsShard::::try_mutate(&range, &events_fraction, |votes| { + votes.try_insert(author.account_id) + }) + .map_err(|_| Error::::EventVotesFull)?; + + // TODO process if ready + + Ok(().into()) + } } #[pallet::hooks] @@ -599,6 +654,7 @@ pub mod pallet { #[pallet::validate_unsigned] impl ValidateUnsigned for Pallet { type Call = Call; + // TODO extend for submit_discovered_events // Confirm that the call comes from an author before it can enter the pool: fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { match call { diff --git a/primitives/avn-common/src/event_discovery.rs b/primitives/avn-common/src/event_discovery.rs new file mode 100644 index 000000000..ffce5d2c3 --- /dev/null +++ b/primitives/avn-common/src/event_discovery.rs @@ -0,0 +1,118 @@ +use crate::*; + +use codec::{Decode, Encode, MaxEncodedLen}; +use event_types::EthEvent; +use sp_core::{bounded::BoundedBTreeSet, ConstU32}; +use sp_io::hashing::blake2_256; + +pub type VotesLimit = ConstU32<100>; +pub type EventsBatchLimit = ConstU32<32>; + +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default, TypeInfo, MaxEncodedLen)] +pub struct EthBlockRange { + pub start_block: u32, + pub length: u32, +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default, TypeInfo, MaxEncodedLen)] +pub struct DiscoveredEvent { + pub event: EthEvent, + pub block: u64, +} + +impl PartialOrd for DiscoveredEvent { + fn partial_cmp(&self, other: &Self) -> Option { + // TODO ensure that the comparison is lowercase. + let ord_sig = self.event.event_id.signature.partial_cmp(&other.event.event_id.signature); + + if let Some(core::cmp::Ordering::Equal) = ord_sig { + return ord_sig + } + + match self.block.partial_cmp(&other.block) { + Some(core::cmp::Ordering::Equal) => {}, + ord => return ord, + } + ord_sig + } +} + +impl Ord for DiscoveredEvent { + fn cmp(&self, other: &Self) -> scale_info::prelude::cmp::Ordering { + // TODO ensure that the comparison is lowercase. + let ord_sig = self.event.event_id.signature.cmp(&other.event.event_id.signature); + + if let core::cmp::Ordering::Equal = ord_sig { + return ord_sig + } + + match self.block.cmp(&other.block) { + core::cmp::Ordering::Equal => {}, + ord => return ord, + } + ord_sig + } +} + +type EthEventsPartition = BoundedBTreeSet; +#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +pub struct DiscoveredEthEventsFraction { + data: EthEventsPartition, + fraction: u16, + fraction_count: u16, + id: H256, +} + +impl DiscoveredEthEventsFraction { + pub fn events(&self) -> &EthEventsPartition { + &self.data + } + + pub fn fraction(&self) -> u16 { + self.fraction + } + + pub fn fraction_count(&self) -> u16 { + self.fraction + } + + pub fn id(&self) -> &H256 { + &self.id + } + + fn new(data: EthEventsPartition, fraction: u16, fraction_count: u16, id: &H256) -> Self { + DiscoveredEthEventsFraction { data, fraction, fraction_count, id: id.clone() } + } +} + +pub mod events_helpers { + use super::*; + pub extern crate alloc; + use alloc::collections::BTreeSet; + + pub fn discovered_eth_events_partition_factory( + events: Vec, + ) -> Vec { + let mut sorted = events.clone(); + sorted.sort(); + let chunk_size: usize = >::get() as usize; + let mut fractions = Vec::::new(); + + let mut iter = sorted.chunks(chunk_size).enumerate(); + let fraction_count = sorted.chunks(chunk_size).count() as u16; + let hash: H256 = blake2_256(&(&events, fraction_count).encode()).into(); + + let _ = iter.try_for_each(|(fraction, chunk)| -> Result<(), ()> { + let inner_data: BTreeSet = chunk.iter().cloned().collect(); + let data = EthEventsPartition::try_from(inner_data)?; + fractions.push(DiscoveredEthEventsFraction::new( + data, + fraction as u16, + fraction_count, + &hash, + )); + Ok(()) + }); + fractions + } +} diff --git a/primitives/avn-common/src/lib.rs b/primitives/avn-common/src/lib.rs index 47392f1f1..62d768dbf 100644 --- a/primitives/avn-common/src/lib.rs +++ b/primitives/avn-common/src/lib.rs @@ -20,6 +20,7 @@ pub const CLOSE_BYTES_TAG: &'static [u8] = b""; #[path = "tests/helpers.rs"] pub mod avn_tests_helpers; pub mod eth_key_actions; +pub mod event_discovery; pub mod event_types; pub mod ocw_lock; From 668ec6b0f4b0061bb64f069a6bbcd74c4d2ce399 Mon Sep 17 00:00:00 2001 From: Thanos Doukoudakis <56822898+thadouk@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:42:23 +0000 Subject: [PATCH 03/25] SYS-3930: Extends validate unsigned for new extrinsic (#370) Updates the validate unsigned function to support submit_discovered_events extrinsic. Jira tickets: - SYS-3635 - SYS-3930 --- pallets/eth-bridge/src/lib.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 772dfd9eb..38dffa406 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -126,6 +126,7 @@ const PALLET_NAME: &'static [u8] = b"EthBridge"; const ADD_CONFIRMATION_CONTEXT: &'static [u8] = b"EthBridgeConfirmation"; const ADD_CORROBORATION_CONTEXT: &'static [u8] = b"EthBridgeCorroboration"; const ADD_ETH_TX_HASH_CONTEXT: &'static [u8] = b"EthBridgeEthTxHash"; +const SUBMIT_ETHEREUM_EVENTS_HASH_CONTEXT: &'static [u8] = b"EthBridgeDiscoveredEthEventsHash"; #[frame_support::pallet] pub mod pallet { @@ -504,7 +505,7 @@ pub mod pallet { #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::add_eth_tx_hash())] - pub fn submit_discovered_events( + pub fn submit_ethereum_events( origin: OriginFor, author: Author, range: EthBlockRange, @@ -654,7 +655,6 @@ pub mod pallet { #[pallet::validate_unsigned] impl ValidateUnsigned for Pallet { type Call = Call; - // TODO extend for submit_discovered_events // Confirm that the call comes from an author before it can enter the pool: fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { match call { @@ -709,6 +709,24 @@ pub mod pallet { } else { InvalidTransaction::Custom(3u8).into() }, + Call::submit_ethereum_events { author, range, events_fraction, signature } => + if AVN::::signature_is_valid( + &( + SUBMIT_ETHEREUM_EVENTS_HASH_CONTEXT, + &author.account_id, + range, + events_fraction, + ), + &author, + signature, + ) { + ValidTransaction::with_tag_prefix("EthBridgeAddEventRange") + .and_provides((call, range)) + .priority(TransactionPriority::max_value() / 2) + .build() + } else { + InvalidTransaction::Custom(4u8).into() + }, _ => InvalidTransaction::Call.into(), } From 31f33bfa5e6964331cd10bfa9b6cbfd71963d184 Mon Sep 17 00:00:00 2001 From: Thanos Doukoudakis <56822898+thadouk@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:18:21 +0100 Subject: [PATCH 04/25] SYS-3930: Implement Vote Processing (#371) This PR introduces support for processing votes upon achieving consensus. It extends extrinsics and data structures accordingly. Once the final vote is cast, the voting round for the Ethereum range partition is finalized, and the partition is processed. Additionally, this PR extends the subscription mechanism of the eth-bridge pallet. By default, pallets ignore processed events, requiring the implementation of a handler to process them. Benchmarking for certain actions will be addressed in a separate PR. Jira Tasks: - SYS-3930 - SYS-3635 --- pallets/avn/src/lib.rs | 10 +- pallets/eth-bridge/src/call.rs | 16 ++ pallets/eth-bridge/src/lib.rs | 147 +++++++++++++++---- pallets/eth-bridge/src/types.rs | 16 ++ primitives/avn-common/src/event_discovery.rs | 102 ++++++++----- primitives/avn-common/src/event_types.rs | 2 +- 6 files changed, 228 insertions(+), 65 deletions(-) diff --git a/pallets/avn/src/lib.rs b/pallets/avn/src/lib.rs index b68661e99..90999e4d2 100644 --- a/pallets/avn/src/lib.rs +++ b/pallets/avn/src/lib.rs @@ -37,7 +37,7 @@ pub use pallet::*; use sp_application_crypto::RuntimeAppPublic; use sp_avn_common::{ bounds::MaximumValidatorsBound, - event_types::{EthEventId, Validator}, + event_types::{EthEvent, EthEventId, Validator}, ocw_lock::{self as OcwLock, OcwStorageError}, recover_public_key_from_ecdsa_signature, DEFAULT_EXTERNAL_SERVICE_PORT_NUMBER, EXTERNAL_SERVICE_PORT_NUMBER_KEY, @@ -767,6 +767,9 @@ pub trait BridgeInterfaceNotification { fn process_lower_proof_result(_: u32, _: Vec, _: Result, ()>) -> DispatchResult { Ok(()) } + fn on_event_processed(_event: &EthEvent) -> DispatchResult { + Ok(()) + } } #[impl_trait_for_tuples::impl_for_tuples(30)] @@ -784,6 +787,11 @@ impl BridgeInterfaceNotification for Tuple { for_tuples!( #( Tuple::process_lower_proof_result(_lower_id, _caller_id.clone(), _encoded_lower.clone())?; )* ); Ok(()) } + + fn on_event_processed(_event: &EthEvent) -> DispatchResult { + for_tuples!( #( Tuple::on_event_processed(_event)?; )* ); + Ok(()) + } } #[cfg(test)] diff --git a/pallets/eth-bridge/src/call.rs b/pallets/eth-bridge/src/call.rs index 66695f63f..50cbf9738 100644 --- a/pallets/eth-bridge/src/call.rs +++ b/pallets/eth-bridge/src/call.rs @@ -34,6 +34,15 @@ pub fn add_corroboration( let _ = SubmitTransaction::>::submit_unsigned_transaction(call.into()); } +pub fn submit_ethereum_events( + author: Author, + events_partition: EthereumEventsPartition, + signature: ::Signature, +) -> Result<(), ()> { + let call = Call::::submit_ethereum_events { author, events_partition, signature }; + SubmitTransaction::>::submit_unsigned_transaction(call.into()) +} + fn add_confirmation_proof( tx_id: EthereumId, confirmation: &ecdsa::Signature, @@ -58,3 +67,10 @@ fn add_corroboration_proof( ) -> Vec { (ADD_CORROBORATION_CONTEXT, tx_id, tx_succeeded, tx_hash_is_valid, &account_id).encode() } + +pub fn create_ethereum_events_proof_data( + account_id: &T::AccountId, + events_partition: &EthereumEventsPartition, +) -> Vec { + (SUBMIT_ETHEREUM_EVENTS_HASH_CONTEXT, &account_id, events_partition).encode() +} diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 38dffa406..3af590b55 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -88,6 +88,8 @@ mod types; mod util; use crate::types::*; +pub use call::{create_ethereum_events_proof_data, submit_ethereum_events}; + mod benchmarking; #[cfg(test)] #[path = "tests/lower_proof_tests.rs"] @@ -138,6 +140,7 @@ pub mod pallet { traits::UnixTime, Blake2_128Concat, }; + use sp_avn_common::event_types::{EthEvent, EthEventId, ValidEvents}; #[pallet::config] pub trait Config: @@ -202,6 +205,11 @@ pub mod pallet { BoundedVec<(BoundedVec, BoundedVec), ParamsLimit>, caller_id: BoundedVec, }, + /// EventProcessed(bool, EthEventId) + EventProcessed { + accepted: bool, + eth_event_id: EthEventId, + }, } #[pallet::pallet] @@ -229,15 +237,13 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn active_ethereum_range)] - pub type ActiveEthereumRange = StorageValue<_, EthBlockRange, OptionQuery>; + pub type ActiveEthereumRange = StorageValue<_, ActiveEthRange, OptionQuery>; #[pallet::storage] - pub type DiscoveredEventsShard = StorageDoubleMap< + pub type EthereumEvents = StorageMap< _, Blake2_128Concat, - EthBlockRange, - Blake2_128Concat, - DiscoveredEthEventsFraction, + EthereumEventsPartition, BoundedBTreeSet, ValueQuery, >; @@ -302,8 +308,10 @@ pub mod pallet { CallerIdLengthExceeded, NoActiveRequest, EventVotesFull, + InvalidEventVote, EventVoteExists, - EventScanningDisabled, + NonActiveEthereumRange, + VotingEnded, } #[pallet::call] @@ -508,28 +516,39 @@ pub mod pallet { pub fn submit_ethereum_events( origin: OriginFor, author: Author, - range: EthBlockRange, - events_fraction: DiscoveredEthEventsFraction, + events_partition: EthereumEventsPartition, _signature: ::Signature, ) -> DispatchResultWithPostInfo { ensure_none(origin)?; - // Validate that the range is the current one. - let active_range = - Self::active_ethereum_range().ok_or(Error::::EventScanningDisabled)?; - ensure!(range == active_range, Error::::EventScanningDisabled); + let active_range = match Self::active_ethereum_range() { + Some(active_range) => { + ensure!( + *events_partition.range() == active_range.range && + events_partition.partition() == active_range.partition, + Error::::NonActiveEthereumRange + ); + active_range + }, + None => Default::default(), + }; - // Check if already vote has been casted - let votes = DiscoveredEventsShard::::get(&range, &events_fraction); - ensure!(votes.contains(&author.account_id) == false, Error::::EventVoteExists); - ensure!(votes.len() as u32 >= AVN::::quorum(), Error::::EventVotesFull); + if active_range.is_initial_range() == false { + ensure!( + author_has_cast_event_vote::(&author.account_id) == false, + Error::::EventVoteExists + ); + } - DiscoveredEventsShard::::try_mutate(&range, &events_fraction, |votes| { - votes.try_insert(author.account_id) - }) - .map_err(|_| Error::::EventVotesFull)?; + let mut votes = EthereumEvents::::get(&events_partition); + votes.try_insert(author.account_id).map_err(|_| Error::::EventVotesFull)?; - // TODO process if ready + if votes.len() < AVN::::quorum() as usize { + EthereumEvents::::insert(&events_partition, votes); + } else { + process_ethereum_events_partition::(&active_range, &events_partition); + advance_partition::(&active_range, &events_partition); + } Ok(().into()) } @@ -651,6 +670,81 @@ pub mod pallet { Ok(()) } + fn author_has_cast_event_vote(author: &T::AccountId) -> bool { + for (_partition, votes) in EthereumEvents::::iter() { + if votes.contains(&author) { + return true + } + } + false + } + + fn advance_partition( + active_range: &ActiveEthRange, + approved_partition: &EthereumEventsPartition, + ) { + let next_active_range = if approved_partition.is_last() { + ActiveEthRange { + range: active_range.range.next_range(), + partition: 0, + // TODO retrieve values from runtime. + event_types_filter: Default::default(), + } + } else { + ActiveEthRange { + partition: active_range.partition.saturating_add(1), + ..active_range.clone() + } + }; + ActiveEthereumRange::::put(next_active_range); + } + + fn process_ethereum_events_partition( + active_range: &ActiveEthRange, + partition: &EthereumEventsPartition, + ) { + // Remove entry from storage. Ignore votes. + let _ = EthereumEvents::::take(partition); + for discovered_event in partition.events().iter() { + match ValidEvents::try_from(&discovered_event.event.event_id.signature) { + Some(valid_event) => + if active_range.event_types_filter.contains(&valid_event) { + process_ethereum_event::(&discovered_event.event); + } else { + log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter); + }, + None => log::warn!( + "Unknown Ethereum event signature in range {:?}", + &discovered_event.event.event_id.signature + ), + }; + } + + // Cleanup + for (partition, votes) in EthereumEvents::::drain() { + // TODO raise offences + log::info!("Collators with invalid votes on ethereum events (range: {:?}, partition: {}): {:?}", partition.range(), partition.partition(), votes); + } + } + + fn process_ethereum_event(event: &EthEvent) { + // TODO before processing ensure that the event has not already been processed + match T::BridgeInterfaceNotification::on_event_processed(&event) { + Ok(_) => { + >::deposit_event(Event::::EventProcessed { + accepted: true, + eth_event_id: event.event_id.clone(), + }); + }, + Err(err) => { + log::error!("💔 Processing ethereum event failed: {:?}", err); + >::deposit_event(Event::::EventProcessed { + accepted: false, + eth_event_id: event.event_id.clone(), + }); + }, + }; + } #[pallet::validate_unsigned] impl ValidateUnsigned for Pallet { @@ -709,19 +803,22 @@ pub mod pallet { } else { InvalidTransaction::Custom(3u8).into() }, - Call::submit_ethereum_events { author, range, events_fraction, signature } => + Call::submit_ethereum_events { author, events_partition, signature } => if AVN::::signature_is_valid( &( SUBMIT_ETHEREUM_EVENTS_HASH_CONTEXT, &author.account_id, - range, - events_fraction, + events_partition, ), &author, signature, ) { ValidTransaction::with_tag_prefix("EthBridgeAddEventRange") - .and_provides((call, range)) + .and_provides(( + call, + events_partition.range(), + events_partition.partition(), + )) .priority(TransactionPriority::max_value() / 2) .build() } else { diff --git a/pallets/eth-bridge/src/types.rs b/pallets/eth-bridge/src/types.rs index 477e58407..dcad519ab 100644 --- a/pallets/eth-bridge/src/types.rs +++ b/pallets/eth-bridge/src/types.rs @@ -1,4 +1,7 @@ use crate::*; +use sp_avn_common::event_types::ValidEvents; +type EventsTypesLimit = ConstU32<20>; +type EthBridgeEventsFilter = BoundedBTreeSet; // The different types of request this pallet can handle. #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] @@ -125,3 +128,16 @@ pub struct ActiveEthTransaction { pub invalid_tx_hash_corroborations: BoundedVec, pub tx_succeeded: bool, } + +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default, TypeInfo, MaxEncodedLen)] +pub struct ActiveEthRange { + pub range: EthBlockRange, + pub partition: u16, + pub event_types_filter: EthBridgeEventsFilter, +} + +impl ActiveEthRange { + pub fn is_initial_range(&self) -> bool { + *self == Default::default() + } +} diff --git a/primitives/avn-common/src/event_discovery.rs b/primitives/avn-common/src/event_discovery.rs index ffce5d2c3..754275a83 100644 --- a/primitives/avn-common/src/event_discovery.rs +++ b/primitives/avn-common/src/event_discovery.rs @@ -3,7 +3,7 @@ use crate::*; use codec::{Decode, Encode, MaxEncodedLen}; use event_types::EthEvent; use sp_core::{bounded::BoundedBTreeSet, ConstU32}; -use sp_io::hashing::blake2_256; +use sp_runtime::traits::Saturating; pub type VotesLimit = ConstU32<100>; pub type EventsBatchLimit = ConstU32<32>; @@ -14,6 +14,19 @@ pub struct EthBlockRange { pub length: u32, } +impl EthBlockRange { + pub fn next_range(&self) -> EthBlockRange { + EthBlockRange { + start_block: self.start_block.saturating_add(self.length), + length: self.length, + } + } + pub fn range(&self) -> (u32, u32) { + let end_block = self.start_block.saturating_add(self.length).saturating_less_one(); + (self.start_block, end_block) + } +} + #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default, TypeInfo, MaxEncodedLen)] pub struct DiscoveredEvent { pub event: EthEvent, @@ -54,34 +67,39 @@ impl Ord for DiscoveredEvent { } } -type EthEventsPartition = BoundedBTreeSet; +type EthereumEventsSet = BoundedBTreeSet; #[derive(PartialEq, Eq, Clone, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -pub struct DiscoveredEthEventsFraction { - data: EthEventsPartition, - fraction: u16, - fraction_count: u16, - id: H256, +pub struct EthereumEventsPartition { + range: EthBlockRange, + partition: u16, + is_last: bool, + data: EthereumEventsSet, } -impl DiscoveredEthEventsFraction { - pub fn events(&self) -> &EthEventsPartition { +impl EthereumEventsPartition { + pub fn partition(&self) -> u16 { + self.partition + } + + pub fn events(&self) -> &EthereumEventsSet { &self.data } - pub fn fraction(&self) -> u16 { - self.fraction + pub fn range(&self) -> &EthBlockRange { + &self.range } - pub fn fraction_count(&self) -> u16 { - self.fraction + pub fn is_last(&self) -> bool { + self.is_last } - pub fn id(&self) -> &H256 { - &self.id + pub fn id(&self) -> H256 { + use sp_io::hashing::blake2_256; + blake2_256(&(&self).encode()).into() } - fn new(data: EthEventsPartition, fraction: u16, fraction_count: u16, id: &H256) -> Self { - DiscoveredEthEventsFraction { data, fraction, fraction_count, id: id.clone() } + fn new(range: EthBlockRange, partition: u16, is_last: bool, data: EthereumEventsSet) -> Self { + EthereumEventsPartition { range, partition, is_last, data } } } @@ -91,28 +109,36 @@ pub mod events_helpers { use alloc::collections::BTreeSet; pub fn discovered_eth_events_partition_factory( + range: EthBlockRange, events: Vec, - ) -> Vec { - let mut sorted = events.clone(); - sorted.sort(); + ) -> Vec { + let sorted_events = { + let mut mut_events = events.clone(); + mut_events.sort(); + mut_events + }; + let chunk_size: usize = >::get() as usize; - let mut fractions = Vec::::new(); - - let mut iter = sorted.chunks(chunk_size).enumerate(); - let fraction_count = sorted.chunks(chunk_size).count() as u16; - let hash: H256 = blake2_256(&(&events, fraction_count).encode()).into(); - - let _ = iter.try_for_each(|(fraction, chunk)| -> Result<(), ()> { - let inner_data: BTreeSet = chunk.iter().cloned().collect(); - let data = EthEventsPartition::try_from(inner_data)?; - fractions.push(DiscoveredEthEventsFraction::new( - data, - fraction as u16, - fraction_count, - &hash, - )); - Ok(()) - }); - fractions + let mut partitions = Vec::::new(); + + let event_chunks: Vec<_> = sorted_events.chunks(chunk_size).collect(); + let partitions_count = event_chunks.len(); + + let _ = + event_chunks + .iter() + .enumerate() + .try_for_each(|(partition, chunk)| -> Result<(), ()> { + let inner_data: BTreeSet = chunk.iter().cloned().collect(); + let data = EthereumEventsSet::try_from(inner_data)?; + partitions.push(EthereumEventsPartition::new( + range.clone(), + partition as u16, + partitions_count == partition.saturating_add(1), + data, + )); + Ok(()) + }); + partitions } } diff --git a/primitives/avn-common/src/event_types.rs b/primitives/avn-common/src/event_types.rs index 182006504..250dafbb1 100644 --- a/primitives/avn-common/src/event_types.rs +++ b/primitives/avn-common/src/event_types.rs @@ -65,7 +65,7 @@ pub enum Error { AvtLowerClaimedEventIdConversion, } -#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialOrd, Ord, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub enum ValidEvents { AddedValidator, Lifted, From 5d718a77147e7a573b0c3322521e92f54939dbc2 Mon Sep 17 00:00:00 2001 From: Ivan Cholakov Date: Wed, 10 Apr 2024 17:57:09 +0300 Subject: [PATCH 05/25] feat: added runtime-api for eth-event polling (#376) Signed-off-by: Ivan Cholakov Co-authored-by: Thanos Doukoudakis <56822898+thadouk@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71f8d1ca5..95cdf38ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6627,9 +6627,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" From ad9c0ffc0442af39016919a2dc1a2efd1372e268 Mon Sep 17 00:00:00 2001 From: Ivan Cholakov Date: Wed, 10 Apr 2024 17:57:09 +0300 Subject: [PATCH 06/25] feat: added runtime-api for eth-event polling (#376) Signed-off-by: Ivan Cholakov Co-authored-by: Thanos Doukoudakis <56822898+thadouk@users.noreply.github.com> --- Cargo.lock | 25 +++++++++ Cargo.toml | 1 + Dockerfile.22_04 | 1 + pallets/eth-bridge/Cargo.toml | 2 + pallets/eth-bridge/runtime-api/Cargo.toml | 37 +++++++++++++ pallets/eth-bridge/runtime-api/src/lib.rs | 25 +++++++++ pallets/eth-bridge/src/lib.rs | 63 +++++++++++++++++++++-- runtime/avn/Cargo.toml | 1 + runtime/avn/src/lib.rs | 35 ++++++++++++- 9 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 pallets/eth-bridge/runtime-api/Cargo.toml create mode 100644 pallets/eth-bridge/runtime-api/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 95cdf38ee..150f24e34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,6 +740,8 @@ dependencies = [ "log", "node-primitives", "pallet-avn", + "pallet-eth-bridge", + "pallet-eth-bridge-runtime-api", "pallet-im-online", "pallet-transaction-payment-rpc", "parity-scale-codec 3.6.4", @@ -826,6 +828,7 @@ dependencies = [ "pallet-balances", "pallet-conviction-voting", "pallet-eth-bridge", + "pallet-eth-bridge-runtime-api", "pallet-ethereum-events", "pallet-im-online", "pallet-nft-manager", @@ -975,17 +978,24 @@ dependencies = [ "jsonrpc-core", "jsonrpsee", "log", + "node-primitives", + "pallet-eth-bridge", + "pallet-eth-bridge-runtime-api", "parity-scale-codec 3.6.4", "sc-client-api", "sc-client-db", "sc-keystore", "sc-service", + "sc-transaction-pool", + "sc-transaction-pool-api", "secp256k1 0.21.3", "secp256k1 0.24.3", "serde", "serde_json", "sp-api", "sp-avn-common", + "sp-block-builder", + "sp-blockchain", "sp-core", "sp-keystore", "sp-runtime", @@ -7175,6 +7185,7 @@ dependencies = [ "parking_lot 0.12.1", "rand 0.8.5", "scale-info", + "sp-api", "sp-application-crypto", "sp-avn-common", "sp-core", @@ -7184,6 +7195,20 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-eth-bridge-runtime-api" +version = "5.2.2" +dependencies = [ + "frame-support", + "pallet-avn", + "pallet-eth-bridge", + "parity-scale-codec 3.6.4", + "sp-api", + "sp-application-crypto", + "sp-avn-common", + "sp-core", +] + [[package]] name = "pallet-ethereum-events" version = "5.2.3" diff --git a/Cargo.toml b/Cargo.toml index d7bc2dd7b..7e079cf32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "node/avn-service", "node/avn-lower-rpc", "pallets/*", + "pallets/*/runtime-api", "primitives/*", "runtime/*", ] diff --git a/Dockerfile.22_04 b/Dockerfile.22_04 index 0b11ec6be..5176a0e34 100644 --- a/Dockerfile.22_04 +++ b/Dockerfile.22_04 @@ -24,6 +24,7 @@ RUN apt-get update && \ ca-certificates \ curl \ jq && \ + update-ca-certificates && \ # apt cleanup apt-get autoremove -y && \ apt-get clean && \ diff --git a/pallets/eth-bridge/Cargo.toml b/pallets/eth-bridge/Cargo.toml index 48c7f6b1c..da3830ba9 100644 --- a/pallets/eth-bridge/Cargo.toml +++ b/pallets/eth-bridge/Cargo.toml @@ -28,6 +28,7 @@ frame-system = { default-features = false, git = "https://github.com/paritytech/ pallet-timestamp = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } pallet-session = {default-features = false, features = ["historical"], git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } pallet-avn = { default-features = false, path = "../avn" } +sp-api = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v1.0.0" } # Optional imports for benchmarking frame-benchmarking = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0", optional = true } @@ -42,6 +43,7 @@ std = [ "frame-benchmarking?/std", "scale-info/std", "codec/std", + "sp-api/std", "sp-std/std", "sp-core/std", "sp-io/std", diff --git a/pallets/eth-bridge/runtime-api/Cargo.toml b/pallets/eth-bridge/runtime-api/Cargo.toml new file mode 100644 index 000000000..a8bb7db2c --- /dev/null +++ b/pallets/eth-bridge/runtime-api/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "pallet-eth-bridge-runtime-api" +description = "Runtime API for module" +license = "GPL-3.0" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +rust-version = { workspace = true } + + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"], default-features = false } +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +pallet-eth-bridge = { default-features = false, path = "../../eth-bridge" } +pallet-avn = { path = "../../avn", default-features = false } +sp-api = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v1.0.0" } +sp-avn-common = { default-features = false, path = "../../../primitives/avn-common" } +sp-application-crypto = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } + + + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "pallet-eth-bridge/std", + "sp-api/std", +] + + diff --git a/pallets/eth-bridge/runtime-api/src/lib.rs b/pallets/eth-bridge/runtime-api/src/lib.rs new file mode 100644 index 000000000..7c792f184 --- /dev/null +++ b/pallets/eth-bridge/runtime-api/src/lib.rs @@ -0,0 +1,25 @@ +#![cfg_attr(not(feature = "std"), no_std)] +use codec::Codec; +use frame_support::dispatch::Vec; +use sp_avn_common::event_discovery::{EthBlockRange, EthereumEventsPartition}; +use sp_core::{H160, H256}; + +sp_api::decl_runtime_apis! { + + #[api_version(1)] + pub trait EthEventHandlerApi + where + AccountId: Codec, + { + fn query_active_block_range()-> (EthBlockRange, u16); + fn query_has_author_casted_event_vote(account_id: AccountId) -> bool; + fn query_signatures() -> Vec; + fn query_bridge_contract() -> H160; + fn create_proof(account_id:AccountId, events_partition:EthereumEventsPartition)-> Vec; + fn submit_vote( + author: AccountId, + events_partition: EthereumEventsPartition, + signature: sp_core::sr25519::Signature + ) -> Result<(), ()>; + } +} diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 3af590b55..b4ffc2c11 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -74,8 +74,11 @@ use pallet_session::historical::IdentificationTuple; use sp_staking::offence::ReportOffence; use sp_application_crypto::RuntimeAppPublic; -use sp_avn_common::{event_discovery::*, event_types::Validator}; -use sp_core::{ecdsa, ConstU32, H256}; +use sp_avn_common::{ + event_discovery::*, + event_types::{ValidEvents, Validator}, +}; +use sp_core::{ecdsa, ConstU32, H160, H256}; use sp_io::hashing::keccak_256; use sp_runtime::{scale_info::TypeInfo, traits::Dispatchable}; use sp_std::prelude::*; @@ -84,7 +87,7 @@ mod call; mod eth; mod request; mod tx; -mod types; +pub mod types; mod util; use crate::types::*; @@ -312,6 +315,7 @@ pub mod pallet { EventVoteExists, NonActiveEthereumRange, VotingEnded, + ValidatorNotFound } #[pallet::call] @@ -670,7 +674,7 @@ pub mod pallet { Ok(()) } - fn author_has_cast_event_vote(author: &T::AccountId) -> bool { + pub fn author_has_cast_event_vote(author: &T::AccountId) -> bool { for (_partition, votes) in EthereumEvents::::iter() { if votes.contains(&author) { return true @@ -868,3 +872,54 @@ pub mod pallet { } } } + +impl Pallet { + pub fn create_eth_events_proof(account_id:T::AccountId, events_partition:EthereumEventsPartition) -> Vec{ + create_ethereum_events_proof_data::(&account_id, &events_partition) + } + pub fn signatures() -> Vec { + let signatures: Vec = match Self::active_ethereum_range() { + Some(active_range) => { + let _events = + active_range.event_types_filter.into_iter().collect::>(); + + let decoded_hex = + hex::decode("418da8f85cfa851601f87634c6950491b6b8785a6445c8584f5658048d512cae"). + expect("test"); + + let mut array = [0; 32]; + array.copy_from_slice(&decoded_hex); + let decoded_event_sig = H256::from(array); + + vec![decoded_event_sig] + }, + None => { + // TODO use values from pallet constant + // vec![] + let decoded_hex = + hex::decode("418da8f85cfa851601f87634c6950491b6b8785a6445c8584f5658048d512cae"). + expect("test"); + + let mut array = [0; 32]; + array.copy_from_slice(&decoded_hex); + let decoded_event_sig = H256::from(array); + + vec![decoded_event_sig] + }, + }; + signatures + } + pub fn submit_vote( + account_id: T::AccountId, + events_partition: EthereumEventsPartition, + signature: ::Signature + ) -> Result<(), ()>{ + let validator: Author = AVN::::validators().into_iter().filter(|v| v.account_id == account_id).nth(0).unwrap(); + + submit_ethereum_events::(validator, events_partition, signature) + } + + pub fn get_bridge_contract() -> H160 { + AVN::::get_bridge_contract_address() + } +} diff --git a/runtime/avn/Cargo.toml b/runtime/avn/Cargo.toml index 37b8b8a37..6f93e823d 100644 --- a/runtime/avn/Cargo.toml +++ b/runtime/avn/Cargo.toml @@ -95,6 +95,7 @@ pallet-nft-manager = { path = "../../pallets/nft-manager", default-features = fa pallet-avn-proxy = { path = "../../pallets/avn-proxy", default-features = false } pallet-avn-transaction-payment = { path = "../../pallets/avn-transaction-payment", default-features = false } pallet-eth-bridge = { path = "../../pallets/eth-bridge", default-features = false } +pallet-eth-bridge-runtime-api = { path = "../../pallets/eth-bridge/runtime-api", default-features = false } pallet-parachain-staking = { path = "../../pallets/parachain-staking", default-features = false } # Common Runtime diff --git a/runtime/avn/src/lib.rs b/runtime/avn/src/lib.rs index 69cd538ce..a2abfe78e 100644 --- a/runtime/avn/src/lib.rs +++ b/runtime/avn/src/lib.rs @@ -68,7 +68,7 @@ use pallet_avn::sr25519::AuthorityId as AvnId; pub use pallet_avn_proxy::{Event as AvnProxyEvent, ProvableProxy}; use pallet_avn_transaction_payment::AvnCurrencyAdapter; -use sp_avn_common::{InnerCallValidator, Proof}; +use sp_avn_common::{event_discovery::{EthBlockRange, EthereumEventsPartition}, InnerCallValidator, Proof}; use pallet_parachain_staking; pub type NegativeImbalance = as Currency< @@ -940,6 +940,39 @@ impl_runtime_apis! { TransactionPayment::length_to_fee(length) } } + + impl pallet_eth_bridge_runtime_api::EthEventHandlerApi for Runtime { + fn query_active_block_range()-> (EthBlockRange, u16){ + if let Some(active_eth_range) = EthBridge::active_ethereum_range(){ + (active_eth_range.range, active_eth_range.partition) + }else { + (EthBlockRange::default(), 0) + } + } + fn query_has_author_casted_event_vote(account_id: AccountId) -> bool{ + pallet_eth_bridge::author_has_cast_event_vote::(&account_id) + } + + fn query_signatures() -> Vec { + EthBridge::signatures() + } + + fn query_bridge_contract() -> H160 { + EthBridge::get_bridge_contract() + } + + fn create_proof(account_id:AccountId, events_partition:EthereumEventsPartition)->Vec{ + EthBridge::create_eth_events_proof(account_id, events_partition) + } + + fn submit_vote(author: AccountId, + events_partition: EthereumEventsPartition, + signature: sp_core::sr25519::Signature, + ) -> Result<(),()>{ + EthBridge::submit_vote(author, events_partition, signature.into()) + } + } + impl cumulus_primitives_core::CollectCollationInfo for Runtime { fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { ParachainSystem::collect_collation_info(header) From 88f7000d77de9110cf4ddb93515a2e412e7d13fe Mon Sep 17 00:00:00 2001 From: Ivan Cholakov Date: Tue, 16 Apr 2024 15:50:42 +0300 Subject: [PATCH 07/25] SYS-3932 - implement initial version of polling mechanism (#378) --- node/Cargo.toml | 2 + node/avn-service/Cargo.toml | 9 + .../src/ethereum_events_handler.rs | 529 +++++++++++++++--- node/src/common.rs | 6 +- node/src/service.rs | 24 +- pallets/eth-bridge/runtime-api/src/lib.rs | 1 + runtime/avn/src/lib.rs | 7 +- 7 files changed, 506 insertions(+), 72 deletions(-) diff --git a/node/Cargo.toml b/node/Cargo.toml index 41cc8a52f..abfcb9582 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -95,6 +95,8 @@ cumulus-relay-chain-interface = { git = "https://github.com/paritytech/cumulus.g # AvN avn-service = { path = "avn-service" } avn-lower-rpc = { path = "avn-lower-rpc" } +pallet-eth-bridge = { default-features = false, path = "../pallets/eth-bridge" } +pallet-eth-bridge-runtime-api = { path = "../pallets/eth-bridge/runtime-api", default-features = false } pallet-avn = { path = "../pallets/avn", default-features = false } tiny-bip39 = "0.8.2" diff --git a/node/avn-service/Cargo.toml b/node/avn-service/Cargo.toml index 80018deeb..12f5187b8 100644 --- a/node/avn-service/Cargo.toml +++ b/node/avn-service/Cargo.toml @@ -40,6 +40,11 @@ web3Secp256k1 = { package = "secp256k1", version = "0.21.2", default-features = # This needs to be the same version as the one used in web3, parity-scale-codec and ethereum-transactions pallet ethereum-types = "0.11.0" +pallet-eth-bridge = { path = "../../pallets/eth-bridge", default-features = false } +pallet-eth-bridge-runtime-api = { path = "../../pallets/eth-bridge/runtime-api", default-features = false } +sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } + # primitives sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } @@ -53,5 +58,9 @@ sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "polka sc-service = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } sc-client-db = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +sc-transaction-pool-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +node-primitives = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } + diff --git a/node/avn-service/src/ethereum_events_handler.rs b/node/avn-service/src/ethereum_events_handler.rs index 27a9150da..62001460d 100644 --- a/node/avn-service/src/ethereum_events_handler.rs +++ b/node/avn-service/src/ethereum_events_handler.rs @@ -1,30 +1,156 @@ +use crate::BlockT; +use futures::lock::Mutex; +use node_primitives::AccountId; +use pallet_eth_bridge_runtime_api::EthEventHandlerApi; +use sc_client_api::{BlockBackend, UsageProvider}; +use sc_keystore::LocalKeystore; +use sp_api::ApiExt; use sp_avn_common::{ - event_discovery::DiscoveredEvent, + event_discovery::{ + events_helpers::discovered_eth_events_partition_factory, DiscoveredEvent, EthBlockRange, + }, event_types::{ AddedValidatorData, AvtGrowthLiftedData, Error, EthEvent, EthEventId, EventData, LiftedData, NftCancelListingData, NftEndBatchListingData, NftMintData, NftTransferToData, ValidEvents, }, + AVN_KEY_ID, }; +use sp_block_builder::BlockBuilder; +use sp_blockchain::HeaderBackend; +use sp_core::H256 as SpH256; +use sp_keystore::Keystore; +use sp_runtime::AccountId32; +use std::{collections::HashMap, marker::PhantomData, time::Instant}; +pub use std::{path::PathBuf, sync::Arc}; +use tide::Error as TideError; +use tokio::time::{sleep, Duration}; use web3::{ - types::{FilterBuilder, Log, H160, H256, U64}, + types::{FilterBuilder, Log, H160, H256 as Web3H256, U64}, Web3, }; +use crate::{server_error, setup_web3_connection, Web3Data}; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; + +pub struct EventInfo { + event_type: ValidEvents, + parser: fn(Vec, Vec>) -> Result, +} + +pub struct EventRegistry { + registry: HashMap, +} + +impl EventRegistry { + pub fn new() -> Self { + let mut m = HashMap::new(); + m.insert( + ValidEvents::AddedValidator.signature(), + EventInfo { + event_type: ValidEvents::AddedValidator, + parser: |data, topics| { + AddedValidatorData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(EventData::LogAddedValidator) + }, + }, + ); + m.insert( + ValidEvents::Lifted.signature(), + EventInfo { + event_type: ValidEvents::Lifted, + parser: |data, topics| { + LiftedData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(|data| EventData::LogLifted(data)) + }, + }, + ); + m.insert( + ValidEvents::AvtGrowthLifted.signature(), + EventInfo { + event_type: ValidEvents::AvtGrowthLifted, + parser: |data, topics| { + AvtGrowthLiftedData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(|data| EventData::LogAvtGrowthLifted(data)) + }, + }, + ); + m.insert( + ValidEvents::NftCancelListing.signature(), + EventInfo { + event_type: ValidEvents::NftCancelListing, + parser: |data, topics| { + NftCancelListingData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(|data| EventData::LogNftCancelListing(data)) + }, + }, + ); + m.insert( + ValidEvents::NftEndBatchListing.signature(), + EventInfo { + event_type: ValidEvents::NftEndBatchListing, + parser: |data, topics| { + NftEndBatchListingData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(|data| EventData::LogNftEndBatchListing(data)) + }, + }, + ); + m.insert( + ValidEvents::NftMint.signature(), + EventInfo { + event_type: ValidEvents::NftMint, + parser: |data, topics| { + NftMintData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(|data| EventData::LogNftMinted(data)) + }, + }, + ); + m.insert( + ValidEvents::NftTransferTo.signature(), + EventInfo { + event_type: ValidEvents::NftTransferTo, + parser: |data, topics| { + NftTransferToData::parse_bytes(Some(data), topics) + .map_err(|err| AppError::ParsingError(err.into())) + .map(|data| EventData::LogNftTransferTo(data)) + }, + }, + ); + EventRegistry { registry: m } + } + + pub fn get_event_info(&self, signature: &SpH256) -> Option<&EventInfo> { + self.registry.get(signature) + } +} + #[derive(Debug)] pub enum AppError { + ErrorParsingEventLogs, ErrorGettingEventLogs, + ErrorGettingBridgeContract, + Web3RetryLimitReached, + SignatureGenerationFailed, MissingTransactionHash, MissingBlockNumber, + MissingEventSignature, ParsingError(Error), + GenericError(String) } pub async fn identify_events( web3: &Web3, - start_block: u64, - end_block: u64, + start_block: u32, + end_block: u32, contract_addresses: Vec, - event_signatures: Vec, + event_signatures: Vec, + events_registry: &EventRegistry, ) -> Result, AppError> { let filter = FilterBuilder::default() .address(contract_addresses) @@ -32,7 +158,6 @@ pub async fn identify_events( .from_block(web3::types::BlockNumber::Number(U64::from(start_block))) .to_block(web3::types::BlockNumber::Number(U64::from(end_block))) .build(); - let logs_result = web3.eth().logs(filter).await; let logs = match logs_result { Ok(logs) => logs, @@ -42,87 +167,357 @@ pub async fn identify_events( let mut events = Vec::new(); for log in logs { - match parse_log(log) { + match parse_log(log, events_registry) { Ok(discovered_event) => events.push(discovered_event), Err(err) => return Err(err), } } - Ok(events) } -fn parse_log(log: Log) -> Result { +fn parse_log(log: Log, events_registry: &EventRegistry) -> Result { + if log.topics.is_empty() { + return Err(AppError::MissingEventSignature) + } + let web3_signature = log.topics[0]; - let signature = sp_core::H256::from(web3_signature.0); + let signature = SpH256::from(web3_signature.0); - let transaction_hash = match log.transaction_hash { - Some(transaction_hash) => transaction_hash, - None => return Err(AppError::MissingTransactionHash), - }; + let transaction_hash = log.transaction_hash.ok_or(AppError::MissingTransactionHash)?; - let event_id = - EthEventId { signature, transaction_hash: sp_core::H256::from(transaction_hash.0) }; + let event_id = EthEventId { signature, transaction_hash: SpH256::from(transaction_hash.0) }; - let event_data = match signature_to_event_type(signature) { - Some(event_type) => { - let topics: Vec> = log.topics.iter().map(|t| t.0.to_vec()).collect(); - match parse_event_data(event_type, log.data.0, topics) { - Ok(data) => data, - Err(err) => return Err(err), - } - }, - None => return Err(AppError::ErrorGettingEventLogs), - }; + let topics: Vec> = log.topics.iter().map(|t| t.0.to_vec()).collect(); + let event_data = parse_event_data(signature, log.data.0, topics, events_registry) + .or_else(|_| Err(AppError::ErrorParsingEventLogs))?; let block_number = log.block_number.ok_or(AppError::MissingBlockNumber)?; Ok(DiscoveredEvent { event: EthEvent { event_id, event_data }, block: block_number.as_u64() }) } -fn signature_to_event_type(signature: sp_core::H256) -> Option { - match signature { - signature if signature == ValidEvents::AddedValidator.signature() => - Some(ValidEvents::AddedValidator), - signature if signature == ValidEvents::Lifted.signature() => Some(ValidEvents::Lifted), - signature if signature == ValidEvents::NftMint.signature() => Some(ValidEvents::NftMint), - signature if signature == ValidEvents::NftTransferTo.signature() => - Some(ValidEvents::NftTransferTo), - signature if signature == ValidEvents::NftCancelListing.signature() => - Some(ValidEvents::NftCancelListing), - signature if signature == ValidEvents::NftEndBatchListing.signature() => - Some(ValidEvents::NftEndBatchListing), - signature if signature == ValidEvents::AvtGrowthLifted.signature() => - Some(ValidEvents::AvtGrowthLifted), - _ => None, - } -} - fn parse_event_data( - event_type: ValidEvents, + signature: SpH256, data: Vec, topics: Vec>, + events_registry: &EventRegistry, ) -> Result { - match event_type { - ValidEvents::AddedValidator => AddedValidatorData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogAddedValidator), - ValidEvents::Lifted => LiftedData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogLifted), - ValidEvents::NftMint => NftMintData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogNftMinted), - ValidEvents::NftTransferTo => NftTransferToData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err)) - .map(EventData::LogNftTransferTo), - ValidEvents::NftCancelListing => NftCancelListingData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogNftCancelListing), - ValidEvents::NftEndBatchListing => NftEndBatchListingData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogNftEndBatchListing), - ValidEvents::AvtGrowthLifted => AvtGrowthLiftedData::parse_bytes(Some(data), topics) - .map_err(|err| AppError::ParsingError(err.into())) - .map(EventData::LogAvtGrowthLifted), + (events_registry + .get_event_info(&signature) + .ok_or(AppError::ErrorParsingEventLogs)? + .parser)(data, topics) +} + +pub struct EthEventHandlerConfig +where + Block: BlockT, + ClientT: BlockBackend + + UsageProvider + + HeaderBackend + + sp_api::ProvideRuntimeApi, + ClientT::Api: pallet_eth_bridge_runtime_api::EthEventHandlerApi + + ApiExt + + BlockBuilder, +{ + pub keystore: Arc, + pub keystore_path: PathBuf, + pub avn_port: Option, + pub eth_node_url: String, + pub web3_data_mutex: Arc>, + pub client: Arc, + pub _block: PhantomData, + pub offchain_transaction_pool_factory: OffchainTransactionPoolFactory, +} + +impl< + Block: BlockT, + ClientT: BlockBackend + + UsageProvider + + HeaderBackend + + sp_api::ProvideRuntimeApi, + > EthEventHandlerConfig +where + ClientT::Api: pallet_eth_bridge_runtime_api::EthEventHandlerApi + + ApiExt + + BlockBuilder, +{ + pub async fn initialise_web3(&self) -> Result<(), TideError> { + if let Some(mut web3_data_mutex) = self.web3_data_mutex.try_lock() { + if web3_data_mutex.web3.is_some() { + log::info!( + "⛓️ avn-service: web3 connection has already been initialised, skipping" + ); + return Ok(()) + } + + let web3_init_time = Instant::now(); + log::info!("⛓️ avn-service: web3 initialisation start"); + + let web3 = setup_web3_connection(&self.eth_node_url); + if web3.is_none() { + log::error!( + "💔 Error creating a web3 connection. URL is not valid {:?}", + &self.eth_node_url + ); + return Err(server_error("Error creating a web3 connection".to_string())) + } + + log::info!("⏲️ web3 init task completed in: {:?}", web3_init_time.elapsed()); + web3_data_mutex.web3 = web3; + Ok(()) + } else { + Err(server_error("Failed to acquire web3 data mutex.".to_string())) + } + } +} + +pub const SLEEP_TIME: u64 = 60; +pub const RETRY_LIMIT: usize = 3; +pub const RETRY_DELAY: u64 = 5; + +async fn initialize_web3_with_retries( + config: &EthEventHandlerConfig, +) -> Result<(), AppError> +where + Block: BlockT, + ClientT: BlockBackend + + UsageProvider + + HeaderBackend + + sp_api::ProvideRuntimeApi, + ClientT::Api: pallet_eth_bridge_runtime_api::EthEventHandlerApi + + ApiExt + + BlockBuilder, +{ + let mut attempts = 0; + + while attempts < RETRY_LIMIT { + match config.initialise_web3().await { + Ok(_) => { + log::info!("Successfully initialized web3 connection."); + return Ok(()) + }, + Err(e) => { + attempts += 1; + log::error!("Failed to initialize web3 (attempt {}): {:?}", attempts, e); + if attempts >= RETRY_LIMIT { + log::error!("Reached maximum retry limit for initializing web3."); + return Err(AppError::Web3RetryLimitReached) + } + sleep(Duration::from_secs(RETRY_DELAY)).await; + }, + } + } + + Err(AppError::GenericError("Failed to initialize web3 after multiple attempts.".to_string())) +} + +pub async fn start_eth_event_handler(config: EthEventHandlerConfig) +where + Block: BlockT, + ClientT: BlockBackend + + UsageProvider + + HeaderBackend + + sp_api::ProvideRuntimeApi, + ClientT::Api: pallet_eth_bridge_runtime_api::EthEventHandlerApi + + ApiExt + + BlockBuilder, +{ + if let Err(e) = initialize_web3_with_retries(&config).await { + log::error!("Web3 initialization ultimately failed: {:?}", e); + return + } + + let events_registry = EventRegistry::new(); + + log::info!("⛓️ ETH EVENT HANDLER INITIALIZED"); + + let public_keys = config.keystore.sr25519_public_keys(AVN_KEY_ID); + + config.client.runtime_api().register_extension( + config + .offchain_transaction_pool_factory + .offchain_transaction_pool(config.client.info().best_hash), + ); + + let author_public_key = match config + .client + .runtime_api() + .query_current_author(config.client.info().best_hash) + .map_err(|e| format!("Failed to query current author: {:?}", e)) + { + Ok(Some(key)) => key, + Ok(None) => { + log::error!("No current author available"); + return // Exit if no author key is available + }, + Err(e) => { + log::error!("{}", e); + return // Exit on error + }, + }; + + let current_public_key = match public_keys + .into_iter() + .find(|public_key| AccountId32::from(public_key.0) == author_public_key) + { + Some(key) => key, + None => { + log::error!("Author not found!"); + return + }, + }; + + loop { + match process_events(&config, ¤t_public_key, &events_registry).await { + Ok(_) => (), + Err(e) => { + log::error!("{}", e); + sleep(Duration::from_secs(SLEEP_TIME)).await; + }, + } + } +} + +async fn process_events( + config: &EthEventHandlerConfig, + current_public_key: &sp_core::sr25519::Public, + events_registry: &EventRegistry, +) -> Result<(), String> +where + Block: BlockT, + ClientT: BlockBackend + + UsageProvider + + HeaderBackend + + sp_api::ProvideRuntimeApi, + ClientT::Api: pallet_eth_bridge_runtime_api::EthEventHandlerApi + + ApiExt + + BlockBuilder, +{ + let (range, partition_id) = config + .client + .runtime_api() + .query_active_block_range(config.client.info().best_hash) + .map_err(|err| format!("Failed to query active block range: {:?}", err))?; + + let contract_address = config + .client + .runtime_api() + .query_bridge_contract(config.client.info().best_hash) + .map_err(|err| format!("Failed to query bridge contract: {:?}", err))?; + let contract_address_web3 = web3::types::H160::from_slice(&contract_address.to_fixed_bytes()); + let contract_addresses = vec![contract_address_web3]; + + let start_block = range.start_block; + let end_block = start_block + range.length; + + let event_signatures = config + .client + .runtime_api() + .query_signatures(config.client.info().best_hash) + .map_err(|err| format!("Failed to query event signatures: {:?}", err))?; + let event_signatures_web3: Vec = event_signatures + .iter() + .map(|h256| Web3H256::from_slice(&h256.to_fixed_bytes())) + .collect(); + + let has_casted_vote = config + .client + .runtime_api() + .query_has_author_casted_event_vote( + config.client.info().best_hash, + current_public_key.0.into(), + ) + .map_err(|err| format!("Failed to check if author has casted event vote: {:?}", err))?; + + if !has_casted_vote { + execute_event_processing( + config, + &event_signatures_web3, + contract_addresses, + start_block, + end_block, + partition_id, + current_public_key, + range, + events_registry, + ) + .await + } else { + Ok(()) } } + +async fn execute_event_processing( + config: &EthEventHandlerConfig, + event_signatures_web3: &[Web3H256], + contract_addresses: Vec, + start_block: u32, + end_block: u32, + partition_id: u16, + current_public_key: &sp_core::sr25519::Public, + range: EthBlockRange, + events_registry: &EventRegistry, +) -> Result<(), String> +where + Block: BlockT, + ClientT: BlockBackend + + UsageProvider + + HeaderBackend + + sp_api::ProvideRuntimeApi, + ClientT::Api: pallet_eth_bridge_runtime_api::EthEventHandlerApi + + ApiExt + + BlockBuilder, +{ + let web3_data_mutex = config.web3_data_mutex.lock().await; + let web3_ref = match web3_data_mutex.web3.as_ref() { + Some(web3) => web3, + None => return Err("Web3 connection not set up".into()), + }; + + let events = identify_events( + web3_ref, + start_block, + end_block, + contract_addresses, + event_signatures_web3.to_vec(), + events_registry, + ) + .await + .map_err(|err| format!("Error retrieving events: {:?}", err))?; + + let ethereum_events_partitions = discovered_eth_events_partition_factory(range, events); + let partition = ethereum_events_partitions + .iter() + .find(|p| p.partition() == partition_id) + .ok_or_else(|| format!("Partition with ID {} not found", partition_id))?; + + let proof = config + .client + .runtime_api() + .create_proof( + config.client.info().best_hash, + current_public_key.clone().into(), + partition.clone(), + ) + .map_err(|err| format!("Failed to create proof: {:?}", err))?; + + let signature = config + .keystore + .sr25519_sign(AVN_KEY_ID, current_public_key, &proof.into_boxed_slice().as_ref()) + .map_err(|err| format!("Failed to sign the proof: {:?}", err))? + .ok_or_else(|| "Signature generation failed".to_string())?; + + let result = config + .client + .runtime_api() + .submit_vote( + config.client.info().best_hash, + current_public_key.clone().into(), + partition.clone(), + signature, + ) + .map_err(|err| format!("Failed to submit vote: {:?}", err))?; + + log::info!("Vote submitted successfully: {:?}", result); + Ok(()) +} diff --git a/node/src/common.rs b/node/src/common.rs index a1eb28f8a..ee1f06f8a 100644 --- a/node/src/common.rs +++ b/node/src/common.rs @@ -1,3 +1,4 @@ +use codec::Codec; use node_primitives::{AccountId, Balance, Block as BlockT, Nonce}; use polkadot_service::BlakeTwo256; use sp_consensus_aura::sr25519::AuthorityId as AuraId; @@ -27,8 +28,10 @@ pub trait AvnRuntimeApiCollection: + sp_session::SessionKeys + cumulus_primitives_core::CollectCollationInfo + sp_consensus_aura::AuraApi + + pallet_eth_bridge_runtime_api::EthEventHandlerApi where >::StateBackend: sp_api::StateBackend, + AccountId: Codec { } @@ -43,7 +46,8 @@ where + sp_offchain::OffchainWorkerApi + sp_session::SessionKeys + cumulus_primitives_core::CollectCollationInfo - + sp_consensus_aura::AuraApi, + + sp_consensus_aura::AuraApi + + pallet_eth_bridge_runtime_api::EthEventHandlerApi, >::StateBackend: sp_api::StateBackend, { } diff --git a/node/src/service.rs b/node/src/service.rs index d94188dcf..359845f82 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -298,19 +298,37 @@ where let avn_config = avn_service::Config:: { keystore: params.keystore_container.local_keystore(), - keystore_path, - avn_port, - eth_node_url, + keystore_path: keystore_path.clone(), + avn_port: avn_port.clone(), + eth_node_url:eth_node_url.clone(), web3_data_mutex: Arc::new(Mutex::new(Web3Data::new())), client: client.clone(), _block: Default::default(), }; + let eth_event_handler_config = avn_service::ethereum_events_handler::EthEventHandlerConfig:: { + keystore: params.keystore_container.local_keystore(), + keystore_path: keystore_path.clone(), + avn_port: avn_port.clone(), + eth_node_url:eth_node_url.clone(), + web3_data_mutex: Arc::new(Mutex::new(Web3Data::new())), + client: client.clone(), + _block: Default::default(), + offchain_transaction_pool_factory: OffchainTransactionPoolFactory::new( + transaction_pool.clone(), + ), + }; + task_manager.spawn_essential_handle().spawn( "avn-service", None, avn_service::start(avn_config), ); + task_manager.spawn_essential_handle().spawn( + "eth-events-handler", + None, + avn_service::ethereum_events_handler::start_eth_event_handler(eth_event_handler_config), + ); let parachain_consensus = build_consensus( client.clone(), block_import, diff --git a/pallets/eth-bridge/runtime-api/src/lib.rs b/pallets/eth-bridge/runtime-api/src/lib.rs index 7c792f184..c10b22120 100644 --- a/pallets/eth-bridge/runtime-api/src/lib.rs +++ b/pallets/eth-bridge/runtime-api/src/lib.rs @@ -11,6 +11,7 @@ sp_api::decl_runtime_apis! { where AccountId: Codec, { + fn query_current_author() -> Option; fn query_active_block_range()-> (EthBlockRange, u16); fn query_has_author_casted_event_vote(account_id: AccountId) -> bool; fn query_signatures() -> Vec; diff --git a/runtime/avn/src/lib.rs b/runtime/avn/src/lib.rs index a2abfe78e..0e088911b 100644 --- a/runtime/avn/src/lib.rs +++ b/runtime/avn/src/lib.rs @@ -942,6 +942,11 @@ impl_runtime_apis! { } impl pallet_eth_bridge_runtime_api::EthEventHandlerApi for Runtime { + fn query_current_author() -> Option{ + Avn::get_validator_for_current_node() + .map(|validator| validator.account_id) + } + fn query_active_block_range()-> (EthBlockRange, u16){ if let Some(active_eth_range) = EthBridge::active_ethereum_range(){ (active_eth_range.range, active_eth_range.partition) @@ -958,7 +963,7 @@ impl_runtime_apis! { } fn query_bridge_contract() -> H160 { - EthBridge::get_bridge_contract() + Avn::get_bridge_contract_address() } fn create_proof(account_id:AccountId, events_partition:EthereumEventsPartition)->Vec{ From b535a3358933cd9cbc20e17fcfc030c10d166e92 Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Wed, 24 Apr 2024 16:23:01 +0100 Subject: [PATCH 08/25] added functionality --- pallets/avn/src/lib.rs | 6 ++++ pallets/eth-bridge/src/lib.rs | 16 +++++++++-- pallets/eth-bridge/src/tests/mock.rs | 30 ++++++++++++++++++-- pallets/eth-bridge/src/tests/tests.rs | 30 ++++++++++++++++++++ pallets/ethereum-events/src/lib.rs | 5 ++++ pallets/nft-manager/src/tests/mock.rs | 3 ++ pallets/parachain-staking/src/tests/mock.rs | 1 + pallets/summary/src/tests/mock.rs | 1 + pallets/token-manager/src/mock.rs | 5 ++++ pallets/validators-manager/src/tests/mock.rs | 5 ++++ runtime/avn/src/lib.rs | 1 + runtime/test/src/lib.rs | 1 + 12 files changed, 99 insertions(+), 5 deletions(-) diff --git a/pallets/avn/src/lib.rs b/pallets/avn/src/lib.rs index 90999e4d2..b61b45db4 100644 --- a/pallets/avn/src/lib.rs +++ b/pallets/avn/src/lib.rs @@ -722,12 +722,18 @@ impl Enforcer for () { pub trait ProcessedEventsChecker { fn check_event(event_id: &EthEventId) -> bool; + + fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult; } impl ProcessedEventsChecker for () { fn check_event(_event_id: &EthEventId) -> bool { return false } + + fn add_event(_event_id: &EthEventId, _processed: bool) -> DispatchResult { + Ok(()) + } } pub trait OnGrowthLiftedHandler { diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index b4ffc2c11..20e91a3c5 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -67,7 +67,7 @@ use frame_system::{ pallet_prelude::{BlockNumberFor, OriginFor}, }; use pallet_avn::{ - self as avn, BridgeInterface, BridgeInterfaceNotification, Error as avn_error, LowerParams, + self as avn, BridgeInterface, BridgeInterfaceNotification, ProcessedEventsChecker, Error as avn_error, LowerParams, }; use pallet_session::historical::IdentificationTuple; @@ -173,6 +173,7 @@ pub mod pallet { IdentificationTuple, CorroborationOffence>, >; + type ProcessedEventsChecker: ProcessedEventsChecker; } #[pallet::event] @@ -283,6 +284,7 @@ pub mod pallet { ExceedsConfirmationLimit, ExceedsCorroborationLimit, ExceedsFunctionNameLimit, + EventAlreadyProcessed, FunctionEncodingError, FunctionNameError, HandlePublishingResultFailed, @@ -731,14 +733,20 @@ pub mod pallet { } } - fn process_ethereum_event(event: &EthEvent) { + fn process_ethereum_event(event: &EthEvent) -> Result<(), DispatchError> { // TODO before processing ensure that the event has not already been processed + // Do the check that is not processed via ProcessedEventsChecker + + ensure!(T::ProcessedEventsChecker::check_event(&event.event_id.clone()), Error::::EventAlreadyProcessed); + match T::BridgeInterfaceNotification::on_event_processed(&event) { Ok(_) => { >::deposit_event(Event::::EventProcessed { accepted: true, eth_event_id: event.event_id.clone(), }); + // Add record of succesful processing via ProcessedEventsChecker + let _ = T::ProcessedEventsChecker::add_event(&event.event_id.clone(), true); }, Err(err) => { log::error!("💔 Processing ethereum event failed: {:?}", err); @@ -746,8 +754,12 @@ pub mod pallet { accepted: false, eth_event_id: event.event_id.clone(), }); + // Add record of unsuccesful processing via ProcessedEventsChecker + let _ = T::ProcessedEventsChecker::add_event(&event.event_id.clone(), false); }, }; + + Ok(()) } #[pallet::validate_unsigned] diff --git a/pallets/eth-bridge/src/tests/mock.rs b/pallets/eth-bridge/src/tests/mock.rs index 1091ac714..5cd2254c9 100644 --- a/pallets/eth-bridge/src/tests/mock.rs +++ b/pallets/eth-bridge/src/tests/mock.rs @@ -6,6 +6,7 @@ use frame_system as system; use pallet_avn::{testing::U64To32BytesConverter, EthereumPublicKeyChecker, OperationType}; use pallet_session as session; use parking_lot::RwLock; +use sp_avn_common::event_types::EthEventId; use sp_core::{ offchain::{ testing::{OffchainState, PoolState, TestOffchainExt, TestTransactionPoolExt}, @@ -14,9 +15,7 @@ use sp_core::{ ConstU32, ConstU64, H256, }; use sp_runtime::{ - testing::{TestSignature, TestXt, UintAuthorityId}, - traits::{BlakeTwo256, ConvertInto, IdentityLookup}, - BuildStorage, Perbill, + testing::{TestSignature, TestXt, UintAuthorityId}, traits::{BlakeTwo256, ConvertInto, IdentityLookup}, BuildStorage, DispatchResult, Perbill }; use sp_staking::offence::OffenceError; use std::{cell::RefCell, convert::From, sync::Arc}; @@ -100,6 +99,7 @@ impl Config for TestRuntime { type AccountToBytesConvert = U64To32BytesConverter; type BridgeInterfaceNotification = TestRuntime; type ReportCorroborationOffence = OffenceHandler; + type ProcessedEventsChecker = Self; } impl system::Config for TestRuntime { @@ -170,6 +170,10 @@ pub fn create_confirming_author(author_id: u64) -> Author { Author:: { key: UintAuthorityId(author_id), account_id: author_id } } +pub fn create_mock_event_partition() -> EthereumEventsPartition { + EthereumEventsPartition::new(EthBlockRange {start_block: 1, length: 2}, 3, true, _) +} + pub fn lower_is_ready_to_be_claimed(lower_id: &u32) -> bool { LOWERSREADYTOCLAIM.with(|lowers| lowers.borrow_mut().iter().any(|l| l == lower_id)) } @@ -198,6 +202,7 @@ pub fn setup_context() -> Context { let already_set_eth_tx_hash = H256::from_slice(&[1u8; 32]); let confirmation_signature = ecdsa::Signature::try_from(&[1; 65][0..65]).unwrap(); let finalised_block_vec = Some(hex::encode(10u32.encode()).into()); + let mock_event_partition = create_mock_event_partition(); UintAuthorityId::set_all_keys(vec![UintAuthorityId(primary_validator_id)]); @@ -403,3 +408,22 @@ impl BridgeInterfaceNotification for TestRuntime { Ok(()) } } + +thread_local! { + static PROCESSED_EVENTS: RefCell> = RefCell::new(vec![]); +} + +pub fn insert_to_mock_processed_events(event_id: &EthEventId, processed: bool) { + PROCESSED_EVENTS.with(|l| l.borrow_mut().push((event_id.clone(), processed))); +} + +impl ProcessedEventsChecker for TestRuntime { + fn check_event(event_id: &EthEventId) -> bool { + PROCESSED_EVENTS.with(|l| l.borrow().iter().any(|(event, _processed)| event == event_id)) + } + + fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { + insert_to_mock_processed_events(event_id, processed); + Ok(()) + } +} diff --git a/pallets/eth-bridge/src/tests/tests.rs b/pallets/eth-bridge/src/tests/tests.rs index 91f1406cd..0b4069871 100644 --- a/pallets/eth-bridge/src/tests/tests.rs +++ b/pallets/eth-bridge/src/tests/tests.rs @@ -754,3 +754,33 @@ fn unsent_transactions_are_replayed() { }))); }); } + + +mod process_events { + use super::*; + + #[test] + fn succesful_event_processing() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context = setup_context(); + EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, c); + }); + } + + #[test] + fn error_event_processing() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + + }); + } + + #[test] + fn event_already_processed() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + + }); + } +} diff --git a/pallets/ethereum-events/src/lib.rs b/pallets/ethereum-events/src/lib.rs index e72ace9be..b85a9bfdf 100644 --- a/pallets/ethereum-events/src/lib.rs +++ b/pallets/ethereum-events/src/lib.rs @@ -1590,6 +1590,11 @@ impl ProcessedEventsChecker for Pallet { fn check_event(event_id: &EthEventId) -> bool { return >::contains_key(event_id) } + + fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { + >::insert(event_id.clone(), processed); + Ok(()) + } } impl InnerCallValidator for Pallet { diff --git a/pallets/nft-manager/src/tests/mock.rs b/pallets/nft-manager/src/tests/mock.rs index 93d6de660..7cc57a72e 100644 --- a/pallets/nft-manager/src/tests/mock.rs +++ b/pallets/nft-manager/src/tests/mock.rs @@ -130,6 +130,9 @@ impl ProcessedEventsChecker for TestRuntime { fn check_event(event_id: &EthEventId) -> bool { return PROCESSED_EVENTS.with(|l| l.borrow_mut().iter().any(|event| event == event_id)) } + fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { + Ok(()) + } } pub struct TestAccount { diff --git a/pallets/parachain-staking/src/tests/mock.rs b/pallets/parachain-staking/src/tests/mock.rs index 30678848e..d27807588 100644 --- a/pallets/parachain-staking/src/tests/mock.rs +++ b/pallets/parachain-staking/src/tests/mock.rs @@ -347,6 +347,7 @@ impl pallet_eth_bridge::Config for Test { type AccountToBytesConvert = AVN; type BridgeInterfaceNotification = Self; type ReportCorroborationOffence = (); + type ProcessedEventsChecker = (); } impl pallet_timestamp::Config for Test { diff --git a/pallets/summary/src/tests/mock.rs b/pallets/summary/src/tests/mock.rs index de857f4e6..73dcac4b6 100644 --- a/pallets/summary/src/tests/mock.rs +++ b/pallets/summary/src/tests/mock.rs @@ -352,6 +352,7 @@ impl pallet_eth_bridge::Config for TestRuntime { type AccountToBytesConvert = AVN; type BridgeInterfaceNotification = Self; type ReportCorroborationOffence = OffenceHandler; + type ProcessedEventsChecker = (); } impl pallet_timestamp::Config for TestRuntime { diff --git a/pallets/token-manager/src/mock.rs b/pallets/token-manager/src/mock.rs index 2b03150cd..3b273282d 100644 --- a/pallets/token-manager/src/mock.rs +++ b/pallets/token-manager/src/mock.rs @@ -303,6 +303,7 @@ impl pallet_eth_bridge::Config for TestRuntime { type AccountToBytesConvert = AVN; type BridgeInterfaceNotification = Self; type ReportCorroborationOffence = (); + type ProcessedEventsChecker = (); } impl pallet_timestamp::Config for TestRuntime { @@ -376,6 +377,10 @@ impl ProcessedEventsChecker for TestRuntime { fn check_event(event_id: &EthEventId) -> bool { return PROCESSED_EVENTS.with(|l| l.borrow_mut().iter().any(|event| event == event_id)) } + + fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { + Ok(()) + } } impl TokenManager { diff --git a/pallets/validators-manager/src/tests/mock.rs b/pallets/validators-manager/src/tests/mock.rs index 5bdedcb40..7dc1611b5 100644 --- a/pallets/validators-manager/src/tests/mock.rs +++ b/pallets/validators-manager/src/tests/mock.rs @@ -250,6 +250,7 @@ impl pallet_eth_bridge::Config for TestRuntime { type AccountToBytesConvert = AVN; type BridgeInterfaceNotification = Self; type ReportCorroborationOffence = (); + type ProcessedEventsChecker = (); } impl BridgeInterfaceNotification for TestRuntime { @@ -350,6 +351,10 @@ impl ProcessedEventsChecker for TestRuntime { }) }) } + + fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { + Ok(()) + } } // TODO: Do we need to test the ECDSA sig verification logic here? If so, replace this with a call diff --git a/runtime/avn/src/lib.rs b/runtime/avn/src/lib.rs index 0e088911b..46539ecf4 100644 --- a/runtime/avn/src/lib.rs +++ b/runtime/avn/src/lib.rs @@ -631,6 +631,7 @@ impl pallet_eth_bridge::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type MinEthBlockConfirmation = MinEthBlockConfirmation; + type ProcessedEventsChecker = EthereumEvents; type AccountToBytesConvert = Avn; type TimeProvider = pallet_timestamp::Pallet; type ReportCorroborationOffence = Offences; diff --git a/runtime/test/src/lib.rs b/runtime/test/src/lib.rs index e3fb84c2c..29f26da26 100644 --- a/runtime/test/src/lib.rs +++ b/runtime/test/src/lib.rs @@ -640,6 +640,7 @@ impl pallet_eth_bridge::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type MinEthBlockConfirmation = MinEthBlockConfirmation; + type ProcessedEventsChecker = EthereumEvents; type AccountToBytesConvert = Avn; type ReportCorroborationOffence = Offences; type TimeProvider = pallet_timestamp::Pallet; From bc8a0fd126300794d05d976420bb6ed7347922af Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Thu, 2 May 2024 11:26:03 +0300 Subject: [PATCH 09/25] tests, mock, more changes --- pallets/avn/src/lib.rs | 11 ++---- pallets/eth-bridge/src/lib.rs | 17 +++++++-- pallets/eth-bridge/src/tests/mock.rs | 39 ++++++++++++++++---- pallets/eth-bridge/src/tests/tests.rs | 27 +++++++++++--- pallets/ethereum-events/src/lib.rs | 7 ++-- pallets/nft-manager/src/tests/mock.rs | 4 +- pallets/token-manager/src/mock.rs | 4 +- pallets/validators-manager/src/tests/mock.rs | 4 +- primitives/avn-common/src/event_discovery.rs | 2 +- 9 files changed, 78 insertions(+), 37 deletions(-) diff --git a/pallets/avn/src/lib.rs b/pallets/avn/src/lib.rs index b61b45db4..f27716eef 100644 --- a/pallets/avn/src/lib.rs +++ b/pallets/avn/src/lib.rs @@ -360,6 +360,7 @@ impl Pallet { // Minimum number required to reach the threshold. pub fn quorum() -> u32 { let total_num_of_validators = Self::validators().len() as u32; + // println!("Num validators: {}", total_num_of_validators); Self::calculate_quorum(total_num_of_validators) } @@ -723,17 +724,13 @@ impl Enforcer for () { pub trait ProcessedEventsChecker { fn check_event(event_id: &EthEventId) -> bool; - fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult; + fn add_processed_event(event_id: &EthEventId, accepted: bool); } impl ProcessedEventsChecker for () { - fn check_event(_event_id: &EthEventId) -> bool { - return false - } + fn check_event(_event_id: &EthEventId) -> bool { false } - fn add_event(_event_id: &EthEventId, _processed: bool) -> DispatchResult { - Ok(()) - } + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } pub trait OnGrowthLiftedHandler { diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 20e91a3c5..d48949bba 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -545,17 +545,25 @@ pub mod pallet { Error::::EventVoteExists ); } + println!("Hello 2"); let mut votes = EthereumEvents::::get(&events_partition); + println!("Votes BEFORE insert: {}", votes.len()); + votes.try_insert(author.account_id).map_err(|_| Error::::EventVotesFull)?; + println!("Votes after insert: {}", votes.len()); + println!("Quorum: {}", AVN::::quorum()); if votes.len() < AVN::::quorum() as usize { + println!("Votes less than"); EthereumEvents::::insert(&events_partition, votes); } else { + println!("Votes more than"); process_ethereum_events_partition::(&active_range, &events_partition); advance_partition::(&active_range, &events_partition); } + println!("End"); Ok(().into()) } } @@ -712,11 +720,13 @@ pub mod pallet { // Remove entry from storage. Ignore votes. let _ = EthereumEvents::::take(partition); for discovered_event in partition.events().iter() { + println!("event: {:?}", discovered_event); match ValidEvents::try_from(&discovered_event.event.event_id.signature) { Some(valid_event) => if active_range.event_types_filter.contains(&valid_event) { - process_ethereum_event::(&discovered_event.event); + let _ = process_ethereum_event::(&discovered_event.event); } else { + println!("does not contain valid event"); log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter); }, None => log::warn!( @@ -736,6 +746,7 @@ pub mod pallet { fn process_ethereum_event(event: &EthEvent) -> Result<(), DispatchError> { // TODO before processing ensure that the event has not already been processed // Do the check that is not processed via ProcessedEventsChecker + println!("Processing events"); ensure!(T::ProcessedEventsChecker::check_event(&event.event_id.clone()), Error::::EventAlreadyProcessed); @@ -746,7 +757,7 @@ pub mod pallet { eth_event_id: event.event_id.clone(), }); // Add record of succesful processing via ProcessedEventsChecker - let _ = T::ProcessedEventsChecker::add_event(&event.event_id.clone(), true); + T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), true); }, Err(err) => { log::error!("💔 Processing ethereum event failed: {:?}", err); @@ -755,7 +766,7 @@ pub mod pallet { eth_event_id: event.event_id.clone(), }); // Add record of unsuccesful processing via ProcessedEventsChecker - let _ = T::ProcessedEventsChecker::add_event(&event.event_id.clone(), false); + T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), false); }, }; diff --git a/pallets/eth-bridge/src/tests/mock.rs b/pallets/eth-bridge/src/tests/mock.rs index 5cd2254c9..b1b0ff579 100644 --- a/pallets/eth-bridge/src/tests/mock.rs +++ b/pallets/eth-bridge/src/tests/mock.rs @@ -6,13 +6,13 @@ use frame_system as system; use pallet_avn::{testing::U64To32BytesConverter, EthereumPublicKeyChecker, OperationType}; use pallet_session as session; use parking_lot::RwLock; -use sp_avn_common::event_types::EthEventId; +use sp_avn_common::event_types::{EthEvent, EthEventId, LiftedData}; use sp_core::{ offchain::{ testing::{OffchainState, PoolState, TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - ConstU32, ConstU64, H256, + ConstU32, ConstU64, H256, U256, }; use sp_runtime::{ testing::{TestSignature, TestXt, UintAuthorityId}, traits::{BlakeTwo256, ConvertInto, IdentityLookup}, BuildStorage, DispatchResult, Perbill @@ -46,10 +46,15 @@ impl ReportOffence for OffenceHandler { pub struct Context { pub eth_tx_hash: H256, pub already_set_eth_tx_hash: H256, + pub mock_event_partition: EthereumEventsPartition, pub test_signature: TestSignature, + pub test_signature_two: TestSignature, + pub test_signature_three: TestSignature, pub confirmation_signature: ecdsa::Signature, pub tx_succeeded: bool, pub author: Author, + pub author_two: Author, + pub author_three: Author, pub confirming_author: Author, pub second_confirming_author: Author, pub third_confirming_author: Author, @@ -170,8 +175,11 @@ pub fn create_confirming_author(author_id: u64) -> Author { Author:: { key: UintAuthorityId(author_id), account_id: author_id } } -pub fn create_mock_event_partition() -> EthereumEventsPartition { - EthereumEventsPartition::new(EthBlockRange {start_block: 1, length: 2}, 3, true, _) +pub fn create_mock_event_partition(events: EthEvent) -> EthereumEventsPartition { + let mut partition: BoundedBTreeSet = BoundedBTreeSet::new(); + partition.try_insert(DiscoveredEvent { event: events.clone(), block: 2 }); + EthereumEventsPartition::new(EthBlockRange {start_block: 1, length: 20}, 3, false, partition) + // events_helpers::discovered_eth_events_partition_factory(EthBlockRange {start_block: 1, length: 20}, discovered_events) } pub fn lower_is_ready_to_be_claimed(lower_id: &u32) -> bool { @@ -184,10 +192,19 @@ pub fn request_failed(id: &u32) -> bool { pub fn setup_context() -> Context { let primary_validator_id = AVN::advance_primary_validator(OperationType::Ethereum).unwrap(); + println!("Primary validator ID: {}", primary_validator_id); let author = Author:: { key: UintAuthorityId(primary_validator_id), account_id: primary_validator_id, }; + let author_two = Author:: { + key: UintAuthorityId(22), + account_id: 22, + }; + let author_three = Author:: { + key: UintAuthorityId(23), + account_id: 23, + }; let mut confirming_validator_id: u64 = 1; if primary_validator_id == confirming_validator_id { confirming_validator_id += 1 @@ -197,21 +214,28 @@ pub fn setup_context() -> Context { let third_confirming_author = create_confirming_author(confirming_validator_id + 2); let fourth_confirming_author = create_confirming_author(confirming_validator_id + 3); let test_signature = generate_signature(author.clone(), b"test context"); + let test_signature_two = generate_signature(author.clone(), b"test context"); + let test_signature_three = generate_signature(author.clone(), b"test context"); let tx_succeeded = false; let eth_tx_hash = H256::from_slice(&[0u8; 32]); let already_set_eth_tx_hash = H256::from_slice(&[1u8; 32]); let confirmation_signature = ecdsa::Signature::try_from(&[1; 65][0..65]).unwrap(); let finalised_block_vec = Some(hex::encode(10u32.encode()).into()); - let mock_event_partition = create_mock_event_partition(); + let mock_event_partition = create_mock_event_partition(EthEvent { event_id: EthEventId { signature: ValidEvents::Lifted.signature(), transaction_hash: eth_tx_hash }, event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { token_contract: H160::zero(), sender_address: H160::zero(), receiver_address: H256::zero(), amount: 1, nonce: U256::zero() }) }); UintAuthorityId::set_all_keys(vec![UintAuthorityId(primary_validator_id)]); Context { eth_tx_hash, + mock_event_partition, already_set_eth_tx_hash, test_signature, + test_signature_two, + test_signature_three, tx_succeeded, author: author.clone(), + author_two: author_two.clone(), + author_three: author_three.clone(), confirming_author: confirming_author.clone(), second_confirming_author: second_confirming_author.clone(), third_confirming_author: third_confirming_author.clone(), @@ -422,8 +446,7 @@ impl ProcessedEventsChecker for TestRuntime { PROCESSED_EVENTS.with(|l| l.borrow().iter().any(|(event, _processed)| event == event_id)) } - fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { - insert_to_mock_processed_events(event_id, processed); - Ok(()) + fn add_processed_event(event_id: &EthEventId, accepted: bool) { + insert_to_mock_processed_events(event_id, accepted); } } diff --git a/pallets/eth-bridge/src/tests/tests.rs b/pallets/eth-bridge/src/tests/tests.rs index 0b4069871..34b38602e 100644 --- a/pallets/eth-bridge/src/tests/tests.rs +++ b/pallets/eth-bridge/src/tests/tests.rs @@ -757,22 +757,38 @@ fn unsent_transactions_are_replayed() { mod process_events { + use sp_avn_common::event_types::EthEventId; + use super::*; #[test] - fn succesful_event_processing() { + fn succesful_event_processing_accepted() { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); ext.execute_with(|| { let context = setup_context(); - EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, c); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone())); + // assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_three, context.mock_event_partition, context.test_signature_three)); + // assert!(System::events().iter().any(|record| record.event == + // mock::RuntimeEvent::EthBridge(Event::::EventProcessed { + // accepted: true, + // eth_event_id: EthEventId {signature: H256::from(0), transaction_hash: 0}, + // }))); }); } #[test] - fn error_event_processing() { + fn succesful_event_processing_not_accepted() { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); ext.execute_with(|| { - + let context = setup_context(); + // avn::Validators::put(val); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, context.mock_event_partition, context.test_signature)); + // assert!(System::events().iter().any(|record| record.event == + // mock::RuntimeEvent::EthBridge(Event::::EventProcessed { + // accepted: false, + // eth_event_id: EthEventId {signature: H256::from(0), transaction_hash: 0}, + // }))); }); } @@ -780,7 +796,8 @@ mod process_events { fn event_already_processed() { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); ext.execute_with(|| { - + let context = setup_context(); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, context.mock_event_partition, context.test_signature)); }); } } diff --git a/pallets/ethereum-events/src/lib.rs b/pallets/ethereum-events/src/lib.rs index b85a9bfdf..e575caf5c 100644 --- a/pallets/ethereum-events/src/lib.rs +++ b/pallets/ethereum-events/src/lib.rs @@ -1588,12 +1588,11 @@ impl Pallet { impl ProcessedEventsChecker for Pallet { fn check_event(event_id: &EthEventId) -> bool { - return >::contains_key(event_id) + return >::contains_key(event_id) || Self::get_pending_event_index(event_id).is_ok() } - fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { - >::insert(event_id.clone(), processed); - Ok(()) + fn add_processed_event(event_id: &EthEventId, accepted: bool) { + >::insert(event_id.clone(), accepted); } } diff --git a/pallets/nft-manager/src/tests/mock.rs b/pallets/nft-manager/src/tests/mock.rs index 7cc57a72e..575bfac21 100644 --- a/pallets/nft-manager/src/tests/mock.rs +++ b/pallets/nft-manager/src/tests/mock.rs @@ -130,9 +130,7 @@ impl ProcessedEventsChecker for TestRuntime { fn check_event(event_id: &EthEventId) -> bool { return PROCESSED_EVENTS.with(|l| l.borrow_mut().iter().any(|event| event == event_id)) } - fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { - Ok(()) - } + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } pub struct TestAccount { diff --git a/pallets/token-manager/src/mock.rs b/pallets/token-manager/src/mock.rs index 3b273282d..4f24cb8a5 100644 --- a/pallets/token-manager/src/mock.rs +++ b/pallets/token-manager/src/mock.rs @@ -378,9 +378,7 @@ impl ProcessedEventsChecker for TestRuntime { return PROCESSED_EVENTS.with(|l| l.borrow_mut().iter().any(|event| event == event_id)) } - fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { - Ok(()) - } + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } impl TokenManager { diff --git a/pallets/validators-manager/src/tests/mock.rs b/pallets/validators-manager/src/tests/mock.rs index 7dc1611b5..02ac8e88e 100644 --- a/pallets/validators-manager/src/tests/mock.rs +++ b/pallets/validators-manager/src/tests/mock.rs @@ -352,9 +352,7 @@ impl ProcessedEventsChecker for TestRuntime { }) } - fn add_event(event_id: &EthEventId, processed: bool) -> DispatchResult { - Ok(()) - } + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } // TODO: Do we need to test the ECDSA sig verification logic here? If so, replace this with a call diff --git a/primitives/avn-common/src/event_discovery.rs b/primitives/avn-common/src/event_discovery.rs index 754275a83..13363701b 100644 --- a/primitives/avn-common/src/event_discovery.rs +++ b/primitives/avn-common/src/event_discovery.rs @@ -98,7 +98,7 @@ impl EthereumEventsPartition { blake2_256(&(&self).encode()).into() } - fn new(range: EthBlockRange, partition: u16, is_last: bool, data: EthereumEventsSet) -> Self { + pub fn new(range: EthBlockRange, partition: u16, is_last: bool, data: EthereumEventsSet) -> Self { EthereumEventsPartition { range, partition, is_last, data } } } From 9af749d9599303fa902e14a91b943a45d53ebe07 Mon Sep 17 00:00:00 2001 From: Thanos Doukoudakis <56822898+thadouk@users.noreply.github.com> Date: Thu, 2 May 2024 15:45:20 +0100 Subject: [PATCH 10/25] Introduces initial range consesnus mechanism (#391) Adds a new endpoint that allows the chain to reach consenus over the initial ethereum range to be used. This allows to simplify the voting logic for active ranges, eliminating the special case of the first range. Jira ticket: - SYS-3930 --- Cargo.lock | 2 +- .../src/ethereum_events_handler.rs | 2 +- node/src/common.rs | 2 +- node/src/service.rs | 27 ++-- pallets/eth-bridge/src/lib.rs | 138 ++++++++++++++---- primitives/avn-common/src/event_discovery.rs | 15 +- primitives/avn-common/src/event_types.rs | 6 +- runtime/avn/src/lib.rs | 5 +- 8 files changed, 143 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 150f24e34..a54dca900 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7197,7 +7197,7 @@ dependencies = [ [[package]] name = "pallet-eth-bridge-runtime-api" -version = "5.2.2" +version = "5.2.3" dependencies = [ "frame-support", "pallet-avn", diff --git a/node/avn-service/src/ethereum_events_handler.rs b/node/avn-service/src/ethereum_events_handler.rs index 62001460d..ab5b48a5a 100644 --- a/node/avn-service/src/ethereum_events_handler.rs +++ b/node/avn-service/src/ethereum_events_handler.rs @@ -141,7 +141,7 @@ pub enum AppError { MissingBlockNumber, MissingEventSignature, ParsingError(Error), - GenericError(String) + GenericError(String), } pub async fn identify_events( diff --git a/node/src/common.rs b/node/src/common.rs index ee1f06f8a..55522a981 100644 --- a/node/src/common.rs +++ b/node/src/common.rs @@ -31,7 +31,7 @@ pub trait AvnRuntimeApiCollection: + pallet_eth_bridge_runtime_api::EthEventHandlerApi where >::StateBackend: sp_api::StateBackend, - AccountId: Codec + AccountId: Codec, { } diff --git a/node/src/service.rs b/node/src/service.rs index 359845f82..8591ca7d6 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -300,24 +300,25 @@ where keystore: params.keystore_container.local_keystore(), keystore_path: keystore_path.clone(), avn_port: avn_port.clone(), - eth_node_url:eth_node_url.clone(), + eth_node_url: eth_node_url.clone(), web3_data_mutex: Arc::new(Mutex::new(Web3Data::new())), client: client.clone(), _block: Default::default(), }; - let eth_event_handler_config = avn_service::ethereum_events_handler::EthEventHandlerConfig:: { - keystore: params.keystore_container.local_keystore(), - keystore_path: keystore_path.clone(), - avn_port: avn_port.clone(), - eth_node_url:eth_node_url.clone(), - web3_data_mutex: Arc::new(Mutex::new(Web3Data::new())), - client: client.clone(), - _block: Default::default(), - offchain_transaction_pool_factory: OffchainTransactionPoolFactory::new( - transaction_pool.clone(), - ), - }; + let eth_event_handler_config = + avn_service::ethereum_events_handler::EthEventHandlerConfig:: { + keystore: params.keystore_container.local_keystore(), + keystore_path: keystore_path.clone(), + avn_port: avn_port.clone(), + eth_node_url: eth_node_url.clone(), + web3_data_mutex: Arc::new(Mutex::new(Web3Data::new())), + client: client.clone(), + _block: Default::default(), + offchain_transaction_pool_factory: OffchainTransactionPoolFactory::new( + transaction_pool.clone(), + ), + }; task_manager.spawn_essential_handle().spawn( "avn-service", diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index b4ffc2c11..5f86b7b44 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -80,7 +80,7 @@ use sp_avn_common::{ }; use sp_core::{ecdsa, ConstU32, H160, H256}; use sp_io::hashing::keccak_256; -use sp_runtime::{scale_info::TypeInfo, traits::Dispatchable}; +use sp_runtime::{scale_info::TypeInfo, traits::Dispatchable, Saturating}; use sp_std::prelude::*; mod call; @@ -251,6 +251,15 @@ pub mod pallet { ValueQuery, >; + #[pallet::storage] + pub type SubmittedBlockNumbers = StorageMap< + _, + Blake2_128Concat, + EthBlockRange, + BoundedBTreeSet, + ValueQuery, + >; + #[pallet::genesis_config] pub struct GenesisConfig { pub _phantom: sp_std::marker::PhantomData, @@ -315,7 +324,7 @@ pub mod pallet { EventVoteExists, NonActiveEthereumRange, VotingEnded, - ValidatorNotFound + ValidatorNotFound, } #[pallet::call] @@ -515,8 +524,9 @@ pub mod pallet { Ok(().into()) } + // TODO update weights #[pallet::call_index(6)] - #[pallet::weight(::WeightInfo::add_eth_tx_hash())] + #[pallet::weight(Weight::zero())] pub fn submit_ethereum_events( origin: OriginFor, author: Author, @@ -525,24 +535,17 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { ensure_none(origin)?; - let active_range = match Self::active_ethereum_range() { - Some(active_range) => { - ensure!( - *events_partition.range() == active_range.range && - events_partition.partition() == active_range.partition, - Error::::NonActiveEthereumRange - ); - active_range - }, - None => Default::default(), - }; - - if active_range.is_initial_range() == false { - ensure!( - author_has_cast_event_vote::(&author.account_id) == false, - Error::::EventVoteExists - ); - } + let active_range = + Self::active_ethereum_range().ok_or_else(|| Error::::NonActiveEthereumRange)?; + ensure!( + *events_partition.range() == active_range.range && + events_partition.partition() == active_range.partition, + Error::::NonActiveEthereumRange + ); + ensure!( + author_has_cast_event_vote::(&author.account_id) == false, + Error::::EventVoteExists + ); let mut votes = EthereumEvents::::get(&events_partition); votes.try_insert(author.account_id).map_err(|_| Error::::EventVotesFull)?; @@ -556,6 +559,62 @@ pub mod pallet { Ok(().into()) } + + // TODO update weights + #[pallet::call_index(7)] + #[pallet::weight(Weight::zero())] + pub fn submit_latest_ethereum_block( + origin: OriginFor, + author: Author, + latest_seen_block: u32, + _signature: ::Signature, + ) -> DispatchResultWithPostInfo { + ensure_none(origin)?; + ensure!(Self::active_ethereum_range().is_none(), Error::::VotingEnded); + ensure!( + author_has_submitted_latest_block::(&author.account_id) == false, + Error::::EventVoteExists + ); + + let nominated_range = + events_helpers::compute_finalised_block_range_for_latest_ethereum_block( + latest_seen_block, + ); + let mut votes = SubmittedBlockNumbers::::get(&nominated_range); + votes.try_insert(author.account_id).map_err(|_| Error::::EventVotesFull)?; + + SubmittedBlockNumbers::::insert(&nominated_range, votes); + + let mut sorted_votes: Vec<(EthBlockRange, usize)> = SubmittedBlockNumbers::::iter() + .map(|(range, votes)| (range, votes.len())) + .collect(); + sorted_votes.sort_by(|(range_a, _votes_a), (range_b, _votes_b)| range_a.cmp(range_b)); + + let total_votes_count = sorted_votes + .iter() + .map(|(_range, votes)| votes) + .fold(0, |acc, x| acc as usize + x); + let mut remaining_votes_threshold = AVN::::supermajority_quorum() as usize; + + if total_votes_count >= remaining_votes_threshold as usize { + let quorum = AVN::::quorum() as usize; + let mut selected_range: EthBlockRange = Default::default(); + for (range, votes_count) in sorted_votes.iter() { + selected_range = range.clone(); + remaining_votes_threshold.saturating_reduce(*votes_count); + if remaining_votes_threshold < quorum { + break + } + } + ActiveEthereumRange::::put(ActiveEthRange { + range: selected_range, + partition: 0, + // TODO retrieve values from runtime. + event_types_filter: Default::default(), + }); + } + Ok(().into()) + } } #[pallet::hooks] @@ -674,6 +733,7 @@ pub mod pallet { Ok(()) } + pub fn author_has_cast_event_vote(author: &T::AccountId) -> bool { for (_partition, votes) in EthereumEvents::::iter() { if votes.contains(&author) { @@ -683,6 +743,15 @@ pub mod pallet { false } + pub fn author_has_submitted_latest_block(author: &T::AccountId) -> bool { + for (_partition, votes) in SubmittedBlockNumbers::::iter() { + if votes.contains(&author) { + return true + } + } + false + } + fn advance_partition( active_range: &ActiveEthRange, approved_partition: &EthereumEventsPartition, @@ -874,7 +943,10 @@ pub mod pallet { } impl Pallet { - pub fn create_eth_events_proof(account_id:T::AccountId, events_partition:EthereumEventsPartition) -> Vec{ + pub fn create_eth_events_proof( + account_id: T::AccountId, + events_partition: EthereumEventsPartition, + ) -> Vec { create_ethereum_events_proof_data::(&account_id, &events_partition) } pub fn signatures() -> Vec { @@ -884,26 +956,26 @@ impl Pallet { active_range.event_types_filter.into_iter().collect::>(); let decoded_hex = - hex::decode("418da8f85cfa851601f87634c6950491b6b8785a6445c8584f5658048d512cae"). - expect("test"); + hex::decode("418da8f85cfa851601f87634c6950491b6b8785a6445c8584f5658048d512cae") + .expect("test"); let mut array = [0; 32]; array.copy_from_slice(&decoded_hex); let decoded_event_sig = H256::from(array); - + vec![decoded_event_sig] }, None => { // TODO use values from pallet constant // vec![] let decoded_hex = - hex::decode("418da8f85cfa851601f87634c6950491b6b8785a6445c8584f5658048d512cae"). - expect("test"); + hex::decode("418da8f85cfa851601f87634c6950491b6b8785a6445c8584f5658048d512cae") + .expect("test"); let mut array = [0; 32]; array.copy_from_slice(&decoded_hex); let decoded_event_sig = H256::from(array); - + vec![decoded_event_sig] }, }; @@ -912,9 +984,13 @@ impl Pallet { pub fn submit_vote( account_id: T::AccountId, events_partition: EthereumEventsPartition, - signature: ::Signature - ) -> Result<(), ()>{ - let validator: Author = AVN::::validators().into_iter().filter(|v| v.account_id == account_id).nth(0).unwrap(); + signature: ::Signature, + ) -> Result<(), ()> { + let validator: Author = AVN::::validators() + .into_iter() + .filter(|v| v.account_id == account_id) + .nth(0) + .unwrap(); submit_ethereum_events::(validator, events_partition, signature) } diff --git a/primitives/avn-common/src/event_discovery.rs b/primitives/avn-common/src/event_discovery.rs index 754275a83..14f55a767 100644 --- a/primitives/avn-common/src/event_discovery.rs +++ b/primitives/avn-common/src/event_discovery.rs @@ -8,7 +8,9 @@ use sp_runtime::traits::Saturating; pub type VotesLimit = ConstU32<100>; pub type EventsBatchLimit = ConstU32<32>; -#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default, TypeInfo, MaxEncodedLen)] +#[derive( + Encode, Decode, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, TypeInfo, MaxEncodedLen, +)] pub struct EthBlockRange { pub start_block: u32, pub length: u32, @@ -141,4 +143,15 @@ pub mod events_helpers { }); partitions } + + // TODO unit test this + pub fn compute_finalised_block_range_for_latest_ethereum_block( + ethereum_block: u32, + ) -> EthBlockRange { + let length = 20u32; + let calculation_block = ethereum_block.saturating_sub(5 * length); + let start_block = calculation_block - calculation_block % length; + + EthBlockRange { start_block, length } + } } diff --git a/primitives/avn-common/src/event_types.rs b/primitives/avn-common/src/event_types.rs index 250dafbb1..b1fbd42f7 100644 --- a/primitives/avn-common/src/event_types.rs +++ b/primitives/avn-common/src/event_types.rs @@ -2,11 +2,7 @@ use crate::bounds::NftExternalRefBound; use codec::{Decode, Encode, MaxEncodedLen}; use hex_literal::hex; use sp_core::{bounded::BoundedVec, H160, H256, H512, U256}; -use sp_runtime::{ - scale_info::TypeInfo, - traits::{Member, Zero}, - DispatchResult, -}; +use sp_runtime::{scale_info::TypeInfo, traits::Member, DispatchResult}; use sp_std::{convert::TryInto, vec::Vec}; // ================================= Events Types ==================================== diff --git a/runtime/avn/src/lib.rs b/runtime/avn/src/lib.rs index 0e088911b..e9d3ef43c 100644 --- a/runtime/avn/src/lib.rs +++ b/runtime/avn/src/lib.rs @@ -68,7 +68,10 @@ use pallet_avn::sr25519::AuthorityId as AvnId; pub use pallet_avn_proxy::{Event as AvnProxyEvent, ProvableProxy}; use pallet_avn_transaction_payment::AvnCurrencyAdapter; -use sp_avn_common::{event_discovery::{EthBlockRange, EthereumEventsPartition}, InnerCallValidator, Proof}; +use sp_avn_common::{ + event_discovery::{EthBlockRange, EthereumEventsPartition}, + InnerCallValidator, Proof, +}; use pallet_parachain_staking; pub type NegativeImbalance = as Currency< From 61cb434ee99bf687c080b7fd3553e0805a604bc2 Mon Sep 17 00:00:00 2001 From: Thanos Doukoudakis <56822898+thadouk@users.noreply.github.com> Date: Thu, 2 May 2024 15:49:40 +0100 Subject: [PATCH 11/25] Exposes initial range voting via the runtime API (#393) Extends the runtime API for initial range consensus mechanism Jira task - SYS-3930 --- pallets/eth-bridge/runtime-api/src/lib.rs | 6 +++ pallets/eth-bridge/src/call.rs | 16 +++++++ pallets/eth-bridge/src/lib.rs | 52 ++++++++++++++++++++--- runtime/avn/src/lib.rs | 9 ++++ 4 files changed, 78 insertions(+), 5 deletions(-) diff --git a/pallets/eth-bridge/runtime-api/src/lib.rs b/pallets/eth-bridge/runtime-api/src/lib.rs index c10b22120..36c738d24 100644 --- a/pallets/eth-bridge/runtime-api/src/lib.rs +++ b/pallets/eth-bridge/runtime-api/src/lib.rs @@ -22,5 +22,11 @@ sp_api::decl_runtime_apis! { events_partition: EthereumEventsPartition, signature: sp_core::sr25519::Signature ) -> Result<(), ()>; + fn submit_latest_ethereum_block( + author: AccountId, + latest_seen_block: u32, + signature: sp_core::sr25519::Signature + ) -> Result<(), ()>; + } } diff --git a/pallets/eth-bridge/src/call.rs b/pallets/eth-bridge/src/call.rs index 50cbf9738..7f612f6b7 100644 --- a/pallets/eth-bridge/src/call.rs +++ b/pallets/eth-bridge/src/call.rs @@ -43,6 +43,15 @@ pub fn submit_ethereum_events( SubmitTransaction::>::submit_unsigned_transaction(call.into()) } +pub fn submit_latest_ethereum_block( + author: Author, + latest_seen_block: u32, + signature: ::Signature, +) -> Result<(), ()> { + let call = Call::::submit_latest_ethereum_block { author, latest_seen_block, signature }; + SubmitTransaction::>::submit_unsigned_transaction(call.into()) +} + fn add_confirmation_proof( tx_id: EthereumId, confirmation: &ecdsa::Signature, @@ -74,3 +83,10 @@ pub fn create_ethereum_events_proof_data( ) -> Vec { (SUBMIT_ETHEREUM_EVENTS_HASH_CONTEXT, &account_id, events_partition).encode() } + +pub fn create_submit_latest_ethereum_block_data( + account_id: &T::AccountId, + latest_seen_block: u32, +) -> Vec { + (SUBMIT_LATEST_ETH_BLOCK_CONTEXT, &account_id, latest_seen_block).encode() +} diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 5f86b7b44..26bbaf9c5 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -91,7 +91,10 @@ pub mod types; mod util; use crate::types::*; -pub use call::{create_ethereum_events_proof_data, submit_ethereum_events}; +pub use call::{ + create_ethereum_events_proof_data, create_submit_latest_ethereum_block_data, + submit_ethereum_events, submit_latest_ethereum_block, +}; mod benchmarking; #[cfg(test)] @@ -132,6 +135,7 @@ const ADD_CONFIRMATION_CONTEXT: &'static [u8] = b"EthBridgeConfirmation"; const ADD_CORROBORATION_CONTEXT: &'static [u8] = b"EthBridgeCorroboration"; const ADD_ETH_TX_HASH_CONTEXT: &'static [u8] = b"EthBridgeEthTxHash"; const SUBMIT_ETHEREUM_EVENTS_HASH_CONTEXT: &'static [u8] = b"EthBridgeDiscoveredEthEventsHash"; +const SUBMIT_LATEST_ETH_BLOCK_CONTEXT: &'static [u8] = b"EthBridgeLatestEthereumBlockHash"; #[frame_support::pallet] pub mod pallet { @@ -878,8 +882,7 @@ pub mod pallet { }, Call::submit_ethereum_events { author, events_partition, signature } => if AVN::::signature_is_valid( - &( - SUBMIT_ETHEREUM_EVENTS_HASH_CONTEXT, + &create_ethereum_events_proof_data::( &author.account_id, events_partition, ), @@ -892,7 +895,23 @@ pub mod pallet { events_partition.range(), events_partition.partition(), )) - .priority(TransactionPriority::max_value() / 2) + .priority(TransactionPriority::max_value()) + .build() + } else { + InvalidTransaction::Custom(4u8).into() + }, + Call::submit_latest_ethereum_block { author, latest_seen_block, signature } => + if AVN::::signature_is_valid( + &create_submit_latest_ethereum_block_data::( + &author.account_id, + *latest_seen_block, + ), + &author, + signature, + ) { + ValidTransaction::with_tag_prefix("EthBridgeAddLatestEthBlock") + .and_provides((call, latest_seen_block)) + .priority(TransactionPriority::max_value()) .build() } else { InvalidTransaction::Custom(4u8).into() @@ -990,11 +1009,34 @@ impl Pallet { .into_iter() .filter(|v| v.account_id == account_id) .nth(0) - .unwrap(); + .ok_or_else(|| { + log::warn!("Events vote sender({:?}) is not a member of authors", &account_id); + () + })?; submit_ethereum_events::(validator, events_partition, signature) } + pub fn submit_latest_ethereum_block_vote( + account_id: T::AccountId, + latest_seen_block: u32, + signature: ::Signature, + ) -> Result<(), ()> { + let validator: Author = AVN::::validators() + .into_iter() + .filter(|v| v.account_id == account_id) + .nth(0) + .ok_or_else(|| { + log::warn!( + "Latest ethereum block vote sender({:?}) is not a member of authors", + &account_id + ); + () + })?; + + submit_latest_ethereum_block::(validator, latest_seen_block, signature) + } + pub fn get_bridge_contract() -> H160 { AVN::::get_bridge_contract_address() } diff --git a/runtime/avn/src/lib.rs b/runtime/avn/src/lib.rs index e9d3ef43c..12e413f7d 100644 --- a/runtime/avn/src/lib.rs +++ b/runtime/avn/src/lib.rs @@ -979,6 +979,15 @@ impl_runtime_apis! { ) -> Result<(),()>{ EthBridge::submit_vote(author, events_partition, signature.into()) } + + fn submit_latest_ethereum_block( + author: AccountId, + latest_seen_block: u32, + signature: sp_core::sr25519::Signature + ) -> Result<(), ()> { + EthBridge::submit_latest_ethereum_block_vote(author, latest_seen_block, signature.into()) + } + } impl cumulus_primitives_core::CollectCollationInfo for Runtime { From 13b32f32d08bf8fa9afcf1e1b03770ef2cfa1dd6 Mon Sep 17 00:00:00 2001 From: Thanos Doukoudakis <56822898+thadouk@users.noreply.github.com> Date: Thu, 2 May 2024 15:53:05 +0100 Subject: [PATCH 12/25] SYS-3930 Adds unit tests for event votes (#372) Adds basic unit tests that check the submit_ethereum_events interface Jira task: - SYS-3930 - SYS-3635 --- pallets/eth-bridge/src/lib.rs | 3 + .../src/tests/event_listener_tests.rs | 416 ++++++++++++++++++ primitives/avn-common/src/event_discovery.rs | 8 + 3 files changed, 427 insertions(+) create mode 100644 pallets/eth-bridge/src/tests/event_listener_tests.rs diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 26bbaf9c5..b1c6c91e7 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -98,6 +98,9 @@ pub use call::{ mod benchmarking; #[cfg(test)] +#[path = "tests/event_listener_tests.rs"] +mod event_listener_tests; +#[cfg(test)] #[path = "tests/lower_proof_tests.rs"] mod lower_proof_tests; #[cfg(test)] diff --git a/pallets/eth-bridge/src/tests/event_listener_tests.rs b/pallets/eth-bridge/src/tests/event_listener_tests.rs new file mode 100644 index 000000000..b0fd160fb --- /dev/null +++ b/pallets/eth-bridge/src/tests/event_listener_tests.rs @@ -0,0 +1,416 @@ +#![cfg(test)] + +use crate::*; +use mock::{EthBridge, ExtBuilder, TestRuntime}; +use sp_avn_common::event_types::{EthEvent, EthEventId, EventData, LiftedData, ValidEvents}; +use sp_core::{H160, U256}; + +use sp_runtime::testing::{TestSignature, UintAuthorityId}; + +use crate::call::create_submit_latest_ethereum_block_data; + +use self::mock::RuntimeOrigin; + +fn event_data_set() -> Vec { + let events = (1..=100) + .map(|i| DiscoveredEvent { + event: EthEvent { + event_id: EthEventId { + signature: ValidEvents::Lifted.signature(), + transaction_hash: H256::from([i; 32]), + }, + event_data: EventData::LogLifted(LiftedData { + token_contract: H160::from([1u8; 20]), + sender_address: H160::from([2u8; 20]), + receiver_address: H256::from([3u8; 32]), + amount: 1000, + nonce: U256::from(1), + }), + }, + block: i as u64, + }) + .collect(); + events +} + +fn alternative_event_data_set() -> Vec { + let events = (1..=20) + .map(|i| DiscoveredEvent { + event: EthEvent { + event_id: EthEventId { + signature: ValidEvents::Lifted.signature(), + transaction_hash: H256::from([i * 2; 32]), + }, + event_data: EventData::LogLifted(LiftedData { + token_contract: H160::from([1u8; 20]), + sender_address: H160::from([2u8; 20]), + receiver_address: H256::from([3u8; 32]), + amount: 1000, + nonce: U256::from(1), + }), + }, + block: i as u64, + }) + .collect(); + events +} + +fn empty_event_data_set() -> Vec { + Default::default() +} + +fn init_active_range() { + ActiveEthereumRange::::put(ActiveEthRange { + range: EthBlockRange { start_block: 1, length: 1000 }, + partition: 0, + event_types_filter: Default::default(), + }); +} + +#[derive(Clone)] +pub struct DiscoveredEthContext { + pub discovered_events: Vec, + pub author: Author, + range: EthBlockRange, +} + +impl Default for DiscoveredEthContext { + fn default() -> Self { + let primary_validator_id = 1; + let author = Author:: { + key: UintAuthorityId(primary_validator_id), + account_id: primary_validator_id, + }; + let events = event_data_set(); + + Self { + author, + discovered_events: events, + range: EthBlockRange { start_block: 1, length: 1000 }, + } + } +} + +impl DiscoveredEthContext { + fn next_range() { + let active_range = ActiveEthereumRange::::get().unwrap_or_default(); + ActiveEthereumRange::::put(ActiveEthRange { + range: active_range.range.next_range(), + partition: 0, + event_types_filter: Default::default(), + }); + } + fn partitions(&self) -> Vec { + events_helpers::discovered_eth_events_partition_factory( + self.range.clone(), + self.discovered_events.clone(), + ) + } +} + +impl DiscoveredEthContext { + fn submit_events_partition(&self, index: usize) -> DispatchResultWithPostInfo { + EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + self.author.clone(), + self.partitions().get(index).expect("index exists").clone(), + self.generate_signature(index), + ) + } + + fn generate_signature(&self, index: usize) -> TestSignature { + self.author + .key + .sign(&create_ethereum_events_proof_data::( + &self.author.account_id, + self.partitions().get(index).expect("Index should exist"), + )) + .expect("Signature is signed") + } +} + +mod submit_discovered_events { + + use super::{DiscoveredEthContext as Context, *}; + use frame_support::{assert_noop, assert_ok}; + use sp_runtime::traits::Saturating; + + fn expected_votes_for_id(id: &H256) -> usize { + let mut votes_count = 0usize; + EthereumEvents::::iter().for_each(|(partition, _votes)| { + if partition.id() == *id { + votes_count.saturating_inc(); + } + }); + votes_count + } + + #[test] + fn adds_vote_correctly() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + init_active_range(); + let context: Context = Default::default(); + + assert_ok!(context.submit_events_partition(0)); + + assert_eq!( + expected_votes_for_id(&context.partitions()[0].id()), + 1, + "Should be a single vote." + ); + }); + } + + #[test] + fn adds_empty_vote_correctly() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + init_active_range(); + let context = + Context { discovered_events: empty_event_data_set(), ..Default::default() }; + + assert_ok!(context.submit_events_partition(0)); + + assert_eq!( + expected_votes_for_id(&context.partitions()[0].id()), + 1, + "Should be a single vote." + ); + }); + } + + #[test] + fn finalises_vote() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + init_active_range(); + let contexts = (1..6 as u64) + .map(|id| Context { + author: Author:: { key: UintAuthorityId(id), account_id: id }, + ..Default::default() + }) + .take(AVN::::quorum() as usize) + .collect::>(); + + let first_partition_index = 0usize; + + // Cast all votes + for context in contexts.iter() { + assert_ok!(context.submit_events_partition(first_partition_index)); + } + let new_active_range = EthBridge::active_ethereum_range().expect("Should be ok"); + let second_partition_index = first_partition_index.saturating_add(1); + assert_eq!( + contexts[0].partitions()[second_partition_index].range(), + &new_active_range.range, + ); + assert_eq!( + contexts[0].partitions()[second_partition_index].partition(), + new_active_range.partition, + ); + }); + } + + mod fails_when { + use super::*; + + #[test] + fn another_range_is_active() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + init_active_range(); + let context: Context = Default::default(); + Context::next_range(); + + assert_noop!( + context.submit_events_partition(0), + Error::::NonActiveEthereumRange, + ); + }); + } + + #[test] + fn author_has_voted_the_partition() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + init_active_range(); + let context: Context = Default::default(); + + assert_ok!(context.submit_events_partition(0)); + + // Resubmit vote + assert_noop!( + context.submit_events_partition(0), + Error::::EventVoteExists, + ); + }); + } + + #[test] + fn author_has_voted_another_partition() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context: Context = Default::default(); + init_active_range(); + let other_context = Context { + discovered_events: alternative_event_data_set(), + ..Default::default() + }; + + assert_ok!(other_context.submit_events_partition(0)); + + // try to submit another vote + assert_noop!( + context.submit_events_partition(0), + Error::::EventVoteExists, + ); + }); + } + } +} + +#[derive(Clone)] +pub struct LatestEthBlockContext { + pub discovered_block: u32, + pub author: Author, +} + +impl Default for LatestEthBlockContext { + fn default() -> Self { + let primary_validator_id = 1; + let author = Author:: { + key: UintAuthorityId(primary_validator_id), + account_id: primary_validator_id, + }; + + Self { author, discovered_block: 100 } + } +} + +impl LatestEthBlockContext { + fn submit_latest_block(&self) -> DispatchResultWithPostInfo { + EthBridge::submit_latest_ethereum_block( + RuntimeOrigin::none(), + self.author.clone(), + self.discovered_block, + self.generate_signature(), + ) + } + + fn generate_signature(&self) -> TestSignature { + self.author + .key + .sign(&create_submit_latest_ethereum_block_data::( + &self.author.account_id, + self.discovered_block, + )) + .expect("Signature is signed") + } + + fn range(&self) -> EthBlockRange { + events_helpers::compute_finalised_block_range_for_latest_ethereum_block( + self.discovered_block, + ) + } +} + +mod initial_range_consensus { + + use super::{LatestEthBlockContext as Context, *}; + use frame_support::{assert_noop, assert_ok}; + use sp_runtime::traits::Saturating; + + fn get_votes_for_range(range: &EthBlockRange) -> usize { + let mut votes_count = 0usize; + SubmittedBlockNumbers::::iter().for_each(|(voted_range, _votes)| { + if voted_range == range.clone() { + votes_count.saturating_inc(); + } + }); + votes_count + } + + #[test] + fn adds_latest_block_successfully() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context: Context = Default::default(); + + assert_ok!(context.submit_latest_block()); + + assert_eq!(get_votes_for_range(&context.range()), 1, "Should be a single vote."); + }); + } + + #[test] + fn finalises_initial_range() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let contexts = (1..5 as u64) + .map(|id| Context { + author: Author:: { key: UintAuthorityId(id), account_id: id }, + discovered_block: id as u32 * 100, + }) + .take(AVN::::supermajority_quorum() as usize) + .collect::>(); + + // Cast all votes + for context in contexts.iter() { + assert_ok!(context.submit_latest_block()); + } + + let active_range = EthBridge::active_ethereum_range().expect("Should be set"); + // Given that the submitted blocks are [100,200,300,400] the expected consensus + assert_eq!( + active_range.range, + events_helpers::compute_finalised_block_range_for_latest_ethereum_block(300) + ); + }); + } + + mod fails_when { + use super::*; + + #[test] + fn a_range_is_active() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + init_active_range(); + let context: Context = Default::default(); + + assert_noop!(context.submit_latest_block(), Error::::VotingEnded,); + }); + } + + #[test] + fn author_has_voted_the_partition() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context: Context = Default::default(); + + assert_ok!(context.submit_latest_block()); + + // Attempt to resubmit vote + assert_noop!(context.submit_latest_block(), Error::::EventVoteExists,); + }); + } + + #[test] + fn author_has_voted_another_block() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context: Context = Default::default(); + let other_context = Context { + discovered_block: context.discovered_block.saturating_add(1), + ..Default::default() + }; + + assert_ok!(other_context.submit_latest_block()); + + // try to submit another vote + assert_noop!(context.submit_latest_block(), Error::::EventVoteExists,); + }); + } + } +} diff --git a/primitives/avn-common/src/event_discovery.rs b/primitives/avn-common/src/event_discovery.rs index 14f55a767..0731985a4 100644 --- a/primitives/avn-common/src/event_discovery.rs +++ b/primitives/avn-common/src/event_discovery.rs @@ -141,6 +141,14 @@ pub mod events_helpers { )); Ok(()) }); + if partitions.is_empty() { + partitions.push(EthereumEventsPartition::new( + range.clone(), + 0, + true, + Default::default(), + )) + } partitions } From 0bf2b268feb18fa1d2a95821122c84398c0deb51 Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Thu, 2 May 2024 19:43:01 +0300 Subject: [PATCH 13/25] tidy-up, add comments --- pallets/avn/src/lib.rs | 1 - pallets/eth-bridge/src/tests/tests.rs | 15 ++++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pallets/avn/src/lib.rs b/pallets/avn/src/lib.rs index f27716eef..07ad82ec5 100644 --- a/pallets/avn/src/lib.rs +++ b/pallets/avn/src/lib.rs @@ -360,7 +360,6 @@ impl Pallet { // Minimum number required to reach the threshold. pub fn quorum() -> u32 { let total_num_of_validators = Self::validators().len() as u32; - // println!("Num validators: {}", total_num_of_validators); Self::calculate_quorum(total_num_of_validators) } diff --git a/pallets/eth-bridge/src/tests/tests.rs b/pallets/eth-bridge/src/tests/tests.rs index 34b38602e..265134bee 100644 --- a/pallets/eth-bridge/src/tests/tests.rs +++ b/pallets/eth-bridge/src/tests/tests.rs @@ -761,6 +761,8 @@ mod process_events { use super::*; + + // succesfully process the specified ethereum_event #[test] fn succesful_event_processing_accepted() { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); @@ -768,30 +770,33 @@ mod process_events { let context = setup_context(); assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone())); - // assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_three, context.mock_event_partition, context.test_signature_three)); // assert!(System::events().iter().any(|record| record.event == // mock::RuntimeEvent::EthBridge(Event::::EventProcessed { // accepted: true, - // eth_event_id: EthEventId {signature: H256::from(0), transaction_hash: 0}, + // eth_event_id: some_value, // }))); }); } + + // This test should fail processing the ethereum_event and emit the specified event #[test] fn succesful_event_processing_not_accepted() { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); ext.execute_with(|| { let context = setup_context(); - // avn::Validators::put(val); - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, context.mock_event_partition, context.test_signature)); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); + assert!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone()).is_err()); // assert!(System::events().iter().any(|record| record.event == // mock::RuntimeEvent::EthBridge(Event::::EventProcessed { // accepted: false, - // eth_event_id: EthEventId {signature: H256::from(0), transaction_hash: 0}, + // eth_event_id: some_value // }))); }); } + + // This test should fail on the check T::ProcessedEventsChecker::check_event(&event.event_id.clone()), if the event is already in the system #[test] fn event_already_processed() { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); From 5b1abf147a3861d2de95c3688908ad33bc085dbc Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Thu, 2 May 2024 20:03:48 +0300 Subject: [PATCH 14/25] fix merge issue, add more comments --- pallets/eth-bridge/src/lib.rs | 8 +------- pallets/eth-bridge/src/tests/mock.rs | 4 +--- pallets/eth-bridge/src/tests/tests.rs | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 9b3ff6a4c..181bac25b 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -557,17 +557,12 @@ pub mod pallet { ); let mut votes = EthereumEvents::::get(&events_partition); - println!("Votes BEFORE insert: {}", votes.len()); votes.try_insert(author.account_id).map_err(|_| Error::::EventVotesFull)?; - println!("Votes after insert: {}", votes.len()); - println!("Quorum: {}", AVN::::quorum()); if votes.len() < AVN::::quorum() as usize { - println!("Votes less than"); EthereumEvents::::insert(&events_partition, votes); } else { - println!("Votes more than"); process_ethereum_events_partition::(&active_range, &events_partition); advance_partition::(&active_range, &events_partition); } @@ -794,13 +789,12 @@ pub mod pallet { // Remove entry from storage. Ignore votes. let _ = EthereumEvents::::take(partition); for discovered_event in partition.events().iter() { - println!("event: {:?}", discovered_event); match ValidEvents::try_from(&discovered_event.event.event_id.signature) { Some(valid_event) => if active_range.event_types_filter.contains(&valid_event) { let _ = process_ethereum_event::(&discovered_event.event); } else { - println!("does not contain valid event"); + println!("DOES NOT contain valid ethereum event, process_ethereum_event not reached\n"); log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter); }, None => log::warn!( diff --git a/pallets/eth-bridge/src/tests/mock.rs b/pallets/eth-bridge/src/tests/mock.rs index b1b0ff579..14ecbc96e 100644 --- a/pallets/eth-bridge/src/tests/mock.rs +++ b/pallets/eth-bridge/src/tests/mock.rs @@ -178,8 +178,7 @@ pub fn create_confirming_author(author_id: u64) -> Author { pub fn create_mock_event_partition(events: EthEvent) -> EthereumEventsPartition { let mut partition: BoundedBTreeSet = BoundedBTreeSet::new(); partition.try_insert(DiscoveredEvent { event: events.clone(), block: 2 }); - EthereumEventsPartition::new(EthBlockRange {start_block: 1, length: 20}, 3, false, partition) - // events_helpers::discovered_eth_events_partition_factory(EthBlockRange {start_block: 1, length: 20}, discovered_events) + EthereumEventsPartition::new(EthBlockRange {start_block: 1, length: 1000}, 0, false, partition) } pub fn lower_is_ready_to_be_claimed(lower_id: &u32) -> bool { @@ -192,7 +191,6 @@ pub fn request_failed(id: &u32) -> bool { pub fn setup_context() -> Context { let primary_validator_id = AVN::advance_primary_validator(OperationType::Ethereum).unwrap(); - println!("Primary validator ID: {}", primary_validator_id); let author = Author:: { key: UintAuthorityId(primary_validator_id), account_id: primary_validator_id, diff --git a/pallets/eth-bridge/src/tests/tests.rs b/pallets/eth-bridge/src/tests/tests.rs index 265134bee..0c0ac3135 100644 --- a/pallets/eth-bridge/src/tests/tests.rs +++ b/pallets/eth-bridge/src/tests/tests.rs @@ -29,6 +29,15 @@ fn corroborate_good_transactions( ) } +// Added this function as in event_listener_tests to initialize the active event range +fn init_active_range() { + ActiveEthereumRange::::put(ActiveEthRange { + range: EthBlockRange { start_block: 1, length: 1000 }, + partition: 0, + event_types_filter: Default::default(), + }); +} + fn corroborate_bad_transactions( tx_id: EthereumId, author: &Validator, @@ -768,6 +777,9 @@ mod process_events { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); ext.execute_with(|| { let context = setup_context(); + init_active_range(); + + // Two calls needed as upon the first there are not enough votes to pass the condition in lin.rs line 563, to reach the call of process_ethereum_events_partition() assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone())); // assert!(System::events().iter().any(|record| record.event == @@ -785,6 +797,7 @@ mod process_events { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); ext.execute_with(|| { let context = setup_context(); + init_active_range(); assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); assert!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone()).is_err()); // assert!(System::events().iter().any(|record| record.event == @@ -802,6 +815,7 @@ mod process_events { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); ext.execute_with(|| { let context = setup_context(); + init_active_range(); assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, context.mock_event_partition, context.test_signature)); }); } From c483d18c2bfb4189d699c123ec510a4cd3ec7140 Mon Sep 17 00:00:00 2001 From: Thanos Doukoudakis <56822898+thadouk@users.noreply.github.com> Date: Fri, 3 May 2024 14:46:55 +0100 Subject: [PATCH 15/25] SYS-3930 Cleanup and finalises interface with runtime (#396) - General cleanup - Rename of storage items - Removal of mocks - Wire up with runtime, implementing different events filters for each runtime. --- pallets/eth-bridge/src/lib.rs | 60 +++++++------------ .../src/tests/event_listener_tests.rs | 6 +- pallets/eth-bridge/src/tests/mock.rs | 1 + pallets/eth-bridge/src/types.rs | 4 +- pallets/summary/src/tests/mock.rs | 1 + primitives/avn-common/src/event_discovery.rs | 15 +++++ runtime/avn/src/lib.rs | 1 + runtime/test/src/lib.rs | 25 +++++++- 8 files changed, 69 insertions(+), 44 deletions(-) diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index b1c6c91e7..00849d3b7 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -75,6 +75,7 @@ use sp_staking::offence::ReportOffence; use sp_application_crypto::RuntimeAppPublic; use sp_avn_common::{ + bounds::MaximumValidatorsBound, event_discovery::*, event_types::{ValidEvents, Validator}, }; @@ -180,6 +181,7 @@ pub mod pallet { IdentificationTuple, CorroborationOffence>, >; + type EthereumEventsFilter: EthereumEventsFilterTrait; } #[pallet::event] @@ -259,7 +261,7 @@ pub mod pallet { >; #[pallet::storage] - pub type SubmittedBlockNumbers = StorageMap< + pub type SubmittedBlockRanges = StorageMap< _, Blake2_128Concat, EthBlockRange, @@ -587,12 +589,12 @@ pub mod pallet { events_helpers::compute_finalised_block_range_for_latest_ethereum_block( latest_seen_block, ); - let mut votes = SubmittedBlockNumbers::::get(&nominated_range); + let mut votes = SubmittedBlockRanges::::get(&nominated_range); votes.try_insert(author.account_id).map_err(|_| Error::::EventVotesFull)?; - SubmittedBlockNumbers::::insert(&nominated_range, votes); + SubmittedBlockRanges::::insert(&nominated_range, votes); - let mut sorted_votes: Vec<(EthBlockRange, usize)> = SubmittedBlockNumbers::::iter() + let mut sorted_votes: Vec<(EthBlockRange, usize)> = SubmittedBlockRanges::::iter() .map(|(range, votes)| (range, votes.len())) .collect(); sorted_votes.sort_by(|(range_a, _votes_a), (range_b, _votes_b)| range_a.cmp(range_b)); @@ -616,9 +618,12 @@ pub mod pallet { ActiveEthereumRange::::put(ActiveEthRange { range: selected_range, partition: 0, - // TODO retrieve values from runtime. - event_types_filter: Default::default(), + event_types_filter: T::EthereumEventsFilter::get_filter(), }); + let _ = SubmittedBlockRanges::::clear( + ::get(), + None, + ); } Ok(().into()) } @@ -751,7 +756,7 @@ pub mod pallet { } pub fn author_has_submitted_latest_block(author: &T::AccountId) -> bool { - for (_partition, votes) in SubmittedBlockNumbers::::iter() { + for (_partition, votes) in SubmittedBlockRanges::::iter() { if votes.contains(&author) { return true } @@ -767,8 +772,7 @@ pub mod pallet { ActiveEthRange { range: active_range.range.next_range(), partition: 0, - // TODO retrieve values from runtime. - event_types_filter: Default::default(), + event_types_filter: T::EthereumEventsFilter::get_filter(), } } else { ActiveEthRange { @@ -972,36 +976,14 @@ impl Pallet { create_ethereum_events_proof_data::(&account_id, &events_partition) } pub fn signatures() -> Vec { - let signatures: Vec = match Self::active_ethereum_range() { - Some(active_range) => { - let _events = - active_range.event_types_filter.into_iter().collect::>(); - - let decoded_hex = - hex::decode("418da8f85cfa851601f87634c6950491b6b8785a6445c8584f5658048d512cae") - .expect("test"); - - let mut array = [0; 32]; - array.copy_from_slice(&decoded_hex); - let decoded_event_sig = H256::from(array); - - vec![decoded_event_sig] - }, - None => { - // TODO use values from pallet constant - // vec![] - let decoded_hex = - hex::decode("418da8f85cfa851601f87634c6950491b6b8785a6445c8584f5658048d512cae") - .expect("test"); - - let mut array = [0; 32]; - array.copy_from_slice(&decoded_hex); - let decoded_event_sig = H256::from(array); - - vec![decoded_event_sig] - }, - }; - signatures + match Self::active_ethereum_range() { + Some(active_range) => active_range + .event_types_filter + .into_iter() + .map(|valid_event| valid_event.signature()) + .collect::>(), + None => Default::default(), + } } pub fn submit_vote( account_id: T::AccountId, diff --git a/pallets/eth-bridge/src/tests/event_listener_tests.rs b/pallets/eth-bridge/src/tests/event_listener_tests.rs index b0fd160fb..19d924431 100644 --- a/pallets/eth-bridge/src/tests/event_listener_tests.rs +++ b/pallets/eth-bridge/src/tests/event_listener_tests.rs @@ -323,7 +323,7 @@ mod initial_range_consensus { fn get_votes_for_range(range: &EthBlockRange) -> usize { let mut votes_count = 0usize; - SubmittedBlockNumbers::::iter().for_each(|(voted_range, _votes)| { + SubmittedBlockRanges::::iter().for_each(|(voted_range, _votes)| { if voted_range == range.clone() { votes_count.saturating_inc(); } @@ -366,6 +366,10 @@ mod initial_range_consensus { active_range.range, events_helpers::compute_finalised_block_range_for_latest_ethereum_block(300) ); + // Ensure that cleanup has occured + for context in contexts.iter() { + assert_eq!(get_votes_for_range(&context.range()), 0, "Should be no votes."); + } }); } diff --git a/pallets/eth-bridge/src/tests/mock.rs b/pallets/eth-bridge/src/tests/mock.rs index 1091ac714..b37a25941 100644 --- a/pallets/eth-bridge/src/tests/mock.rs +++ b/pallets/eth-bridge/src/tests/mock.rs @@ -100,6 +100,7 @@ impl Config for TestRuntime { type AccountToBytesConvert = U64To32BytesConverter; type BridgeInterfaceNotification = TestRuntime; type ReportCorroborationOffence = OffenceHandler; + type EthereumEventsFilter = (); } impl system::Config for TestRuntime { diff --git a/pallets/eth-bridge/src/types.rs b/pallets/eth-bridge/src/types.rs index dcad519ab..b8f6f6125 100644 --- a/pallets/eth-bridge/src/types.rs +++ b/pallets/eth-bridge/src/types.rs @@ -1,7 +1,5 @@ use crate::*; -use sp_avn_common::event_types::ValidEvents; -type EventsTypesLimit = ConstU32<20>; -type EthBridgeEventsFilter = BoundedBTreeSet; +use sp_avn_common::event_discovery::EthBridgeEventsFilter; // The different types of request this pallet can handle. #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] diff --git a/pallets/summary/src/tests/mock.rs b/pallets/summary/src/tests/mock.rs index de857f4e6..3060840ba 100644 --- a/pallets/summary/src/tests/mock.rs +++ b/pallets/summary/src/tests/mock.rs @@ -352,6 +352,7 @@ impl pallet_eth_bridge::Config for TestRuntime { type AccountToBytesConvert = AVN; type BridgeInterfaceNotification = Self; type ReportCorroborationOffence = OffenceHandler; + type EthereumEventsFilter = (); } impl pallet_timestamp::Config for TestRuntime { diff --git a/primitives/avn-common/src/event_discovery.rs b/primitives/avn-common/src/event_discovery.rs index 0731985a4..4f3292124 100644 --- a/primitives/avn-common/src/event_discovery.rs +++ b/primitives/avn-common/src/event_discovery.rs @@ -5,6 +5,8 @@ use event_types::EthEvent; use sp_core::{bounded::BoundedBTreeSet, ConstU32}; use sp_runtime::traits::Saturating; +use self::event_types::ValidEvents; + pub type VotesLimit = ConstU32<100>; pub type EventsBatchLimit = ConstU32<32>; @@ -105,6 +107,19 @@ impl EthereumEventsPartition { } } +pub type EventsTypesLimit = ConstU32<20>; +pub type EthBridgeEventsFilter = BoundedBTreeSet; + +pub trait EthereumEventsFilterTrait { + fn get_filter() -> EthBridgeEventsFilter; +} + +impl EthereumEventsFilterTrait for () { + fn get_filter() -> EthBridgeEventsFilter { + Default::default() + } +} + pub mod events_helpers { use super::*; pub extern crate alloc; diff --git a/runtime/avn/src/lib.rs b/runtime/avn/src/lib.rs index 12e413f7d..f021035e7 100644 --- a/runtime/avn/src/lib.rs +++ b/runtime/avn/src/lib.rs @@ -639,6 +639,7 @@ impl pallet_eth_bridge::Config for Runtime { type ReportCorroborationOffence = Offences; type WeightInfo = pallet_eth_bridge::default_weights::SubstrateWeight; type BridgeInterfaceNotification = (Summary, TokenManager, ParachainStaking); + type EthereumEventsFilter = (); } // Other pallets diff --git a/runtime/test/src/lib.rs b/runtime/test/src/lib.rs index e3fb84c2c..6f3636267 100644 --- a/runtime/test/src/lib.rs +++ b/runtime/test/src/lib.rs @@ -24,6 +24,8 @@ use sp_runtime::{ transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; +pub extern crate alloc; +use alloc::collections::BTreeSet; use sp_std::prelude::*; #[cfg(feature = "std")] @@ -67,7 +69,11 @@ use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use pallet_avn::sr25519::AuthorityId as AvnId; use pallet_avn_proxy::ProvableProxy; -use sp_avn_common::{InnerCallValidator, Proof}; +use sp_avn_common::{ + event_discovery::{EthBridgeEventsFilter, EthereumEventsFilterTrait}, + event_types::ValidEvents, + InnerCallValidator, Proof, +}; use pallet_parachain_staking; pub type NegativeImbalance = as Currency< @@ -108,6 +114,22 @@ where } } +pub struct EthBridgeTestRuntimeEventsFilter; +impl EthereumEventsFilterTrait for EthBridgeTestRuntimeEventsFilter { + fn get_filter() -> EthBridgeEventsFilter { + let allowed_events: BTreeSet = vec![ + ValidEvents::AddedValidator, + ValidEvents::Lifted, + ValidEvents::AvtGrowthLifted, + ValidEvents::AvtLowerClaimed, + ] + .into_iter() + .collect(); + + EthBridgeEventsFilter::try_from(allowed_events).unwrap_or_default() + } +} + pub use node_primitives::{AccountId, Signature}; use node_primitives::{Balance, BlockNumber, Hash, Nonce}; @@ -645,6 +667,7 @@ impl pallet_eth_bridge::Config for Runtime { type TimeProvider = pallet_timestamp::Pallet; type WeightInfo = pallet_eth_bridge::default_weights::SubstrateWeight; type BridgeInterfaceNotification = (Summary, TokenManager, ParachainStaking); + type EthereumEventsFilter = EthBridgeTestRuntimeEventsFilter; } // Other pallets From 41933c709dbebbb65ae1537c9d279e20a6a0d112 Mon Sep 17 00:00:00 2001 From: Thanos <56822898+thadouk@users.noreply.github.com> Date: Fri, 3 May 2024 15:47:29 +0100 Subject: [PATCH 16/25] Squashed commit of the following: commit 842e507693b00fa8fc308ff5ed9399234df93928 Merge: 5b1abf1 c483d18 Author: Thanos <56822898+thadouk@users.noreply.github.com> Date: Fri May 3 14:57:01 2024 +0100 Merge remote-tracking branch 'origin/feat/SYS-3560-add-event-listener' into SYS-3965-migration-processed-events-mechanism-first commit 5b1abf147a3861d2de95c3688908ad33bc085dbc Author: Michael Brozhko Date: Thu May 2 20:03:48 2024 +0300 fix merge issue, add more comments commit 0bf2b268feb18fa1d2a95821122c84398c0deb51 Author: Michael Brozhko Date: Thu May 2 19:43:01 2024 +0300 tidy-up, add comments commit 787caa3fc298fd24fbeb2acbe52cd62c40b0afe6 Merge: bc8a0fd 13b32f3 Author: Michael Brozhko Date: Thu May 2 19:28:22 2024 +0300 Merge branch 'feat/SYS-3560-add-event-listener' into SYS-3965-migration-processed-events-mechanism-first commit bc8a0fd126300794d05d976420bb6ed7347922af Author: Michael Brozhko Date: Thu May 2 11:26:03 2024 +0300 tests, mock, more changes commit b535a3358933cd9cbc20e17fcfc030c10d166e92 Author: Michael Brozhko Date: Wed Apr 24 16:23:01 2024 +0100 added functionality --- pallets/avn/src/lib.rs | 8 ++- pallets/eth-bridge/src/lib.rs | 21 ++++++- pallets/eth-bridge/src/tests/mock.rs | 53 ++++++++++++++-- pallets/eth-bridge/src/tests/tests.rs | 65 ++++++++++++++++++++ pallets/ethereum-events/src/lib.rs | 6 +- pallets/nft-manager/src/tests/mock.rs | 1 + pallets/parachain-staking/src/tests/mock.rs | 1 + pallets/summary/src/tests/mock.rs | 1 + pallets/token-manager/src/mock.rs | 3 + pallets/validators-manager/src/tests/mock.rs | 3 + primitives/avn-common/src/event_discovery.rs | 2 +- runtime/avn/src/lib.rs | 1 + runtime/test/src/lib.rs | 1 + 13 files changed, 154 insertions(+), 12 deletions(-) diff --git a/pallets/avn/src/lib.rs b/pallets/avn/src/lib.rs index f22b8fa0c..2ff7d8598 100644 --- a/pallets/avn/src/lib.rs +++ b/pallets/avn/src/lib.rs @@ -704,12 +704,14 @@ impl Enforcer for () { pub trait ProcessedEventsChecker { fn check_event(event_id: &EthEventId) -> bool; + + fn add_processed_event(event_id: &EthEventId, accepted: bool); } impl ProcessedEventsChecker for () { - fn check_event(_event_id: &EthEventId) -> bool { - return false - } + fn check_event(_event_id: &EthEventId) -> bool { false } + + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } pub trait OnGrowthLiftedHandler { diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index a02898d74..5c927444f 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -67,7 +67,7 @@ use frame_system::{ pallet_prelude::{BlockNumberFor, OriginFor}, }; use pallet_avn::{ - self as avn, BridgeInterface, BridgeInterfaceNotification, Error as avn_error, LowerParams, + self as avn, BridgeInterface, BridgeInterfaceNotification, ProcessedEventsChecker, Error as avn_error, LowerParams, MAX_VALIDATOR_ACCOUNTS, }; @@ -183,6 +183,7 @@ pub mod pallet { IdentificationTuple, CorroborationOffence>, >; + type ProcessedEventsChecker: ProcessedEventsChecker; type EthereumEventsFilter: EthereumEventsFilterTrait; } @@ -303,6 +304,7 @@ pub mod pallet { ExceedsConfirmationLimit, ExceedsCorroborationLimit, ExceedsFunctionNameLimit, + EventAlreadyProcessed, FunctionEncodingError, FunctionNameError, HandlePublishingResultFailed, @@ -565,6 +567,7 @@ pub mod pallet { ); let mut votes = EthereumEvents::::get(&events_partition); + votes.try_insert(author.account_id).map_err(|_| Error::::EventVotesFull)?; if votes.len() < AVN::::quorum() as usize { @@ -801,8 +804,9 @@ pub mod pallet { match ValidEvents::try_from(&discovered_event.event.event_id.signature) { Some(valid_event) => if active_range.event_types_filter.contains(&valid_event) { - process_ethereum_event::(&discovered_event.event); + let _ = process_ethereum_event::(&discovered_event.event); } else { + println!("DOES NOT contain valid ethereum event, process_ethereum_event not reached\n"); log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter); }, None => log::warn!( @@ -819,14 +823,21 @@ pub mod pallet { } } - fn process_ethereum_event(event: &EthEvent) { + fn process_ethereum_event(event: &EthEvent) -> Result<(), DispatchError> { // TODO before processing ensure that the event has not already been processed + // Do the check that is not processed via ProcessedEventsChecker + println!("Processing events"); + + ensure!(T::ProcessedEventsChecker::check_event(&event.event_id.clone()), Error::::EventAlreadyProcessed); + match T::BridgeInterfaceNotification::on_event_processed(&event) { Ok(_) => { >::deposit_event(Event::::EventProcessed { accepted: true, eth_event_id: event.event_id.clone(), }); + // Add record of succesful processing via ProcessedEventsChecker + T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), true); }, Err(err) => { log::error!("💔 Processing ethereum event failed: {:?}", err); @@ -834,8 +845,12 @@ pub mod pallet { accepted: false, eth_event_id: event.event_id.clone(), }); + // Add record of unsuccesful processing via ProcessedEventsChecker + T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), false); }, }; + + Ok(()) } #[pallet::validate_unsigned] diff --git a/pallets/eth-bridge/src/tests/mock.rs b/pallets/eth-bridge/src/tests/mock.rs index af670e125..298a1c43f 100644 --- a/pallets/eth-bridge/src/tests/mock.rs +++ b/pallets/eth-bridge/src/tests/mock.rs @@ -6,17 +6,16 @@ use frame_system as system; use pallet_avn::{testing::U64To32BytesConverter, EthereumPublicKeyChecker}; use pallet_session as session; use parking_lot::RwLock; +use sp_avn_common::event_types::{EthEvent, EthEventId, LiftedData}; use sp_core::{ offchain::{ testing::{OffchainState, PoolState, TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - ConstU32, ConstU64, H256, + ConstU32, ConstU64, H256, U256, }; use sp_runtime::{ - testing::{TestSignature, TestXt, UintAuthorityId}, - traits::{BlakeTwo256, ConvertInto, IdentityLookup}, - BuildStorage, Perbill, + testing::{TestSignature, TestXt, UintAuthorityId}, traits::{BlakeTwo256, ConvertInto, IdentityLookup}, BuildStorage, DispatchResult, Perbill }; use sp_staking::offence::OffenceError; use std::{cell::RefCell, convert::From, sync::Arc}; @@ -47,10 +46,15 @@ impl ReportOffence for OffenceHandler { pub struct Context { pub eth_tx_hash: H256, pub already_set_eth_tx_hash: H256, + pub mock_event_partition: EthereumEventsPartition, pub test_signature: TestSignature, + pub test_signature_two: TestSignature, + pub test_signature_three: TestSignature, pub confirmation_signature: ecdsa::Signature, pub tx_succeeded: bool, pub author: Author, + pub author_two: Author, + pub author_three: Author, pub confirming_author: Author, pub second_confirming_author: Author, pub third_confirming_author: Author, @@ -100,6 +104,7 @@ impl Config for TestRuntime { type AccountToBytesConvert = U64To32BytesConverter; type BridgeInterfaceNotification = TestRuntime; type ReportCorroborationOffence = OffenceHandler; + type ProcessedEventsChecker = Self; type EthereumEventsFilter = (); } @@ -171,6 +176,12 @@ pub fn create_confirming_author(author_id: u64) -> Author { Author:: { key: UintAuthorityId(author_id), account_id: author_id } } +pub fn create_mock_event_partition(events: EthEvent) -> EthereumEventsPartition { + let mut partition: BoundedBTreeSet = BoundedBTreeSet::new(); + partition.try_insert(DiscoveredEvent { event: events.clone(), block: 2 }); + EthereumEventsPartition::new(EthBlockRange {start_block: 1, length: 1000}, 0, false, partition) +} + pub fn lower_is_ready_to_be_claimed(lower_id: &u32) -> bool { LOWERSREADYTOCLAIM.with(|lowers| lowers.borrow_mut().iter().any(|l| l == lower_id)) } @@ -185,6 +196,14 @@ pub fn setup_context() -> Context { key: UintAuthorityId(primary_validator_id), account_id: primary_validator_id, }; + let author_two = Author:: { + key: UintAuthorityId(22), + account_id: 22, + }; + let author_three = Author:: { + key: UintAuthorityId(23), + account_id: 23, + }; let mut confirming_validator_id: u64 = 1; if primary_validator_id == confirming_validator_id { confirming_validator_id += 1 @@ -194,20 +213,28 @@ pub fn setup_context() -> Context { let third_confirming_author = create_confirming_author(confirming_validator_id + 2); let fourth_confirming_author = create_confirming_author(confirming_validator_id + 3); let test_signature = generate_signature(author.clone(), b"test context"); + let test_signature_two = generate_signature(author.clone(), b"test context"); + let test_signature_three = generate_signature(author.clone(), b"test context"); let tx_succeeded = false; let eth_tx_hash = H256::from_slice(&[0u8; 32]); let already_set_eth_tx_hash = H256::from_slice(&[1u8; 32]); let confirmation_signature = ecdsa::Signature::try_from(&[1; 65][0..65]).unwrap(); let finalised_block_vec = Some(hex::encode(10u32.encode()).into()); + let mock_event_partition = create_mock_event_partition(EthEvent { event_id: EthEventId { signature: ValidEvents::Lifted.signature(), transaction_hash: eth_tx_hash }, event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { token_contract: H160::zero(), sender_address: H160::zero(), receiver_address: H256::zero(), amount: 1, nonce: U256::zero() }) }); UintAuthorityId::set_all_keys(vec![UintAuthorityId(primary_validator_id)]); Context { eth_tx_hash, + mock_event_partition, already_set_eth_tx_hash, test_signature, + test_signature_two, + test_signature_three, tx_succeeded, author: author.clone(), + author_two: author_two.clone(), + author_three: author_three.clone(), confirming_author: confirming_author.clone(), second_confirming_author: second_confirming_author.clone(), third_confirming_author: third_confirming_author.clone(), @@ -404,3 +431,21 @@ impl BridgeInterfaceNotification for TestRuntime { Ok(()) } } + +thread_local! { + static PROCESSED_EVENTS: RefCell> = RefCell::new(vec![]); +} + +pub fn insert_to_mock_processed_events(event_id: &EthEventId, processed: bool) { + PROCESSED_EVENTS.with(|l| l.borrow_mut().push((event_id.clone(), processed))); +} + +impl ProcessedEventsChecker for TestRuntime { + fn check_event(event_id: &EthEventId) -> bool { + PROCESSED_EVENTS.with(|l| l.borrow().iter().any(|(event, _processed)| event == event_id)) + } + + fn add_processed_event(event_id: &EthEventId, accepted: bool) { + insert_to_mock_processed_events(event_id, accepted); + } +} diff --git a/pallets/eth-bridge/src/tests/tests.rs b/pallets/eth-bridge/src/tests/tests.rs index ac14b1d61..38d0d48d7 100644 --- a/pallets/eth-bridge/src/tests/tests.rs +++ b/pallets/eth-bridge/src/tests/tests.rs @@ -29,6 +29,15 @@ fn corroborate_good_transactions( ) } +// Added this function as in event_listener_tests to initialize the active event range +fn init_active_range() { + ActiveEthereumRange::::put(ActiveEthRange { + range: EthBlockRange { start_block: 1, length: 1000 }, + partition: 0, + event_types_filter: Default::default(), + }); +} + fn corroborate_bad_transactions( tx_id: EthereumId, author: &Validator, @@ -803,3 +812,59 @@ fn self_corroborate_fails() { ); }); } + +mod process_events { + use sp_avn_common::event_types::EthEventId; + + use super::*; + + + // succesfully process the specified ethereum_event + #[test] + fn succesful_event_processing_accepted() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context = setup_context(); + init_active_range(); + + // Two calls needed as upon the first there are not enough votes to pass the condition in lin.rs line 563, to reach the call of process_ethereum_events_partition() + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone())); + // assert!(System::events().iter().any(|record| record.event == + // mock::RuntimeEvent::EthBridge(Event::::EventProcessed { + // accepted: true, + // eth_event_id: some_value, + // }))); + }); + } + + + // This test should fail processing the ethereum_event and emit the specified event + #[test] + fn succesful_event_processing_not_accepted() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context = setup_context(); + init_active_range(); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); + assert!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone()).is_err()); + // assert!(System::events().iter().any(|record| record.event == + // mock::RuntimeEvent::EthBridge(Event::::EventProcessed { + // accepted: false, + // eth_event_id: some_value + // }))); + }); + } + + + // This test should fail on the check T::ProcessedEventsChecker::check_event(&event.event_id.clone()), if the event is already in the system + #[test] + fn event_already_processed() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context = setup_context(); + init_active_range(); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, context.mock_event_partition, context.test_signature)); + }); + } +} diff --git a/pallets/ethereum-events/src/lib.rs b/pallets/ethereum-events/src/lib.rs index afc94ebf6..b0185c2ac 100644 --- a/pallets/ethereum-events/src/lib.rs +++ b/pallets/ethereum-events/src/lib.rs @@ -1587,7 +1587,11 @@ impl Pallet { impl ProcessedEventsChecker for Pallet { fn check_event(event_id: &EthEventId) -> bool { - return >::contains_key(event_id) + return >::contains_key(event_id) || Self::get_pending_event_index(event_id).is_ok() + } + + fn add_processed_event(event_id: &EthEventId, accepted: bool) { + >::insert(event_id.clone(), accepted); } } diff --git a/pallets/nft-manager/src/tests/mock.rs b/pallets/nft-manager/src/tests/mock.rs index 93d6de660..575bfac21 100644 --- a/pallets/nft-manager/src/tests/mock.rs +++ b/pallets/nft-manager/src/tests/mock.rs @@ -130,6 +130,7 @@ impl ProcessedEventsChecker for TestRuntime { fn check_event(event_id: &EthEventId) -> bool { return PROCESSED_EVENTS.with(|l| l.borrow_mut().iter().any(|event| event == event_id)) } + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } pub struct TestAccount { diff --git a/pallets/parachain-staking/src/tests/mock.rs b/pallets/parachain-staking/src/tests/mock.rs index 001590908..b8cb358e0 100644 --- a/pallets/parachain-staking/src/tests/mock.rs +++ b/pallets/parachain-staking/src/tests/mock.rs @@ -347,6 +347,7 @@ impl pallet_eth_bridge::Config for Test { type AccountToBytesConvert = AVN; type BridgeInterfaceNotification = Self; type ReportCorroborationOffence = (); + type ProcessedEventsChecker = (); } impl pallet_timestamp::Config for Test { diff --git a/pallets/summary/src/tests/mock.rs b/pallets/summary/src/tests/mock.rs index 3060840ba..13ff38442 100644 --- a/pallets/summary/src/tests/mock.rs +++ b/pallets/summary/src/tests/mock.rs @@ -352,6 +352,7 @@ impl pallet_eth_bridge::Config for TestRuntime { type AccountToBytesConvert = AVN; type BridgeInterfaceNotification = Self; type ReportCorroborationOffence = OffenceHandler; + type ProcessedEventsChecker = (); type EthereumEventsFilter = (); } diff --git a/pallets/token-manager/src/mock.rs b/pallets/token-manager/src/mock.rs index 2b03150cd..4f24cb8a5 100644 --- a/pallets/token-manager/src/mock.rs +++ b/pallets/token-manager/src/mock.rs @@ -303,6 +303,7 @@ impl pallet_eth_bridge::Config for TestRuntime { type AccountToBytesConvert = AVN; type BridgeInterfaceNotification = Self; type ReportCorroborationOffence = (); + type ProcessedEventsChecker = (); } impl pallet_timestamp::Config for TestRuntime { @@ -376,6 +377,8 @@ impl ProcessedEventsChecker for TestRuntime { fn check_event(event_id: &EthEventId) -> bool { return PROCESSED_EVENTS.with(|l| l.borrow_mut().iter().any(|event| event == event_id)) } + + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } impl TokenManager { diff --git a/pallets/validators-manager/src/tests/mock.rs b/pallets/validators-manager/src/tests/mock.rs index 5bdedcb40..02ac8e88e 100644 --- a/pallets/validators-manager/src/tests/mock.rs +++ b/pallets/validators-manager/src/tests/mock.rs @@ -250,6 +250,7 @@ impl pallet_eth_bridge::Config for TestRuntime { type AccountToBytesConvert = AVN; type BridgeInterfaceNotification = Self; type ReportCorroborationOffence = (); + type ProcessedEventsChecker = (); } impl BridgeInterfaceNotification for TestRuntime { @@ -350,6 +351,8 @@ impl ProcessedEventsChecker for TestRuntime { }) }) } + + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } // TODO: Do we need to test the ECDSA sig verification logic here? If so, replace this with a call diff --git a/primitives/avn-common/src/event_discovery.rs b/primitives/avn-common/src/event_discovery.rs index 4f3292124..98d3878dc 100644 --- a/primitives/avn-common/src/event_discovery.rs +++ b/primitives/avn-common/src/event_discovery.rs @@ -102,7 +102,7 @@ impl EthereumEventsPartition { blake2_256(&(&self).encode()).into() } - fn new(range: EthBlockRange, partition: u16, is_last: bool, data: EthereumEventsSet) -> Self { + pub fn new(range: EthBlockRange, partition: u16, is_last: bool, data: EthereumEventsSet) -> Self { EthereumEventsPartition { range, partition, is_last, data } } } diff --git a/runtime/avn/src/lib.rs b/runtime/avn/src/lib.rs index d3ba331ec..94c5b672d 100644 --- a/runtime/avn/src/lib.rs +++ b/runtime/avn/src/lib.rs @@ -634,6 +634,7 @@ impl pallet_eth_bridge::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type MinEthBlockConfirmation = MinEthBlockConfirmation; + type ProcessedEventsChecker = EthereumEvents; type AccountToBytesConvert = Avn; type TimeProvider = pallet_timestamp::Pallet; type ReportCorroborationOffence = Offences; diff --git a/runtime/test/src/lib.rs b/runtime/test/src/lib.rs index ee5041dd8..4628f0ca0 100644 --- a/runtime/test/src/lib.rs +++ b/runtime/test/src/lib.rs @@ -662,6 +662,7 @@ impl pallet_eth_bridge::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type MinEthBlockConfirmation = MinEthBlockConfirmation; + type ProcessedEventsChecker = EthereumEvents; type AccountToBytesConvert = Avn; type ReportCorroborationOffence = Offences; type TimeProvider = pallet_timestamp::Pallet; From e807d3c78b495fab1295707113612b0b0e4a6ad2 Mon Sep 17 00:00:00 2001 From: nahuseyoum Date: Fri, 3 May 2024 16:47:48 +0100 Subject: [PATCH 17/25] rename event and update --- pallets/avn/src/lib.rs | 6 +++--- pallets/eth-bridge/src/lib.rs | 24 ++++++++++-------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/pallets/avn/src/lib.rs b/pallets/avn/src/lib.rs index 07ad82ec5..ff84c90a7 100644 --- a/pallets/avn/src/lib.rs +++ b/pallets/avn/src/lib.rs @@ -769,7 +769,7 @@ pub trait BridgeInterfaceNotification { fn process_lower_proof_result(_: u32, _: Vec, _: Result, ()>) -> DispatchResult { Ok(()) } - fn on_event_processed(_event: &EthEvent) -> DispatchResult { + fn on_incoming_event_processed(_event: &EthEvent) -> DispatchResult { Ok(()) } } @@ -790,8 +790,8 @@ impl BridgeInterfaceNotification for Tuple { Ok(()) } - fn on_event_processed(_event: &EthEvent) -> DispatchResult { - for_tuples!( #( Tuple::on_event_processed(_event)?; )* ); + fn on_incoming_event_processed(_event: &EthEvent) -> DispatchResult { + for_tuples!( #( Tuple::on_incoming_event_processed(_event)?; )* ); Ok(()) } } diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index eca5a4acf..3b6077a31 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -815,33 +815,29 @@ pub mod pallet { } } + // TODO: re-add the `Accepted and Rejected events fn process_ethereum_event(event: &EthEvent) -> Result<(), DispatchError> { // TODO before processing ensure that the event has not already been processed // Do the check that is not processed via ProcessedEventsChecker println!("Processing events"); ensure!(T::ProcessedEventsChecker::check_event(&event.event_id.clone()), Error::::EventAlreadyProcessed); + let mut event_accepted = false; match T::BridgeInterfaceNotification::on_event_processed(&event) { - Ok(_) => { - >::deposit_event(Event::::EventProcessed { - accepted: true, - eth_event_id: event.event_id.clone(), - }); - // Add record of succesful processing via ProcessedEventsChecker - T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), true); - }, + Ok(_) => { event_accepted = true }, Err(err) => { log::error!("💔 Processing ethereum event failed: {:?}", err); - >::deposit_event(Event::::EventProcessed { - accepted: false, - eth_event_id: event.event_id.clone(), - }); - // Add record of unsuccesful processing via ProcessedEventsChecker - T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), false); }, }; + >::deposit_event(Event::::EventProcessed { + accepted: event_accepted, + eth_event_id: event.event_id.clone(), + }); + // Add record of succesful processing via ProcessedEventsChecker + T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); + Ok(()) } From c0c44f99309aa73df65a1bce6d4c15f5b261f78b Mon Sep 17 00:00:00 2001 From: nahuseyoum Date: Mon, 13 May 2024 13:13:08 +0100 Subject: [PATCH 18/25] update code and fix tests --- pallets/avn/src/lib.rs | 4 +- pallets/eth-bridge/src/lib.rs | 15 ++- .../src/tests/incoming_events_tests.rs | 91 +++++++++++++++++++ pallets/eth-bridge/src/tests/mock.rs | 37 +++++++- pallets/eth-bridge/src/tests/tests.rs | 64 ------------- pallets/ethereum-events/src/lib.rs | 2 +- pallets/nft-manager/src/batch_nft.rs | 4 +- pallets/nft-manager/src/lib.rs | 4 +- pallets/nft-manager/src/tests/mock.rs | 2 +- pallets/token-manager/src/lib.rs | 6 +- pallets/token-manager/src/mock.rs | 4 +- pallets/validators-manager/src/tests/mock.rs | 10 +- .../src/tests/test_eth_key_actions.rs | 5 +- 13 files changed, 148 insertions(+), 100 deletions(-) create mode 100644 pallets/eth-bridge/src/tests/incoming_events_tests.rs diff --git a/pallets/avn/src/lib.rs b/pallets/avn/src/lib.rs index 7da885ea2..75f8648bc 100644 --- a/pallets/avn/src/lib.rs +++ b/pallets/avn/src/lib.rs @@ -703,13 +703,13 @@ impl Enforcer for () { } pub trait ProcessedEventsChecker { - fn check_event(event_id: &EthEventId) -> bool; + fn processed_event_exists(event_id: &EthEventId) -> bool; fn add_processed_event(event_id: &EthEventId, accepted: bool); } impl ProcessedEventsChecker for () { - fn check_event(_event_id: &EthEventId) -> bool { false } + fn processed_event_exists(_event_id: &EthEventId) -> bool { false } fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index a5aa36855..b235bbe94 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -111,6 +111,9 @@ mod mock; #[cfg(test)] #[path = "tests/tests.rs"] mod tests; +#[cfg(test)] +#[path = "tests/incoming_events_tests.rs"] +mod incoming_events_tests; pub use pallet::*; pub mod default_weights; @@ -806,13 +809,10 @@ pub mod pallet { if active_range.event_types_filter.contains(&valid_event) { let _ = process_ethereum_event::(&discovered_event.event); } else { - println!("DOES NOT contain valid ethereum event, process_ethereum_event not reached\n"); log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter); }, - None => log::warn!( - "Unknown Ethereum event signature in range {:?}", - &discovered_event.event.event_id.signature - ), + None => log::warn!("Unknown Ethereum event signature in range {:?}", &discovered_event.event.event_id.signature) + }; } @@ -827,12 +827,11 @@ pub mod pallet { fn process_ethereum_event(event: &EthEvent) -> Result<(), DispatchError> { // TODO before processing ensure that the event has not already been processed // Do the check that is not processed via ProcessedEventsChecker - println!("Processing events"); - ensure!(T::ProcessedEventsChecker::check_event(&event.event_id.clone()), Error::::EventAlreadyProcessed); + ensure!(false == T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()), Error::::EventAlreadyProcessed); let mut event_accepted = false; - match T::BridgeInterfaceNotification::on_event_processed(&event) { + match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { Ok(_) => { event_accepted = true }, Err(err) => { log::error!("💔 Processing ethereum event failed: {:?}", err); diff --git a/pallets/eth-bridge/src/tests/incoming_events_tests.rs b/pallets/eth-bridge/src/tests/incoming_events_tests.rs new file mode 100644 index 000000000..f12b74758 --- /dev/null +++ b/pallets/eth-bridge/src/tests/incoming_events_tests.rs @@ -0,0 +1,91 @@ +// Copyright 2023 Aventus Network Systems (UK) Ltd. + +#![cfg(test)] +use crate::{eth::generate_send_calldata, mock::*, request::*, *}; +use frame_support::{ + assert_err, assert_noop, assert_ok, dispatch::DispatchResultWithPostInfo, error::BadOrigin, +}; +use sp_runtime::{testing::UintAuthorityId, DispatchError}; +pub extern crate alloc; +use alloc::collections::BTreeSet; +use sp_avn_common::event_discovery::EthBridgeEventsFilter; + +const ROOT_HASH: &str = "30b83f0d722d1d4308ab4660a72dbaf0a7392d5674eca3cd21d57256d42df7a0"; +const REWARDS: &[u8] = b"15043665996000000000"; +const AVG_STAKED: &[u8] = b"9034532443555111110000"; +const PERIOD: &[u8] = b"3"; +const T2_PUB_KEY: &str = "14aeac90dbd3573458f9e029eb2de122ee94f2f0bc5ee4b6c6c5839894f1a547"; +const T1_PUB_KEY: &str = "23d79f6492dddecb436333a5e7a4cfcc969f568e01283fa2964aae15327fb8a3b685a4d0f3ef9b3c2adb20f681dbc74b7f82c1cf8438d37f2c10e9c79591e9ea"; + +// Added this function as in event_listener_tests to initialize the active event range +fn init_active_range() { + ActiveEthereumRange::::put(ActiveEthRange { + range: EthBlockRange { start_block: 1, length: 1000 }, + partition: 0, + event_types_filter: EthBridgeEventsFilter::try_from(vec![ + ValidEvents::AddedValidator, + ValidEvents::Lifted, + ValidEvents::AvtGrowthLifted, + ValidEvents::AvtLowerClaimed, + ] + .into_iter() + .collect::>()).unwrap() + }); +} + +mod process_events { + use sp_avn_common::event_types::EthEventId; + use super::*; + + // succesfully process the specified ethereum_event + #[test] + fn succesful_event_processing_accepted() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context = setup_context(); + init_active_range(); + + // Two calls needed as upon the first there are not enough votes to pass the condition in lib.rs line 563, to reach the call of process_ethereum_events_partition() + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone())); + + assert!(System::events().iter().any(|record| record.event == + mock::RuntimeEvent::EthBridge(Event::::EventProcessed { + accepted: true, + eth_event_id: context.eth_event_id.clone(), + }))); + }); + } + + + // This test should fail processing the ethereum_event and emit the specified event + #[test] + fn succesful_event_processing_not_accepted() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context = setup_context(); + init_active_range(); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.bad_mock_event_partition.clone(), context.test_signature.clone())); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.bad_mock_event_partition.clone(), context.test_signature.clone())); + + + assert!(System::events().iter().any(|record| record.event == + mock::RuntimeEvent::EthBridge(Event::::EventProcessed { + accepted: false, + eth_event_id: context.bad_eth_event_id.clone(), + }))); + }); + } + + + // This test should fail on the check T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()), if the event is already in the system + #[test] + fn event_already_processed() { + let mut ext = ExtBuilder::build_default().with_validators().as_externality(); + ext.execute_with(|| { + let context = setup_context(); + init_active_range(); + assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, context.mock_event_partition, context.test_signature)); + }); + } +} diff --git a/pallets/eth-bridge/src/tests/mock.rs b/pallets/eth-bridge/src/tests/mock.rs index 298a1c43f..772d1cc09 100644 --- a/pallets/eth-bridge/src/tests/mock.rs +++ b/pallets/eth-bridge/src/tests/mock.rs @@ -15,7 +15,7 @@ use sp_core::{ ConstU32, ConstU64, H256, U256, }; use sp_runtime::{ - testing::{TestSignature, TestXt, UintAuthorityId}, traits::{BlakeTwo256, ConvertInto, IdentityLookup}, BuildStorage, DispatchResult, Perbill + testing::{TestSignature, TestXt, UintAuthorityId}, traits::{BlakeTwo256, ConvertInto, IdentityLookup}, BuildStorage, DispatchResult, DispatchError, Perbill }; use sp_staking::offence::OffenceError; use std::{cell::RefCell, convert::From, sync::Arc}; @@ -47,6 +47,7 @@ pub struct Context { pub eth_tx_hash: H256, pub already_set_eth_tx_hash: H256, pub mock_event_partition: EthereumEventsPartition, + pub bad_mock_event_partition: EthereumEventsPartition, pub test_signature: TestSignature, pub test_signature_two: TestSignature, pub test_signature_three: TestSignature, @@ -66,6 +67,8 @@ pub struct Context { pub lower_id: u32, pub block_number: BlockNumber, pub expected_lower_msg_hash: String, + pub eth_event_id: EthEventId, + pub bad_eth_event_id: EthEventId, } const ROOT_HASH: &str = "30b83f0d722d1d4308ab4660a72dbaf0a7392d5674eca3cd21d57256d42df7a0"; @@ -178,7 +181,7 @@ pub fn create_confirming_author(author_id: u64) -> Author { pub fn create_mock_event_partition(events: EthEvent) -> EthereumEventsPartition { let mut partition: BoundedBTreeSet = BoundedBTreeSet::new(); - partition.try_insert(DiscoveredEvent { event: events.clone(), block: 2 }); + partition.try_insert(DiscoveredEvent { event: events.clone(), block: 2 }).unwrap(); EthereumEventsPartition::new(EthBlockRange {start_block: 1, length: 1000}, 0, false, partition) } @@ -220,7 +223,22 @@ pub fn setup_context() -> Context { let already_set_eth_tx_hash = H256::from_slice(&[1u8; 32]); let confirmation_signature = ecdsa::Signature::try_from(&[1; 65][0..65]).unwrap(); let finalised_block_vec = Some(hex::encode(10u32.encode()).into()); - let mock_event_partition = create_mock_event_partition(EthEvent { event_id: EthEventId { signature: ValidEvents::Lifted.signature(), transaction_hash: eth_tx_hash }, event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { token_contract: H160::zero(), sender_address: H160::zero(), receiver_address: H256::zero(), amount: 1, nonce: U256::zero() }) }); + let eth_event_id = EthEventId { signature: ValidEvents::Lifted.signature(), transaction_hash: eth_tx_hash }; + let bad_eth_event_id = EthEventId { signature: ValidEvents::Lifted.signature(), transaction_hash: H256::from_slice(&[6u8; 32])}; + let bad_eth_event = EthEvent { + event_id: bad_eth_event_id.clone(), + event_data: sp_avn_common::event_types::EventData::LogLifted( + LiftedData { token_contract: H160::zero(), sender_address: H160::zero(), receiver_address: H256::zero(), amount: 1, nonce: U256::zero() } + ) + }; + let mock_event_partition = create_mock_event_partition( + EthEvent { + event_id: eth_event_id.clone(), + event_data: sp_avn_common::event_types::EventData::LogLifted( + LiftedData { token_contract: H160::zero(), sender_address: H160::zero(), receiver_address: H256::zero(), amount: 1, nonce: U256::zero() } + ) + }); + let bad_mock_event_partition = create_mock_event_partition(bad_eth_event); UintAuthorityId::set_all_keys(vec![UintAuthorityId(primary_validator_id)]); @@ -249,6 +267,9 @@ pub fn setup_context() -> Context { // if request_params changes, this should also change expected_lower_msg_hash: "5892dee772ffe3d97e9525b62805bbcd91bac29026536cfa09269623128280ca" .to_string(), + eth_event_id, + bad_mock_event_partition, + bad_eth_event_id, } } @@ -430,6 +451,14 @@ impl BridgeInterfaceNotification for TestRuntime { Ok(()) } + + fn on_incoming_event_processed(event: &EthEvent) -> DispatchResult { + if event.event_id.transaction_hash == H256::from_slice(&[6u8; 32]) { + return Err(DispatchError::Other("Test - Bad event")) + } + + Ok(()) + } } thread_local! { @@ -441,7 +470,7 @@ pub fn insert_to_mock_processed_events(event_id: &EthEventId, processed: bool) { } impl ProcessedEventsChecker for TestRuntime { - fn check_event(event_id: &EthEventId) -> bool { + fn processed_event_exists(event_id: &EthEventId) -> bool { PROCESSED_EVENTS.with(|l| l.borrow().iter().any(|(event, _processed)| event == event_id)) } diff --git a/pallets/eth-bridge/src/tests/tests.rs b/pallets/eth-bridge/src/tests/tests.rs index 38d0d48d7..4d3da1a42 100644 --- a/pallets/eth-bridge/src/tests/tests.rs +++ b/pallets/eth-bridge/src/tests/tests.rs @@ -29,15 +29,6 @@ fn corroborate_good_transactions( ) } -// Added this function as in event_listener_tests to initialize the active event range -fn init_active_range() { - ActiveEthereumRange::::put(ActiveEthRange { - range: EthBlockRange { start_block: 1, length: 1000 }, - partition: 0, - event_types_filter: Default::default(), - }); -} - fn corroborate_bad_transactions( tx_id: EthereumId, author: &Validator, @@ -813,58 +804,3 @@ fn self_corroborate_fails() { }); } -mod process_events { - use sp_avn_common::event_types::EthEventId; - - use super::*; - - - // succesfully process the specified ethereum_event - #[test] - fn succesful_event_processing_accepted() { - let mut ext = ExtBuilder::build_default().with_validators().as_externality(); - ext.execute_with(|| { - let context = setup_context(); - init_active_range(); - - // Two calls needed as upon the first there are not enough votes to pass the condition in lin.rs line 563, to reach the call of process_ethereum_events_partition() - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone())); - // assert!(System::events().iter().any(|record| record.event == - // mock::RuntimeEvent::EthBridge(Event::::EventProcessed { - // accepted: true, - // eth_event_id: some_value, - // }))); - }); - } - - - // This test should fail processing the ethereum_event and emit the specified event - #[test] - fn succesful_event_processing_not_accepted() { - let mut ext = ExtBuilder::build_default().with_validators().as_externality(); - ext.execute_with(|| { - let context = setup_context(); - init_active_range(); - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); - assert!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone()).is_err()); - // assert!(System::events().iter().any(|record| record.event == - // mock::RuntimeEvent::EthBridge(Event::::EventProcessed { - // accepted: false, - // eth_event_id: some_value - // }))); - }); - } - - - // This test should fail on the check T::ProcessedEventsChecker::check_event(&event.event_id.clone()), if the event is already in the system - #[test] - fn event_already_processed() { - let mut ext = ExtBuilder::build_default().with_validators().as_externality(); - ext.execute_with(|| { - let context = setup_context(); - init_active_range(); - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, context.mock_event_partition, context.test_signature)); - }); - } -} diff --git a/pallets/ethereum-events/src/lib.rs b/pallets/ethereum-events/src/lib.rs index b0185c2ac..fc586c424 100644 --- a/pallets/ethereum-events/src/lib.rs +++ b/pallets/ethereum-events/src/lib.rs @@ -1586,7 +1586,7 @@ impl Pallet { } impl ProcessedEventsChecker for Pallet { - fn check_event(event_id: &EthEventId) -> bool { + fn processed_event_exists(event_id: &EthEventId) -> bool { return >::contains_key(event_id) || Self::get_pending_event_index(event_id).is_ok() } diff --git a/pallets/nft-manager/src/batch_nft.rs b/pallets/nft-manager/src/batch_nft.rs index 454e650da..f87bebf98 100644 --- a/pallets/nft-manager/src/batch_nft.rs +++ b/pallets/nft-manager/src/batch_nft.rs @@ -140,7 +140,7 @@ pub fn process_mint_batch_nft_event( data: &NftMintData, ) -> DispatchResult { ensure!( - T::ProcessedEventsChecker::check_event(event_id), + T::ProcessedEventsChecker::processed_event_exists(event_id), Error::::NoTier1EventForNftOperation ); ensure!( @@ -210,7 +210,7 @@ pub fn process_end_batch_listing_event( data: &NftEndBatchListingData, ) -> DispatchResult { ensure!( - T::ProcessedEventsChecker::check_event(event_id), + T::ProcessedEventsChecker::processed_event_exists(event_id), Error::::NoTier1EventForNftOperation ); diff --git a/pallets/nft-manager/src/lib.rs b/pallets/nft-manager/src/lib.rs index 7d385b2d8..c860c7c29 100644 --- a/pallets/nft-manager/src/lib.rs +++ b/pallets/nft-manager/src/lib.rs @@ -925,7 +925,7 @@ impl Pallet { ensure!(data.op_id == nft.nonce, Error::::NftNonceMismatch); ensure!( - T::ProcessedEventsChecker::check_event(event_id), + T::ProcessedEventsChecker::processed_event_exists(event_id), Error::::NoTier1EventForNftOperation ); @@ -977,7 +977,7 @@ impl Pallet { ensure!(market == NftSaleType::Ethereum, Error::::NftNotListedForEthereumSale); ensure!(data.op_id == nft.nonce, Error::::NftNonceMismatch); ensure!( - T::ProcessedEventsChecker::check_event(event_id), + T::ProcessedEventsChecker::processed_event_exists(event_id), Error::::NoTier1EventForNftOperation ); diff --git a/pallets/nft-manager/src/tests/mock.rs b/pallets/nft-manager/src/tests/mock.rs index 575bfac21..4eb2c9be6 100644 --- a/pallets/nft-manager/src/tests/mock.rs +++ b/pallets/nft-manager/src/tests/mock.rs @@ -127,7 +127,7 @@ pub fn insert_to_mock_processed_events(event_id: &EthEventId) { } impl ProcessedEventsChecker for TestRuntime { - fn check_event(event_id: &EthEventId) -> bool { + fn processed_event_exists(event_id: &EthEventId) -> bool { return PROCESSED_EVENTS.with(|l| l.borrow_mut().iter().any(|event| event == event_id)) } fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} diff --git a/pallets/token-manager/src/lib.rs b/pallets/token-manager/src/lib.rs index ac124f411..e678d45eb 100644 --- a/pallets/token-manager/src/lib.rs +++ b/pallets/token-manager/src/lib.rs @@ -854,7 +854,7 @@ impl Pallet { let recipient_account_id = T::AccountId::decode(&mut data.receiver_address.as_bytes()) .expect("32 bytes will always decode into an AccountId"); - let event_validity = T::ProcessedEventsChecker::check_event(event_id); + let event_validity = T::ProcessedEventsChecker::processed_event_exists(event_id); ensure!(event_validity, Error::::NoTier1EventForLogLifted); if data.amount == 0 { @@ -883,7 +883,7 @@ impl Pallet { fn process_avt_growth_lift(event: &EthEvent, data: &AvtGrowthLiftedData) -> DispatchResult { let event_id = &event.event_id; - let event_validity = T::ProcessedEventsChecker::check_event(event_id); + let event_validity = T::ProcessedEventsChecker::processed_event_exists(event_id); ensure!(event_validity, Error::::NoTier1EventForLogAvtGrowthLifted); if data.amount == 0 { @@ -914,7 +914,7 @@ impl Pallet { fn process_lower_claim(event: &EthEvent, data: &AvtLowerClaimedData) -> DispatchResult { let event_id = &event.event_id; - let event_validity = T::ProcessedEventsChecker::check_event(event_id); + let event_validity = T::ProcessedEventsChecker::processed_event_exists(event_id); ensure!(event_validity, Error::::NoTier1EventForLogLowerClaimed); ensure!( diff --git a/pallets/token-manager/src/mock.rs b/pallets/token-manager/src/mock.rs index 4f24cb8a5..154749402 100644 --- a/pallets/token-manager/src/mock.rs +++ b/pallets/token-manager/src/mock.rs @@ -374,10 +374,10 @@ pub fn insert_to_mock_processed_events(event_id: &EthEventId) { } impl ProcessedEventsChecker for TestRuntime { - fn check_event(event_id: &EthEventId) -> bool { + fn processed_event_exists(event_id: &EthEventId) -> bool { return PROCESSED_EVENTS.with(|l| l.borrow_mut().iter().any(|event| event == event_id)) } - + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } diff --git a/pallets/validators-manager/src/tests/mock.rs b/pallets/validators-manager/src/tests/mock.rs index 02ac8e88e..9c20c0860 100644 --- a/pallets/validators-manager/src/tests/mock.rs +++ b/pallets/validators-manager/src/tests/mock.rs @@ -343,7 +343,7 @@ thread_local! { } impl ProcessedEventsChecker for TestRuntime { - fn check_event(event_id: &EthEventId) -> bool { + fn processed_event_exists(event_id: &EthEventId) -> bool { return PROCESSED_EVENTS.with(|l| { l.borrow_mut().iter().any(|event| { &EthEventId { signature: event.0.clone(), transaction_hash: event.1.clone() } == @@ -351,7 +351,7 @@ impl ProcessedEventsChecker for TestRuntime { }) }) } - + fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } @@ -463,11 +463,7 @@ impl ExtBuilder { } validators_account_ids.push(TestAccount::new(seed).account_id()); } - println!( - "keys {:?} {:?}", - validators_account_ids.len(), - initial_maximum_validators_public_keys().len() - ); + self.setup_validators(&validators_account_ids, initial_maximum_validators_public_keys) } diff --git a/primitives/avn-common/src/tests/test_eth_key_actions.rs b/primitives/avn-common/src/tests/test_eth_key_actions.rs index 49ad5c7a9..fba9d2d86 100644 --- a/primitives/avn-common/src/tests/test_eth_key_actions.rs +++ b/primitives/avn-common/src/tests/test_eth_key_actions.rs @@ -24,10 +24,7 @@ fn test_decompress_eth_public_key() { let decompressed_key = decompress_eth_public_key(compressed_key); match decompressed_key { - Ok(key) => { - println!("decompressed_pub_key: {:?}", key); - assert_eq!(key, expected_decompressed_key); - }, + Ok(key) => assert_eq!(key, expected_decompressed_key), Err(e) => { panic!("decompress_eth_public_key failed with error: {:?}", e); }, From 5b8b87602fe290ffad19b7acd95d042b3c049544 Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Tue, 14 May 2024 16:39:10 +0100 Subject: [PATCH 19/25] Change error handling in process events, fmt code --- pallets/avn/src/lib.rs | 4 +- pallets/eth-bridge/src/lib.rs | 64 +++++++------ .../src/tests/incoming_events_tests.rs | 90 +++++++++++++++---- pallets/eth-bridge/src/tests/mock.rs | 86 +++++++++++++----- pallets/eth-bridge/src/tests/tests.rs | 1 - pallets/ethereum-events/src/lib.rs | 3 +- primitives/avn-common/src/event_discovery.rs | 7 +- 7 files changed, 181 insertions(+), 74 deletions(-) diff --git a/pallets/avn/src/lib.rs b/pallets/avn/src/lib.rs index 75f8648bc..6faa4ff21 100644 --- a/pallets/avn/src/lib.rs +++ b/pallets/avn/src/lib.rs @@ -709,7 +709,9 @@ pub trait ProcessedEventsChecker { } impl ProcessedEventsChecker for () { - fn processed_event_exists(_event_id: &EthEventId) -> bool { false } + fn processed_event_exists(_event_id: &EthEventId) -> bool { + false + } fn add_processed_event(_event_id: &EthEventId, _accepted: bool) {} } diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index b235bbe94..476f9c602 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -67,8 +67,8 @@ use frame_system::{ pallet_prelude::{BlockNumberFor, OriginFor}, }; use pallet_avn::{ - self as avn, BridgeInterface, BridgeInterfaceNotification, ProcessedEventsChecker, Error as avn_error, LowerParams, - MAX_VALIDATOR_ACCOUNTS, + self as avn, BridgeInterface, BridgeInterfaceNotification, Error as avn_error, LowerParams, + ProcessedEventsChecker, MAX_VALIDATOR_ACCOUNTS, }; use pallet_session::historical::IdentificationTuple; @@ -103,6 +103,9 @@ mod benchmarking; #[path = "tests/event_listener_tests.rs"] mod event_listener_tests; #[cfg(test)] +#[path = "tests/incoming_events_tests.rs"] +mod incoming_events_tests; +#[cfg(test)] #[path = "tests/lower_proof_tests.rs"] mod lower_proof_tests; #[cfg(test)] @@ -111,9 +114,6 @@ mod mock; #[cfg(test)] #[path = "tests/tests.rs"] mod tests; -#[cfg(test)] -#[path = "tests/incoming_events_tests.rs"] -mod incoming_events_tests; pub use pallet::*; pub mod default_weights; @@ -228,6 +228,9 @@ pub mod pallet { accepted: bool, eth_event_id: EthEventId, }, + DuplicateEventSubmission { + eth_event_id: EthEventId, + }, } #[pallet::pallet] @@ -308,6 +311,7 @@ pub mod pallet { ExceedsCorroborationLimit, ExceedsFunctionNameLimit, EventAlreadyProcessed, + EventNotProcessed, FunctionEncodingError, FunctionNameError, HandlePublishingResultFailed, @@ -807,12 +811,14 @@ pub mod pallet { match ValidEvents::try_from(&discovered_event.event.event_id.signature) { Some(valid_event) => if active_range.event_types_filter.contains(&valid_event) { - let _ = process_ethereum_event::(&discovered_event.event); + process_ethereum_event::(&discovered_event.event); } else { log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter); }, - None => log::warn!("Unknown Ethereum event signature in range {:?}", &discovered_event.event.event_id.signature) - + None => log::warn!( + "Unknown Ethereum event signature in range {:?}", + &discovered_event.event.event_id.signature + ), }; } @@ -824,28 +830,30 @@ pub mod pallet { } // TODO: re-add the `Accepted and Rejected events - fn process_ethereum_event(event: &EthEvent) -> Result<(), DispatchError> { - // TODO before processing ensure that the event has not already been processed - // Do the check that is not processed via ProcessedEventsChecker - - ensure!(false == T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()), Error::::EventAlreadyProcessed); - let mut event_accepted = false; - - match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { - Ok(_) => { event_accepted = true }, - Err(err) => { - log::error!("💔 Processing ethereum event failed: {:?}", err); - }, - }; + fn process_ethereum_event(event: &EthEvent) { + if T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()) { + log::error!("💔 Event already processed, duplicate event"); - >::deposit_event(Event::::EventProcessed { - accepted: event_accepted, - eth_event_id: event.event_id.clone(), - }); - // Add record of succesful processing via ProcessedEventsChecker - T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); + >::deposit_event(Event::::DuplicateEventSubmission { + eth_event_id: event.event_id.clone(), + }); + } else { + let mut event_accepted = false; - Ok(()) + match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { + Ok(_) => event_accepted = true, + Err(err) => { + log::error!("💔 Processing ethereum event failed: {:?}", err); + }, + }; + + >::deposit_event(Event::::EventProcessed { + accepted: event_accepted, + eth_event_id: event.event_id.clone(), + }); + // Add record of succesful processing via ProcessedEventsChecker + T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); + } } #[pallet::validate_unsigned] diff --git a/pallets/eth-bridge/src/tests/incoming_events_tests.rs b/pallets/eth-bridge/src/tests/incoming_events_tests.rs index f12b74758..0143ee0f7 100644 --- a/pallets/eth-bridge/src/tests/incoming_events_tests.rs +++ b/pallets/eth-bridge/src/tests/incoming_events_tests.rs @@ -22,20 +22,23 @@ fn init_active_range() { ActiveEthereumRange::::put(ActiveEthRange { range: EthBlockRange { start_block: 1, length: 1000 }, partition: 0, - event_types_filter: EthBridgeEventsFilter::try_from(vec![ - ValidEvents::AddedValidator, - ValidEvents::Lifted, - ValidEvents::AvtGrowthLifted, - ValidEvents::AvtLowerClaimed, - ] - .into_iter() - .collect::>()).unwrap() + event_types_filter: EthBridgeEventsFilter::try_from( + vec![ + ValidEvents::AddedValidator, + ValidEvents::Lifted, + ValidEvents::AvtGrowthLifted, + ValidEvents::AvtLowerClaimed, + ] + .into_iter() + .collect::>(), + ) + .unwrap(), }); } mod process_events { - use sp_avn_common::event_types::EthEventId; use super::*; + use sp_avn_common::event_types::EthEventId; // succesfully process the specified ethereum_event #[test] @@ -45,9 +48,20 @@ mod process_events { let context = setup_context(); init_active_range(); - // Two calls needed as upon the first there are not enough votes to pass the condition in lib.rs line 563, to reach the call of process_ethereum_events_partition() - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.mock_event_partition.clone(), context.test_signature.clone())); - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.mock_event_partition.clone(), context.test_signature_two.clone())); + // Two calls needed as upon the first there are not enough votes to pass the condition + // in lib.rs line 563, to reach the call of process_ethereum_events_partition() + assert_ok!(EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author.clone(), + context.mock_event_partition.clone(), + context.test_signature.clone() + )); + assert_ok!(EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author_two.clone(), + context.mock_event_partition.clone(), + context.test_signature_two.clone() + )); assert!(System::events().iter().any(|record| record.event == mock::RuntimeEvent::EthBridge(Event::::EventProcessed { @@ -57,7 +71,6 @@ mod process_events { }); } - // This test should fail processing the ethereum_event and emit the specified event #[test] fn succesful_event_processing_not_accepted() { @@ -65,9 +78,18 @@ mod process_events { ext.execute_with(|| { let context = setup_context(); init_active_range(); - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author.clone(), context.bad_mock_event_partition.clone(), context.test_signature.clone())); - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author_two.clone(), context.bad_mock_event_partition.clone(), context.test_signature.clone())); - + assert_ok!(EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author.clone(), + context.bad_mock_event_partition.clone(), + context.test_signature.clone() + )); + assert_ok!(EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author_two.clone(), + context.bad_mock_event_partition.clone(), + context.test_signature.clone() + )); assert!(System::events().iter().any(|record| record.event == mock::RuntimeEvent::EthBridge(Event::::EventProcessed { @@ -77,15 +99,45 @@ mod process_events { }); } - - // This test should fail on the check T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()), if the event is already in the system + // This test should fail on the check + // T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()), if the event is + // already in the system #[test] fn event_already_processed() { let mut ext = ExtBuilder::build_default().with_validators().as_externality(); ext.execute_with(|| { let context = setup_context(); init_active_range(); - assert_ok!(EthBridge::submit_ethereum_events(RuntimeOrigin::none(), context.author, context.mock_event_partition, context.test_signature)); + assert_ok!(EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author.clone(), + context.mock_event_partition.clone(), + context.test_signature.clone() + )); + assert_ok!(EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author_two.clone(), + context.mock_event_partition.clone(), + context.test_signature_two.clone() + )); + assert_ok!(EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author.clone(), + context.second_mock_event_partition.clone(), + context.test_signature.clone() + )); + assert_ok!(EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author_two.clone(), + context.second_mock_event_partition.clone(), + context.test_signature_two.clone() + )); + assert!(System::events().iter().any(|record| record.event == + mock::RuntimeEvent::EthBridge( + Event::::DuplicateEventSubmission { + eth_event_id: context.eth_event_id.clone(), + } + ))); }); } } diff --git a/pallets/eth-bridge/src/tests/mock.rs b/pallets/eth-bridge/src/tests/mock.rs index 772d1cc09..87d4a81fe 100644 --- a/pallets/eth-bridge/src/tests/mock.rs +++ b/pallets/eth-bridge/src/tests/mock.rs @@ -15,7 +15,9 @@ use sp_core::{ ConstU32, ConstU64, H256, U256, }; use sp_runtime::{ - testing::{TestSignature, TestXt, UintAuthorityId}, traits::{BlakeTwo256, ConvertInto, IdentityLookup}, BuildStorage, DispatchResult, DispatchError, Perbill + testing::{TestSignature, TestXt, UintAuthorityId}, + traits::{BlakeTwo256, ConvertInto, IdentityLookup}, + BuildStorage, DispatchError, DispatchResult, Perbill, }; use sp_staking::offence::OffenceError; use std::{cell::RefCell, convert::From, sync::Arc}; @@ -48,6 +50,7 @@ pub struct Context { pub already_set_eth_tx_hash: H256, pub mock_event_partition: EthereumEventsPartition, pub bad_mock_event_partition: EthereumEventsPartition, + pub second_mock_event_partition: EthereumEventsPartition, pub test_signature: TestSignature, pub test_signature_two: TestSignature, pub test_signature_three: TestSignature, @@ -181,8 +184,28 @@ pub fn create_confirming_author(author_id: u64) -> Author { pub fn create_mock_event_partition(events: EthEvent) -> EthereumEventsPartition { let mut partition: BoundedBTreeSet = BoundedBTreeSet::new(); - partition.try_insert(DiscoveredEvent { event: events.clone(), block: 2 }).unwrap(); - EthereumEventsPartition::new(EthBlockRange {start_block: 1, length: 1000}, 0, false, partition) + partition + .try_insert(DiscoveredEvent { event: events.clone(), block: 2 }) + .unwrap(); + EthereumEventsPartition::new( + EthBlockRange { start_block: 1, length: 1000 }, + 0, + false, + partition, + ) +} + +pub fn create_second_mock_event_partition(events: EthEvent) -> EthereumEventsPartition { + let mut partition: BoundedBTreeSet = BoundedBTreeSet::new(); + partition + .try_insert(DiscoveredEvent { event: events.clone(), block: 2 }) + .unwrap(); + EthereumEventsPartition::new( + EthBlockRange { start_block: 1, length: 1000 }, + 1, + false, + partition, + ) } pub fn lower_is_ready_to_be_claimed(lower_id: &u32) -> bool { @@ -199,14 +222,8 @@ pub fn setup_context() -> Context { key: UintAuthorityId(primary_validator_id), account_id: primary_validator_id, }; - let author_two = Author:: { - key: UintAuthorityId(22), - account_id: 22, - }; - let author_three = Author:: { - key: UintAuthorityId(23), - account_id: 23, - }; + let author_two = Author:: { key: UintAuthorityId(22), account_id: 22 }; + let author_three = Author:: { key: UintAuthorityId(23), account_id: 23 }; let mut confirming_validator_id: u64 = 1; if primary_validator_id == confirming_validator_id { confirming_validator_id += 1 @@ -223,28 +240,51 @@ pub fn setup_context() -> Context { let already_set_eth_tx_hash = H256::from_slice(&[1u8; 32]); let confirmation_signature = ecdsa::Signature::try_from(&[1; 65][0..65]).unwrap(); let finalised_block_vec = Some(hex::encode(10u32.encode()).into()); - let eth_event_id = EthEventId { signature: ValidEvents::Lifted.signature(), transaction_hash: eth_tx_hash }; - let bad_eth_event_id = EthEventId { signature: ValidEvents::Lifted.signature(), transaction_hash: H256::from_slice(&[6u8; 32])}; + let eth_event_id = + EthEventId { signature: ValidEvents::Lifted.signature(), transaction_hash: eth_tx_hash }; + let bad_eth_event_id = EthEventId { + signature: ValidEvents::Lifted.signature(), + transaction_hash: H256::from_slice(&[6u8; 32]), + }; let bad_eth_event = EthEvent { event_id: bad_eth_event_id.clone(), - event_data: sp_avn_common::event_types::EventData::LogLifted( - LiftedData { token_contract: H160::zero(), sender_address: H160::zero(), receiver_address: H256::zero(), amount: 1, nonce: U256::zero() } - ) + event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { + token_contract: H160::zero(), + sender_address: H160::zero(), + receiver_address: H256::zero(), + amount: 1, + nonce: U256::zero(), + }), }; - let mock_event_partition = create_mock_event_partition( - EthEvent { - event_id: eth_event_id.clone(), - event_data: sp_avn_common::event_types::EventData::LogLifted( - LiftedData { token_contract: H160::zero(), sender_address: H160::zero(), receiver_address: H256::zero(), amount: 1, nonce: U256::zero() } - ) - }); + let mock_event_partition = create_mock_event_partition(EthEvent { + event_id: eth_event_id.clone(), + event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { + token_contract: H160::zero(), + sender_address: H160::zero(), + receiver_address: H256::zero(), + amount: 1, + nonce: U256::zero(), + }), + }); let bad_mock_event_partition = create_mock_event_partition(bad_eth_event); + let second_mock_event_partition = create_second_mock_event_partition(EthEvent { + event_id: eth_event_id.clone(), + event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { + token_contract: H160::zero(), + sender_address: H160::zero(), + receiver_address: H256::zero(), + amount: 1, + nonce: U256::zero(), + }), + }); + UintAuthorityId::set_all_keys(vec![UintAuthorityId(primary_validator_id)]); Context { eth_tx_hash, mock_event_partition, + second_mock_event_partition, already_set_eth_tx_hash, test_signature, test_signature_two, diff --git a/pallets/eth-bridge/src/tests/tests.rs b/pallets/eth-bridge/src/tests/tests.rs index 4d3da1a42..ac14b1d61 100644 --- a/pallets/eth-bridge/src/tests/tests.rs +++ b/pallets/eth-bridge/src/tests/tests.rs @@ -803,4 +803,3 @@ fn self_corroborate_fails() { ); }); } - diff --git a/pallets/ethereum-events/src/lib.rs b/pallets/ethereum-events/src/lib.rs index fc586c424..bd04ea0ea 100644 --- a/pallets/ethereum-events/src/lib.rs +++ b/pallets/ethereum-events/src/lib.rs @@ -1587,7 +1587,8 @@ impl Pallet { impl ProcessedEventsChecker for Pallet { fn processed_event_exists(event_id: &EthEventId) -> bool { - return >::contains_key(event_id) || Self::get_pending_event_index(event_id).is_ok() + return >::contains_key(event_id) || + Self::get_pending_event_index(event_id).is_ok() } fn add_processed_event(event_id: &EthEventId, accepted: bool) { diff --git a/primitives/avn-common/src/event_discovery.rs b/primitives/avn-common/src/event_discovery.rs index 98d3878dc..9c3737df2 100644 --- a/primitives/avn-common/src/event_discovery.rs +++ b/primitives/avn-common/src/event_discovery.rs @@ -102,7 +102,12 @@ impl EthereumEventsPartition { blake2_256(&(&self).encode()).into() } - pub fn new(range: EthBlockRange, partition: u16, is_last: bool, data: EthereumEventsSet) -> Self { + pub fn new( + range: EthBlockRange, + partition: u16, + is_last: bool, + data: EthereumEventsSet, + ) -> Self { EthereumEventsPartition { range, partition, is_last, data } } } From 0b52c396771831af91c080675281bd5ab2ee84e2 Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Wed, 15 May 2024 09:33:15 +0100 Subject: [PATCH 20/25] re-add accepted and rejected events --- pallets/eth-bridge/src/lib.rs | 24 ++++++++++++------- .../src/tests/incoming_events_tests.rs | 4 ++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 476f9c602..7acf1b9d6 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -223,8 +223,11 @@ pub mod pallet { BoundedVec<(BoundedVec, BoundedVec), ParamsLimit>, caller_id: BoundedVec, }, - /// EventProcessed(bool, EthEventId) - EventProcessed { + EventProcessingAccepted { + accepted: bool, + eth_event_id: EthEventId, + }, + EventProcessingRejected { accepted: bool, eth_event_id: EthEventId, }, @@ -829,7 +832,6 @@ pub mod pallet { } } - // TODO: re-add the `Accepted and Rejected events fn process_ethereum_event(event: &EthEvent) { if T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()) { log::error!("💔 Event already processed, duplicate event"); @@ -841,16 +843,22 @@ pub mod pallet { let mut event_accepted = false; match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { - Ok(_) => event_accepted = true, + Ok(_) => { + event_accepted = true; + >::deposit_event(Event::::EventProcessingAccepted { + accepted: event_accepted, + eth_event_id: event.event_id.clone(), + }); + } Err(err) => { log::error!("💔 Processing ethereum event failed: {:?}", err); + >::deposit_event(Event::::EventProcessingRejected { + accepted: event_accepted, + eth_event_id: event.event_id.clone(), + }); }, }; - >::deposit_event(Event::::EventProcessed { - accepted: event_accepted, - eth_event_id: event.event_id.clone(), - }); // Add record of succesful processing via ProcessedEventsChecker T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); } diff --git a/pallets/eth-bridge/src/tests/incoming_events_tests.rs b/pallets/eth-bridge/src/tests/incoming_events_tests.rs index 0143ee0f7..0e9c24dfb 100644 --- a/pallets/eth-bridge/src/tests/incoming_events_tests.rs +++ b/pallets/eth-bridge/src/tests/incoming_events_tests.rs @@ -64,7 +64,7 @@ mod process_events { )); assert!(System::events().iter().any(|record| record.event == - mock::RuntimeEvent::EthBridge(Event::::EventProcessed { + mock::RuntimeEvent::EthBridge(Event::::EventProcessingAccepted { accepted: true, eth_event_id: context.eth_event_id.clone(), }))); @@ -92,7 +92,7 @@ mod process_events { )); assert!(System::events().iter().any(|record| record.event == - mock::RuntimeEvent::EthBridge(Event::::EventProcessed { + mock::RuntimeEvent::EthBridge(Event::::EventProcessingRejected { accepted: false, eth_event_id: context.bad_eth_event_id.clone(), }))); From 3a41efd07c5b6bf85c8cb184aebea13503a60ea2 Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Thu, 16 May 2024 17:25:30 +0100 Subject: [PATCH 21/25] fix error handling --- pallets/eth-bridge/src/lib.rs | 80 +++++++++---------- .../src/tests/incoming_events_tests.rs | 27 +++---- pallets/eth-bridge/src/tests/mock.rs | 67 +++++++--------- 3 files changed, 81 insertions(+), 93 deletions(-) diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 7acf1b9d6..5b67d39dd 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -223,12 +223,10 @@ pub mod pallet { BoundedVec<(BoundedVec, BoundedVec), ParamsLimit>, caller_id: BoundedVec, }, - EventProcessingAccepted { - accepted: bool, + EventAccepted { eth_event_id: EthEventId, }, - EventProcessingRejected { - accepted: bool, + EventRejected { eth_event_id: EthEventId, }, DuplicateEventSubmission { @@ -306,6 +304,7 @@ pub mod pallet { pub enum Error { CorroborateCallFailed, DuplicateConfirmation, + DuplicateEventSubmission, EmptyFunctionName, ErrorAssigningSender, EthTxHashAlreadySet, @@ -583,7 +582,7 @@ pub mod pallet { if votes.len() < AVN::::quorum() as usize { EthereumEvents::::insert(&events_partition, votes); } else { - process_ethereum_events_partition::(&active_range, &events_partition); + process_ethereum_events_partition::(&active_range, &events_partition)?; advance_partition::(&active_range, &events_partition); } @@ -807,22 +806,24 @@ pub mod pallet { fn process_ethereum_events_partition( active_range: &ActiveEthRange, partition: &EthereumEventsPartition, - ) { + ) -> Result<(), DispatchError> { // Remove entry from storage. Ignore votes. let _ = EthereumEvents::::take(partition); for discovered_event in partition.events().iter() { match ValidEvents::try_from(&discovered_event.event.event_id.signature) { Some(valid_event) => if active_range.event_types_filter.contains(&valid_event) { - process_ethereum_event::(&discovered_event.event); + process_ethereum_event::(&discovered_event.event)?; } else { log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter); }, - None => log::warn!( - "Unknown Ethereum event signature in range {:?}", - &discovered_event.event.event_id.signature - ), - }; + None => { + log::warn!( + "Unknown Ethereum event signature in range {:?}", + &discovered_event.event.event_id.signature + ); + }, + } } // Cleanup @@ -830,38 +831,37 @@ pub mod pallet { // TODO raise offences log::info!("Collators with invalid votes on ethereum events (range: {:?}, partition: {}): {:?}", partition.range(), partition.partition(), votes); } + + Ok(()) } - fn process_ethereum_event(event: &EthEvent) { - if T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()) { - log::error!("💔 Event already processed, duplicate event"); + fn process_ethereum_event(event: &EthEvent) -> Result<(), DispatchError> { + ensure!( + false == T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()), + Error::::EventAlreadyProcessed + ); - >::deposit_event(Event::::DuplicateEventSubmission { - eth_event_id: event.event_id.clone(), - }); - } else { - let mut event_accepted = false; - - match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { - Ok(_) => { - event_accepted = true; - >::deposit_event(Event::::EventProcessingAccepted { - accepted: event_accepted, - eth_event_id: event.event_id.clone(), - }); - } - Err(err) => { - log::error!("💔 Processing ethereum event failed: {:?}", err); - >::deposit_event(Event::::EventProcessingRejected { - accepted: event_accepted, - eth_event_id: event.event_id.clone(), - }); - }, - }; + let mut event_accepted = false; - // Add record of succesful processing via ProcessedEventsChecker - T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); - } + match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { + Ok(_) => { + event_accepted = true; + >::deposit_event(Event::::EventAccepted { + eth_event_id: event.event_id.clone(), + }); + }, + Err(err) => { + log::error!("💔 Processing ethereum event failed: {:?}", err); + >::deposit_event(Event::::EventRejected { + eth_event_id: event.event_id.clone(), + }); + }, + }; + + // Add record of succesful processing via ProcessedEventsChecker + T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); + + Ok(()) } #[pallet::validate_unsigned] diff --git a/pallets/eth-bridge/src/tests/incoming_events_tests.rs b/pallets/eth-bridge/src/tests/incoming_events_tests.rs index 0e9c24dfb..3f9b7e39e 100644 --- a/pallets/eth-bridge/src/tests/incoming_events_tests.rs +++ b/pallets/eth-bridge/src/tests/incoming_events_tests.rs @@ -64,8 +64,7 @@ mod process_events { )); assert!(System::events().iter().any(|record| record.event == - mock::RuntimeEvent::EthBridge(Event::::EventProcessingAccepted { - accepted: true, + mock::RuntimeEvent::EthBridge(Event::::EventAccepted { eth_event_id: context.eth_event_id.clone(), }))); }); @@ -92,8 +91,7 @@ mod process_events { )); assert!(System::events().iter().any(|record| record.event == - mock::RuntimeEvent::EthBridge(Event::::EventProcessingRejected { - accepted: false, + mock::RuntimeEvent::EthBridge(Event::::EventRejected { eth_event_id: context.bad_eth_event_id.clone(), }))); }); @@ -126,18 +124,15 @@ mod process_events { context.second_mock_event_partition.clone(), context.test_signature.clone() )); - assert_ok!(EthBridge::submit_ethereum_events( - RuntimeOrigin::none(), - context.author_two.clone(), - context.second_mock_event_partition.clone(), - context.test_signature_two.clone() - )); - assert!(System::events().iter().any(|record| record.event == - mock::RuntimeEvent::EthBridge( - Event::::DuplicateEventSubmission { - eth_event_id: context.eth_event_id.clone(), - } - ))); + assert_noop!( + EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author_two.clone(), + context.second_mock_event_partition.clone(), + context.test_signature_two.clone() + ), + Error::::EventAlreadyProcessed + ); }); } } diff --git a/pallets/eth-bridge/src/tests/mock.rs b/pallets/eth-bridge/src/tests/mock.rs index 87d4a81fe..0c03a28a0 100644 --- a/pallets/eth-bridge/src/tests/mock.rs +++ b/pallets/eth-bridge/src/tests/mock.rs @@ -182,27 +182,14 @@ pub fn create_confirming_author(author_id: u64) -> Author { Author:: { key: UintAuthorityId(author_id), account_id: author_id } } -pub fn create_mock_event_partition(events: EthEvent) -> EthereumEventsPartition { +pub fn create_mock_event_partition(events: EthEvent, part: u16) -> EthereumEventsPartition { let mut partition: BoundedBTreeSet = BoundedBTreeSet::new(); partition .try_insert(DiscoveredEvent { event: events.clone(), block: 2 }) .unwrap(); EthereumEventsPartition::new( EthBlockRange { start_block: 1, length: 1000 }, - 0, - false, - partition, - ) -} - -pub fn create_second_mock_event_partition(events: EthEvent) -> EthereumEventsPartition { - let mut partition: BoundedBTreeSet = BoundedBTreeSet::new(); - partition - .try_insert(DiscoveredEvent { event: events.clone(), block: 2 }) - .unwrap(); - EthereumEventsPartition::new( - EthBlockRange { start_block: 1, length: 1000 }, - 1, + part, false, partition, ) @@ -256,28 +243,34 @@ pub fn setup_context() -> Context { nonce: U256::zero(), }), }; - let mock_event_partition = create_mock_event_partition(EthEvent { - event_id: eth_event_id.clone(), - event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { - token_contract: H160::zero(), - sender_address: H160::zero(), - receiver_address: H256::zero(), - amount: 1, - nonce: U256::zero(), - }), - }); - let bad_mock_event_partition = create_mock_event_partition(bad_eth_event); - - let second_mock_event_partition = create_second_mock_event_partition(EthEvent { - event_id: eth_event_id.clone(), - event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { - token_contract: H160::zero(), - sender_address: H160::zero(), - receiver_address: H256::zero(), - amount: 1, - nonce: U256::zero(), - }), - }); + let mock_event_partition = create_mock_event_partition( + EthEvent { + event_id: eth_event_id.clone(), + event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { + token_contract: H160::zero(), + sender_address: H160::zero(), + receiver_address: H256::zero(), + amount: 1, + nonce: U256::zero(), + }), + }, + 0, + ); + let bad_mock_event_partition = create_mock_event_partition(bad_eth_event, 0); + + let second_mock_event_partition = create_mock_event_partition( + EthEvent { + event_id: eth_event_id.clone(), + event_data: sp_avn_common::event_types::EventData::LogLifted(LiftedData { + token_contract: H160::zero(), + sender_address: H160::zero(), + receiver_address: H256::zero(), + amount: 1, + nonce: U256::zero(), + }), + }, + 1, + ); UintAuthorityId::set_all_keys(vec![UintAuthorityId(primary_validator_id)]); From e19db3ba5ab289ce273cb01faeddcc8541526386 Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Tue, 21 May 2024 15:55:48 +0100 Subject: [PATCH 22/25] revert error handling, do not throw errors --- pallets/avn/src/lib.rs | 1 - pallets/eth-bridge/src/lib.rs | 60 +++++++++---------- .../src/tests/incoming_events_tests.rs | 19 +++--- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/pallets/avn/src/lib.rs b/pallets/avn/src/lib.rs index 6faa4ff21..1fba0a605 100644 --- a/pallets/avn/src/lib.rs +++ b/pallets/avn/src/lib.rs @@ -704,7 +704,6 @@ impl Enforcer for () { pub trait ProcessedEventsChecker { fn processed_event_exists(event_id: &EthEventId) -> bool; - fn add_processed_event(event_id: &EthEventId, accepted: bool); } diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 5b67d39dd..6030bdf72 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -78,7 +78,7 @@ use sp_application_crypto::RuntimeAppPublic; use sp_avn_common::{ bounds::MaximumValidatorsBound, event_discovery::*, - event_types::{ValidEvents, Validator}, + event_types::{Validator}, }; use sp_core::{ecdsa, ConstU32, H160, H256}; use sp_io::hashing::keccak_256; @@ -582,7 +582,7 @@ pub mod pallet { if votes.len() < AVN::::quorum() as usize { EthereumEvents::::insert(&events_partition, votes); } else { - process_ethereum_events_partition::(&active_range, &events_partition)?; + process_ethereum_events_partition::(&active_range, &events_partition); advance_partition::(&active_range, &events_partition); } @@ -806,14 +806,14 @@ pub mod pallet { fn process_ethereum_events_partition( active_range: &ActiveEthRange, partition: &EthereumEventsPartition, - ) -> Result<(), DispatchError> { + ) { // Remove entry from storage. Ignore votes. let _ = EthereumEvents::::take(partition); for discovered_event in partition.events().iter() { match ValidEvents::try_from(&discovered_event.event.event_id.signature) { Some(valid_event) => if active_range.event_types_filter.contains(&valid_event) { - process_ethereum_event::(&discovered_event.event)?; + process_ethereum_event::(&discovered_event.event); } else { log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter); }, @@ -831,37 +831,35 @@ pub mod pallet { // TODO raise offences log::info!("Collators with invalid votes on ethereum events (range: {:?}, partition: {}): {:?}", partition.range(), partition.partition(), votes); } - - Ok(()) } - fn process_ethereum_event(event: &EthEvent) -> Result<(), DispatchError> { - ensure!( - false == T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()), - Error::::EventAlreadyProcessed - ); - - let mut event_accepted = false; - - match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { - Ok(_) => { - event_accepted = true; - >::deposit_event(Event::::EventAccepted { - eth_event_id: event.event_id.clone(), - }); - }, - Err(err) => { - log::error!("💔 Processing ethereum event failed: {:?}", err); - >::deposit_event(Event::::EventRejected { - eth_event_id: event.event_id.clone(), - }); - }, - }; + fn process_ethereum_event(event: &EthEvent) { + if false == T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()) { + let mut event_accepted = false; - // Add record of succesful processing via ProcessedEventsChecker - T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); + match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { + Ok(_) => { + event_accepted = true; + >::deposit_event(Event::::EventAccepted { + eth_event_id: event.event_id.clone(), + }); + }, + Err(err) => { + log::error!("💔 Processing ethereum event failed: {:?}", err); + >::deposit_event(Event::::EventRejected { + eth_event_id: event.event_id.clone(), + }); + }, + }; - Ok(()) + // Add record of succesful processing via ProcessedEventsChecker + T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); + } else { + log::error!("💔 Duplicate Event Submission"); + >::deposit_event(Event::::DuplicateEventSubmission { + eth_event_id: event.event_id.clone(), + }); + } } #[pallet::validate_unsigned] diff --git a/pallets/eth-bridge/src/tests/incoming_events_tests.rs b/pallets/eth-bridge/src/tests/incoming_events_tests.rs index 3f9b7e39e..896c5f37c 100644 --- a/pallets/eth-bridge/src/tests/incoming_events_tests.rs +++ b/pallets/eth-bridge/src/tests/incoming_events_tests.rs @@ -124,15 +124,16 @@ mod process_events { context.second_mock_event_partition.clone(), context.test_signature.clone() )); - assert_noop!( - EthBridge::submit_ethereum_events( - RuntimeOrigin::none(), - context.author_two.clone(), - context.second_mock_event_partition.clone(), - context.test_signature_two.clone() - ), - Error::::EventAlreadyProcessed - ); + assert_ok!(EthBridge::submit_ethereum_events( + RuntimeOrigin::none(), + context.author_two.clone(), + context.second_mock_event_partition.clone(), + context.test_signature_two.clone() + )); + assert!(System::events().iter().any(|record| record.event == + mock::RuntimeEvent::EthBridge(Event::::DuplicateEventSubmission { + eth_event_id: context.eth_event_id.clone(), + }))); }); } } From 4b74809faf44e3f2181cea8496b8180eab997bb4 Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Tue, 21 May 2024 16:26:54 +0100 Subject: [PATCH 23/25] revert again --- pallets/eth-bridge/src/lib.rs | 60 ++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 6030bdf72..32d935000 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -813,7 +813,15 @@ pub mod pallet { match ValidEvents::try_from(&discovered_event.event.event_id.signature) { Some(valid_event) => if active_range.event_types_filter.contains(&valid_event) { - process_ethereum_event::(&discovered_event.event); + match process_ethereum_event::(&discovered_event.event) { + Ok(_) => {}, + Err(_) => { + log::error!("💔 Duplicate Event Submission"); + >::deposit_event(Event::::DuplicateEventSubmission { + eth_event_id: discovered_event.event.event_id.clone(), + }); + }, + } } else { log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter); }, @@ -833,33 +841,33 @@ pub mod pallet { } } - fn process_ethereum_event(event: &EthEvent) { - if false == T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()) { - let mut event_accepted = false; + fn process_ethereum_event(event: &EthEvent) -> Result<(), DispatchError> { + ensure!( + false == T::ProcessedEventsChecker::processed_event_exists(&event.event_id.clone()), + Error::::EventAlreadyProcessed + ); - match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { - Ok(_) => { - event_accepted = true; - >::deposit_event(Event::::EventAccepted { - eth_event_id: event.event_id.clone(), - }); - }, - Err(err) => { - log::error!("💔 Processing ethereum event failed: {:?}", err); - >::deposit_event(Event::::EventRejected { - eth_event_id: event.event_id.clone(), - }); - }, - }; + let mut event_accepted = false; - // Add record of succesful processing via ProcessedEventsChecker - T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); - } else { - log::error!("💔 Duplicate Event Submission"); - >::deposit_event(Event::::DuplicateEventSubmission { - eth_event_id: event.event_id.clone(), - }); - } + match T::BridgeInterfaceNotification::on_incoming_event_processed(&event) { + Ok(_) => { + event_accepted = true; + >::deposit_event(Event::::EventAccepted { + eth_event_id: event.event_id.clone(), + }); + }, + Err(err) => { + log::error!("💔 Processing ethereum event failed: {:?}", err); + >::deposit_event(Event::::EventRejected { + eth_event_id: event.event_id.clone(), + }); + }, + }; + + // Add record of succesful processing via ProcessedEventsChecker + T::ProcessedEventsChecker::add_processed_event(&event.event_id.clone(), event_accepted); + + Ok(()) } #[pallet::validate_unsigned] From f5271167d7d2e449cdbc67f487876d00794166be Mon Sep 17 00:00:00 2001 From: Michael Brozhko Date: Tue, 21 May 2024 16:27:21 +0100 Subject: [PATCH 24/25] small tests fix --- pallets/eth-bridge/src/tests/incoming_events_tests.rs | 2 +- pallets/eth-bridge/src/tests/mock.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/eth-bridge/src/tests/incoming_events_tests.rs b/pallets/eth-bridge/src/tests/incoming_events_tests.rs index 896c5f37c..1355323fa 100644 --- a/pallets/eth-bridge/src/tests/incoming_events_tests.rs +++ b/pallets/eth-bridge/src/tests/incoming_events_tests.rs @@ -8,7 +8,7 @@ use frame_support::{ use sp_runtime::{testing::UintAuthorityId, DispatchError}; pub extern crate alloc; use alloc::collections::BTreeSet; -use sp_avn_common::event_discovery::EthBridgeEventsFilter; +use sp_avn_common::{event_discovery::EthBridgeEventsFilter, event_types::ValidEvents}; const ROOT_HASH: &str = "30b83f0d722d1d4308ab4660a72dbaf0a7392d5674eca3cd21d57256d42df7a0"; const REWARDS: &[u8] = b"15043665996000000000"; diff --git a/pallets/eth-bridge/src/tests/mock.rs b/pallets/eth-bridge/src/tests/mock.rs index 0c03a28a0..f7816f460 100644 --- a/pallets/eth-bridge/src/tests/mock.rs +++ b/pallets/eth-bridge/src/tests/mock.rs @@ -6,7 +6,7 @@ use frame_system as system; use pallet_avn::{testing::U64To32BytesConverter, EthereumPublicKeyChecker}; use pallet_session as session; use parking_lot::RwLock; -use sp_avn_common::event_types::{EthEvent, EthEventId, LiftedData}; +use sp_avn_common::event_types::{EthEvent, EthEventId, LiftedData, ValidEvents}; use sp_core::{ offchain::{ testing::{OffchainState, PoolState, TestOffchainExt, TestTransactionPoolExt}, From 66e01430ec1be31a10df6dcff9af35101361adec Mon Sep 17 00:00:00 2001 From: Michael <62653655+MBrozhko34@users.noreply.github.com> Date: Wed, 22 May 2024 10:59:10 +0100 Subject: [PATCH 25/25] Update pallets/eth-bridge/src/lib.rs Co-authored-by: Thanos Doukoudakis <56822898+thadouk@users.noreply.github.com> Signed-off-by: Michael <62653655+MBrozhko34@users.noreply.github.com> --- pallets/eth-bridge/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pallets/eth-bridge/src/lib.rs b/pallets/eth-bridge/src/lib.rs index 32d935000..b4461c9cf 100644 --- a/pallets/eth-bridge/src/lib.rs +++ b/pallets/eth-bridge/src/lib.rs @@ -813,14 +813,11 @@ pub mod pallet { match ValidEvents::try_from(&discovered_event.event.event_id.signature) { Some(valid_event) => if active_range.event_types_filter.contains(&valid_event) { - match process_ethereum_event::(&discovered_event.event) { - Ok(_) => {}, - Err(_) => { + if process_ethereum_event::(&discovered_event.event).is_err() { log::error!("💔 Duplicate Event Submission"); >::deposit_event(Event::::DuplicateEventSubmission { eth_event_id: discovered_event.event.event_id.clone(), }); - }, } } else { log::warn!("Ethereum event signature ({:?}) included in approved range ({:?}), but not part of the expected ones {:?}", &discovered_event.event.event_id.signature, active_range.range, active_range.event_types_filter);