diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b7faad5ab..c1b8a36957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Description of the upcoming release here. ### Added +- [#1770](https://github.com/FuelLabs/fuel-core/pull/1770): Add the new L1 event type for forced transactions. - [#1767](https://github.com/FuelLabs/fuel-core/pull/1767): Added consensus parameters version and state transition version to the `ApplicationHeader` to describe what was used to produce this block. - [#1760](https://github.com/FuelLabs/fuel-core/pull/1760): Added tests to verify that the network operates with a custom chain id and base asset id. - [#1752](https://github.com/FuelLabs/fuel-core/pull/1752): Add `ProducerGasPrice` trait that the `Producer` depends on to get the gas price for the block. diff --git a/bin/fuel-core/src/cli/snapshot.rs b/bin/fuel-core/src/cli/snapshot.rs index 59edfd90f4..70cf2183a4 100644 --- a/bin/fuel-core/src/cli/snapshot.rs +++ b/bin/fuel-core/src/cli/snapshot.rs @@ -288,7 +288,7 @@ mod tests { CompressedCoinV1, }, contract::ContractUtxoInfo, - message::{ + relayer::message::{ Message, MessageV1, }, diff --git a/crates/chain-config/src/config/message.rs b/crates/chain-config/src/config/message.rs index 15d2429642..34f1a573d3 100644 --- a/crates/chain-config/src/config/message.rs +++ b/crates/chain-config/src/config/message.rs @@ -5,7 +5,7 @@ use crate::{ use fuel_core_storage::MerkleRoot; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, - entities::message::{ + entities::relayer::message::{ Message, MessageV1, }, diff --git a/crates/fuel-core/src/coins_query.rs b/crates/fuel-core/src/coins_query.rs index 1f89146941..ecbcaa31ba 100644 --- a/crates/fuel-core/src/coins_query.rs +++ b/crates/fuel-core/src/coins_query.rs @@ -263,7 +263,7 @@ mod tests { Coin, CompressedCoin, }, - message::{ + relayer::message::{ Message, MessageV1, }, diff --git a/crates/fuel-core/src/database/block.rs b/crates/fuel-core/src/database/block.rs index 29a2e7401e..dcccc02993 100644 --- a/crates/fuel-core/src/database/block.rs +++ b/crates/fuel-core/src/database/block.rs @@ -32,7 +32,7 @@ use fuel_core_types::{ }, primitives::BlockId, }, - entities::message::MerkleProof, + entities::relayer::message::MerkleProof, fuel_merkle::binary::MerkleTree, fuel_types::BlockHeight, }; diff --git a/crates/fuel-core/src/database/message.rs b/crates/fuel-core/src/database/message.rs index 64ddda0569..dbcca1dbfa 100644 --- a/crates/fuel-core/src/database/message.rs +++ b/crates/fuel-core/src/database/message.rs @@ -22,7 +22,7 @@ use fuel_core_storage::{ Result as StorageResult, }; use fuel_core_types::{ - entities::message::Message, + entities::relayer::message::Message, fuel_types::{ Address, Nonce, diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 1bec755dc3..3773bd830a 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -41,7 +41,7 @@ mod tests { }, entities::{ coins::coin::CompressedCoin, - message::{ + relayer::message::{ Message, MessageV1, }, @@ -2954,7 +2954,10 @@ mod tests { }, StorageAsMut, }; - use fuel_core_types::fuel_merkle::binary::root_calculator::MerkleRootCalculator; + use fuel_core_types::{ + entities::RelayedTransaction, + fuel_merkle::binary::root_calculator::MerkleRootCalculator, + }; fn database_with_genesis_block(da_block_height: u64) -> Database { let mut db = Database::default(); @@ -2975,6 +2978,16 @@ mod tests { .expect("Should insert event"); } + fn add_events_to_relayer( + db: &mut Database, + da_height: DaBlockHeight, + events: &[Event], + ) { + db.storage::() + .insert(&da_height, events) + .expect("Should insert event"); + } + fn add_messages_to_relayer(db: &mut Database, relayer_da_height: u64) { for da_height in 0..=relayer_da_height { let mut message = Message::default(); @@ -3112,11 +3125,23 @@ mod tests { let relayer_da_height = 10u64; let mut root_calculator = MerkleRootCalculator::new(); for da_height in (genesis_da_height + 1)..=relayer_da_height { + // message let mut message = Message::default(); message.set_da_height(da_height.into()); message.set_nonce(da_height.into()); root_calculator.push(message.id().as_ref()); - add_message_to_relayer(&mut relayer_db, message); + // transaction + let mut transaction = RelayedTransaction::default(); + transaction.set_da_height(da_height.into()); + transaction.set_max_gas(da_height); + transaction.set_serialized_transaction(da_height.to_be_bytes().to_vec()); + root_calculator.push(Bytes32::from(transaction.id()).as_ref()); + // add events to relayer + add_events_to_relayer( + &mut relayer_db, + da_height.into(), + &[message.into(), transaction.into()], + ); } let producer = create_relayer_executor(on_chain_db, relayer_db); let block = test_block(block_height.into(), relayer_da_height.into(), 0); diff --git a/crates/fuel-core/src/graphql_api/database.rs b/crates/fuel-core/src/graphql_api/database.rs index be15d7ad3c..35706a6141 100644 --- a/crates/fuel-core/src/graphql_api/database.rs +++ b/crates/fuel-core/src/graphql_api/database.rs @@ -33,7 +33,7 @@ use fuel_core_types::{ DaBlockHeight, }, }, - entities::message::{ + entities::relayer::message::{ MerkleProof, Message, }, diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index f3df9101aa..53662e3d2c 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -27,7 +27,7 @@ use fuel_core_types::{ DaBlockHeight, }, }, - entities::message::{ + entities::relayer::message::{ MerkleProof, Message, }, diff --git a/crates/fuel-core/src/query/message.rs b/crates/fuel-core/src/query/message.rs index 8820a1521e..b9e527272b 100644 --- a/crates/fuel-core/src/query/message.rs +++ b/crates/fuel-core/src/query/message.rs @@ -28,7 +28,7 @@ use fuel_core_storage::{ }; use fuel_core_types::{ blockchain::block::CompressedBlock, - entities::message::{ + entities::relayer::message::{ MerkleProof, Message, MessageProof, diff --git a/crates/fuel-core/src/query/message/test.rs b/crates/fuel-core/src/query/message/test.rs index 33719da25a..8651b09ec8 100644 --- a/crates/fuel-core/src/query/message/test.rs +++ b/crates/fuel-core/src/query/message/test.rs @@ -6,7 +6,7 @@ use fuel_core_types::{ ConsensusHeader, PartialBlockHeader, }, - entities::message::MerkleProof, + entities::relayer::message::MerkleProof, fuel_tx::{ Script, Transaction, diff --git a/crates/fuel-core/src/schema/message.rs b/crates/fuel-core/src/schema/message.rs index 43cdd8379f..f2dfc16639 100644 --- a/crates/fuel-core/src/schema/message.rs +++ b/crates/fuel-core/src/schema/message.rs @@ -33,7 +33,7 @@ use async_graphql::{ }; use fuel_core_types::entities; -pub struct Message(pub(crate) entities::message::Message); +pub struct Message(pub(crate) entities::relayer::message::Message); #[Object] impl Message { @@ -158,7 +158,7 @@ impl MessageQuery { Ok(status.into()) } } -pub struct MerkleProof(pub(crate) entities::message::MerkleProof); +pub struct MerkleProof(pub(crate) entities::relayer::message::MerkleProof); #[Object] impl MerkleProof { @@ -176,7 +176,7 @@ impl MerkleProof { } } -pub struct MessageProof(pub(crate) entities::message::MessageProof); +pub struct MessageProof(pub(crate) entities::relayer::message::MessageProof); #[Object] impl MessageProof { @@ -217,19 +217,19 @@ impl MessageProof { } } -impl From for Message { - fn from(message: entities::message::Message) -> Self { +impl From for Message { + fn from(message: entities::relayer::message::Message) -> Self { Message(message) } } -impl From for MerkleProof { - fn from(proof: entities::message::MerkleProof) -> Self { +impl From for MerkleProof { + fn from(proof: entities::relayer::message::MerkleProof) -> Self { MerkleProof(proof) } } -pub struct MessageStatus(pub(crate) entities::message::MessageStatus); +pub struct MessageStatus(pub(crate) entities::relayer::message::MessageStatus); #[derive(Enum, Copy, Clone, Eq, PartialEq)] enum MessageState { @@ -242,15 +242,15 @@ enum MessageState { impl MessageStatus { async fn state(&self) -> MessageState { match self.0.state { - entities::message::MessageState::Unspent => MessageState::Unspent, - entities::message::MessageState::Spent => MessageState::Spent, - entities::message::MessageState::NotFound => MessageState::NotFound, + entities::relayer::message::MessageState::Unspent => MessageState::Unspent, + entities::relayer::message::MessageState::Spent => MessageState::Spent, + entities::relayer::message::MessageState::NotFound => MessageState::NotFound, } } } -impl From for MessageStatus { - fn from(status: entities::message::MessageStatus) -> Self { +impl From for MessageStatus { + fn from(status: entities::relayer::message::MessageStatus) -> Self { MessageStatus(status) } } diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index 0551884f9d..ee00e2f4eb 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -26,7 +26,7 @@ use fuel_core_txpool::{ types::TxId, }; use fuel_core_types::{ - entities::message::MerkleProof, + entities::relayer::message::MerkleProof, fuel_tx::{ Bytes32, Transaction, diff --git a/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs b/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs index 1662133ecc..f550abf28c 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api/on_chain.rs @@ -27,7 +27,7 @@ use fuel_core_types::{ block::CompressedBlock, primitives::DaBlockHeight, }, - entities::message::Message, + entities::relayer::message::Message, fuel_tx::AssetId, fuel_types::{ BlockHeight, diff --git a/crates/fuel-core/src/service/adapters/txpool.rs b/crates/fuel-core/src/service/adapters/txpool.rs index 6b243a3b91..8f2aa4c6a0 100644 --- a/crates/fuel-core/src/service/adapters/txpool.rs +++ b/crates/fuel-core/src/service/adapters/txpool.rs @@ -24,7 +24,7 @@ use fuel_core_txpool::{ use fuel_core_types::{ entities::{ coins::coin::CompressedCoin, - message::Message, + relayer::message::Message, }, fuel_tx::{ Transaction, diff --git a/crates/fuel-core/src/service/genesis.rs b/crates/fuel-core/src/service/genesis.rs index 3546761493..06466e0436 100644 --- a/crates/fuel-core/src/service/genesis.rs +++ b/crates/fuel-core/src/service/genesis.rs @@ -52,7 +52,7 @@ use fuel_core_types::{ entities::{ coins::coin::Coin, contract::ContractUtxoInfo, - message::Message, + relayer::message::Message, }, fuel_tx::Contract, fuel_types::{ diff --git a/crates/fuel-core/src/service/genesis/off_chain.rs b/crates/fuel-core/src/service/genesis/off_chain.rs index ce39163ffc..f8a1039102 100644 --- a/crates/fuel-core/src/service/genesis/off_chain.rs +++ b/crates/fuel-core/src/service/genesis/off_chain.rs @@ -21,7 +21,7 @@ use fuel_core_txpool::types::TxId; use fuel_core_types::{ entities::{ coins::coin::CompressedCoin, - message::Message, + Message, }, fuel_tx::{ Transaction, diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index e83e003c34..208fb371d4 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -750,6 +750,9 @@ where .events .push(ExecutorEvent::MessageImported(message)); } + Event::Transaction(_) => { + // TODO: implement handling of forced transactions in a later PR + } } } } diff --git a/crates/services/relayer/src/abi.rs b/crates/services/relayer/src/abi.rs index 82ea7c57ab..0347a7fb82 100644 --- a/crates/services/relayer/src/abi.rs +++ b/crates/services/relayer/src/abi.rs @@ -8,6 +8,7 @@ pub mod bridge { MessageSent, r#"[ event MessageSent(bytes32 indexed sender, bytes32 indexed recipient, uint256 indexed nonce, uint64 amount, bytes data) + event Transaction(uint64 max_gas, bytes canonically_serialized_tx) ]"#, ); } diff --git a/crates/services/relayer/src/config.rs b/crates/services/relayer/src/config.rs index 3651dfabbf..47673f0cc9 100644 --- a/crates/services/relayer/src/config.rs +++ b/crates/services/relayer/src/config.rs @@ -13,6 +13,9 @@ use std::{ pub(crate) static ETH_LOG_MESSAGE: Lazy = Lazy::new(crate::abi::bridge::MessageSentFilter::signature); +pub(crate) static ETH_FORCED_TX: Lazy = + Lazy::new(crate::abi::bridge::TransactionFilter::signature); + // TODO: Move settlement fields into `ChainConfig` because it is part of the consensus. #[derive(Clone, Debug)] /// Configuration settings for the Relayer. diff --git a/crates/services/relayer/src/log.rs b/crates/services/relayer/src/log.rs index 95dfc0cf4d..c7dae87630 100644 --- a/crates/services/relayer/src/log.rs +++ b/crates/services/relayer/src/log.rs @@ -10,9 +10,15 @@ use ethers_core::{ }; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, - entities::message::{ - Message, - MessageV1, + entities::{ + relayer::{ + message::{ + Message, + MessageV1, + }, + transaction::RelayedTransactionV1, + }, + RelayedTransaction, }, fuel_types::{ Address, @@ -32,6 +38,13 @@ pub struct MessageLog { pub da_height: DaBlockHeight, } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct TransactionLog { + pub max_gas: u64, + pub serialized_transaction: Vec, + pub da_height: DaBlockHeight, +} + impl From<&MessageLog> for Message { fn from(message: &MessageLog) -> Self { MessageV1 { @@ -46,10 +59,22 @@ impl From<&MessageLog> for Message { } } +impl From for RelayedTransaction { + fn from(transaction: TransactionLog) -> Self { + RelayedTransactionV1 { + max_gas: transaction.max_gas, + serialized_transaction: transaction.serialized_transaction, + da_height: transaction.da_height, + } + .into() + } +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum EthEventLog { // Bridge message from da side Message(MessageLog), + Transaction(TransactionLog), Ignored, } @@ -63,6 +88,7 @@ impl TryFrom<&Log> for EthEventLog { let log = match log.topics[0] { n if n == *config::ETH_LOG_MESSAGE => { + // event has 3 indexed fields, so it should have 4 topics if log.topics.len() != 4 { return Err(anyhow!("Malformed topics for Message")) } @@ -97,6 +123,36 @@ impl TryFrom<&Log> for EthEventLog { ), }) } + n if n == *config::ETH_FORCED_TX => { + // event has no indexed fields, so there is only 1 topic + if log.topics.len() != 1 { + return Err(anyhow!("Malformed topics for forced Transaction")) + } + + let raw_log = RawLog { + topics: log.topics.clone(), + data: log.data.to_vec(), + }; + + let event = abi::bridge::TransactionFilter::decode_log(&raw_log) + .map_err(anyhow::Error::msg)?; + + let max_gas = event.max_gas; + let serialized_transaction = event.canonically_serialized_tx; + + Self::Transaction(TransactionLog { + max_gas, + serialized_transaction: serialized_transaction.to_vec(), + // Safety: logs without block numbers are rejected by + // FinalizationQueue::append_eth_log before the conversion to EthEventLog happens. + // If block_number is none, that means the log is pending. + da_height: DaBlockHeight::from( + log.block_number + .ok_or(anyhow!("Log missing block height"))? + .as_u64(), + ), + }) + } _ => Self::Ignored, }; diff --git a/crates/services/relayer/src/mock_db.rs b/crates/services/relayer/src/mock_db.rs index a544bde0af..7e307a6a0c 100644 --- a/crates/services/relayer/src/mock_db.rs +++ b/crates/services/relayer/src/mock_db.rs @@ -7,7 +7,11 @@ use fuel_core_storage::{ }; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, - entities::message::Message, + entities::{ + relayer::transaction::RelayedTransactionId, + Message, + RelayedTransaction, + }, fuel_types::Nonce, services::relayer::Event, }; @@ -25,6 +29,8 @@ use std::{ #[derive(Default)] pub struct Data { pub messages: BTreeMap>, + pub transactions: + BTreeMap>, pub finalized_da_height: Option, } @@ -46,6 +52,18 @@ impl MockDb { .iter() .find_map(|(_, map)| map.get(id).cloned()) } + + pub fn get_transaction( + &self, + id: &RelayedTransactionId, + ) -> Option { + self.data + .lock() + .unwrap() + .transactions + .iter() + .find_map(|(_, map)| map.get(id).cloned()) + } } impl RelayerDb for MockDb { @@ -63,6 +81,12 @@ impl RelayerDb for MockDb { .or_default() .insert(*message.id(), message.clone()); } + Event::Transaction(transaction) => { + m.transactions + .entry(transaction.da_height()) + .or_default() + .insert(transaction.id(), transaction.clone()); + } } } let max = m.finalized_da_height.get_or_insert(0u64.into()); diff --git a/crates/services/relayer/src/ports/tests.rs b/crates/services/relayer/src/ports/tests.rs index 57a2757124..1a4b3aa3e8 100644 --- a/crates/services/relayer/src/ports/tests.rs +++ b/crates/services/relayer/src/ports/tests.rs @@ -14,7 +14,13 @@ use fuel_core_storage::test_helpers::{ MockBasic, MockStorage, }; -use fuel_core_types::entities::message::Message; +use fuel_core_types::{ + entities::{ + Message, + RelayedTransaction, + }, + services::relayer::Event, +}; use std::borrow::Cow; use test_case::test_case; @@ -119,58 +125,85 @@ fn insert_always_raises_da_height_monotonically() { } #[test] -fn insert_fails_for_messages_with_different_height() { - // Given - let last_height = 1u64; - let events: Vec<_> = (0..=last_height) - .map(|i| { - let mut message = Message::default(); - message.set_da_height(i.into()); - message.set_amount(i); - message.into() - }) - .collect(); - - let mut db = MockDatabase { - data: Box::new(DBTx::default), - storage: Default::default(), - }; - - // When - let result = db.insert_events(&last_height.into(), &events); +fn insert_fails_for_events_with_different_height() { + fn inner_test Event>(f: F) { + // Given + let last_height = 1u64; + let events: Vec<_> = (0..=last_height).map(f).collect(); + + let mut db = MockDatabase { + data: Box::new(DBTx::default), + storage: Default::default(), + }; + + // When + let result = db.insert_events(&last_height.into(), &events); + + // Then + let err = result.expect_err( + "Should return error since DA message heights are different between each other", + ); + assert!(err.to_string().contains("Invalid da height")); + } - // Then - let err = result.expect_err( - "Should return error since DA message heights are different between each other", - ); - assert!(err.to_string().contains("Invalid da height")); + // test with messages + inner_test(|i| { + let mut message = Message::default(); + message.set_da_height(i.into()); + message.set_amount(i); + message.into() + }); + + // test with forced transactions + inner_test(|i| { + let mut transaction = RelayedTransaction::default(); + transaction.set_da_height(i.into()); + transaction.set_max_gas(i); + transaction.into() + }) } #[test] -fn insert_fails_for_messages_same_height_but_on_different_height() { - // Given +fn insert_fails_for_events_same_height_but_on_different_height() { + fn inner_test Event>(f: F, last_height: u64) { + // Given + let events: Vec<_> = (0..=last_height).map(f).collect(); + + // When + let mut db = MockDatabase { + data: Box::new(DBTx::default), + storage: Default::default(), + }; + let next_height = last_height + 1; + let result = db.insert_events(&next_height.into(), &events); + + // Then + let err = + result.expect_err("Should return error since DA message heights and commit da heights are different"); + assert!(err.to_string().contains("Invalid da height")); + } + let last_height = 1u64; - let events: Vec<_> = (0..=last_height) - .map(|i| { + // messages + inner_test( + |i| { let mut message = Message::default(); message.set_da_height(last_height.into()); message.set_amount(i); message.into() - }) - .collect(); - - // When - let mut db = MockDatabase { - data: Box::new(DBTx::default), - storage: Default::default(), - }; - let next_height = last_height + 1; - let result = db.insert_events(&next_height.into(), &events); - - // Then - let err = - result.expect_err("Should return error since DA message heights and commit da heights are different"); - assert!(err.to_string().contains("Invalid da height")); + }, + last_height, + ); + // relayed transactions + inner_test( + |i| { + let mut transaction = RelayedTransaction::default(); + transaction.set_da_height(last_height.into()); + transaction.set_max_gas(i); + transaction.into() + }, + last_height, + ); } #[test_case(None, 0, 0; "can set DA height to 0 when there is none available")] diff --git a/crates/services/relayer/src/service.rs b/crates/services/relayer/src/service.rs index 00cd86acc1..083b6d1953 100644 --- a/crates/services/relayer/src/service.rs +++ b/crates/services/relayer/src/service.rs @@ -29,7 +29,7 @@ use fuel_core_services::{ }; use fuel_core_types::{ blockchain::primitives::DaBlockHeight, - entities::message::Message, + entities::Message, }; use futures::StreamExt; use std::{ diff --git a/crates/services/relayer/src/service/get_logs.rs b/crates/services/relayer/src/service/get_logs.rs index 515def95fd..8a7761d198 100644 --- a/crates/services/relayer/src/service/get_logs.rs +++ b/crates/services/relayer/src/service/get_logs.rs @@ -1,5 +1,8 @@ use super::*; -use fuel_core_types::services::relayer::Event; +use fuel_core_types::{ + entities::RelayedTransaction, + services::relayer::Event, +}; use futures::TryStreamExt; use std::collections::BTreeMap; @@ -74,6 +77,9 @@ where EthEventLog::Message(m) => { Some(Ok(Event::Message(Message::from(&m)))) } + EthEventLog::Transaction(tx) => { + Some(Ok(Event::Transaction(RelayedTransaction::from(tx)))) + } // TODO: Log out ignored messages. EthEventLog::Ignored => None, } diff --git a/crates/services/relayer/src/storage.rs b/crates/services/relayer/src/storage.rs index 3ef5438d46..7c7b0a7056 100644 --- a/crates/services/relayer/src/storage.rs +++ b/crates/services/relayer/src/storage.rs @@ -221,6 +221,9 @@ mod tests { fuel_core_storage::basic_storage_tests!( EventsHistory, ::Key::default(), - vec![Event::Message(Default::default())] + vec![ + Event::Message(Default::default()), + Event::Transaction(Default::default()) + ] ); } diff --git a/crates/services/relayer/src/test_helpers.rs b/crates/services/relayer/src/test_helpers.rs index 2b00477134..84d9958724 100644 --- a/crates/services/relayer/src/test_helpers.rs +++ b/crates/services/relayer/src/test_helpers.rs @@ -23,7 +23,10 @@ use ethers_core::{ }, }; use fuel_core_types::{ - entities::message::Message, + entities::{ + Message, + RelayedTransaction, + }, fuel_types::Address, }; @@ -31,6 +34,7 @@ pub mod middleware; pub trait LogTestHelper { fn to_msg(&self) -> Message; + fn to_tx(&self) -> RelayedTransaction; } pub trait EvtToLog { @@ -44,6 +48,13 @@ impl LogTestHelper for Log { _ => panic!("This log does not form a message"), } } + + fn to_tx(&self) -> RelayedTransaction { + match EthEventLog::try_from(self).unwrap() { + EthEventLog::Transaction(t) => RelayedTransaction::from(t), + _ => panic!("This log does not form a relayed transaction"), + } + } } impl EvtToLog for crate::abi::bridge::MessageSentFilter { @@ -52,6 +63,12 @@ impl EvtToLog for crate::abi::bridge::MessageSentFilter { } } +impl EvtToLog for crate::abi::bridge::TransactionFilter { + fn into_log(self) -> Log { + event_to_log(self, &crate::abi::bridge::MESSAGESENT_ABI) + } +} + pub fn event_to_log(event: E, abi: ðers_core::abi::Abi) -> Log where E: EthEvent, diff --git a/crates/services/relayer/tests/integration.rs b/crates/services/relayer/tests/integration.rs index d93118439b..a47bc41828 100644 --- a/crates/services/relayer/tests/integration.rs +++ b/crates/services/relayer/tests/integration.rs @@ -1,8 +1,14 @@ #![cfg(feature = "test-helpers")] -use ethers_core::types::U256; +use ethers_core::types::{ + Log, + U256, +}; use fuel_core_relayer::{ - bridge::MessageSentFilter, + bridge::{ + MessageSentFilter, + TransactionFilter, + }, mock_db::MockDb, new_service_test, ports::RelayerDb, @@ -90,12 +96,8 @@ async fn stop_service_at_the_middle() { } #[tokio::test(start_paused = true)] -async fn can_get_messages() { - let mock_db = MockDb::default(); - let eth_node = MockMiddleware::default(); - - let config = Config::default(); - let contract_address = config.eth_v2_listening_contracts[0]; +#[allow(non_snake_case)] +async fn relayer__downloads_message_logs_to_events_table() { let message = |nonce: u64, block_number: u64| { let message = MessageSentFilter { nonce: U256::from_dec_str(nonce.to_string().as_str()) @@ -103,27 +105,51 @@ async fn can_get_messages() { ..Default::default() }; let mut log = message.into_log(); - log.address = contract_address; log.block_number = Some(block_number.into()); log }; + // setup mock data let logs = vec![message(1, 3), message(2, 5)]; let expected_messages: Vec<_> = logs.iter().map(|l| l.to_msg()).collect(); - eth_node.update_data(|data| data.logs_batch = vec![logs.clone()]); - // Setup the eth node with a block high enough that there - // will be some finalized blocks. - eth_node.update_data(|data| data.best_block.number = Some(100.into())); - let relayer = new_service_test(eth_node, mock_db.clone(), config); - relayer.start_and_await().await.unwrap(); - - relayer.shared.await_synced().await.unwrap(); - + let mut ctx = TestContext::new(); + // given logs + ctx.given_logs(logs); + // when the relayer runs + let mock_db = ctx.when_relayer_syncs().await; + // expect several messages in the database for msg in expected_messages { assert_eq!(mock_db.get_message(msg.id()).unwrap(), msg); } } +#[tokio::test(start_paused = true)] +#[allow(non_snake_case)] +async fn relayer__downloads_transaction_logs_to_events_table() { + let transaction = |max_gas: u64, block_number: u64| { + let transaction = TransactionFilter { + max_gas, + ..Default::default() + }; + let mut log = transaction.into_log(); + log.block_number = Some(block_number.into()); + log + }; + + // setup mock data + let logs = vec![transaction(2, 1), transaction(3, 2)]; + let expected_transactions: Vec<_> = logs.iter().map(|l| l.to_tx()).collect(); + let mut ctx = TestContext::new(); + // given logs + ctx.given_logs(logs); + // when the relayer runs + let mock_db = ctx.when_relayer_syncs().await; + // expect several transaction events in the database + for tx in expected_transactions { + assert_eq!(mock_db.get_transaction(&tx.id()).unwrap(), tx); + } +} + #[tokio::test(start_paused = true)] async fn deploy_height_is_set() { let mock_db = MockDb::default(); @@ -161,3 +187,42 @@ async fn deploy_height_is_set() { rx.await.unwrap(); assert_eq!(*mock_db.get_finalized_da_height().unwrap(), 54); } + +struct TestContext { + mock_db: MockDb, + eth_node: MockMiddleware, + config: Config, +} + +impl TestContext { + fn new() -> Self { + Self { + mock_db: MockDb::default(), + eth_node: MockMiddleware::default(), + config: Config::default(), + } + } + + fn given_logs(&mut self, mut logs: Vec) { + let contract_address = self.config.eth_v2_listening_contracts[0]; + for log in &mut logs { + log.address = contract_address; + } + + self.eth_node + .update_data(|data| data.logs_batch = vec![logs.clone()]); + // Setup the eth node with a block high enough that there + // will be some finalized blocks. + self.eth_node + .update_data(|data| data.best_block.number = Some(100.into())); + } + + async fn when_relayer_syncs(self) -> MockDb { + let relayer = new_service_test(self.eth_node, self.mock_db.clone(), self.config); + relayer.start_and_await().await.unwrap(); + + relayer.shared.await_synced().await.unwrap(); + + self.mock_db + } +} diff --git a/crates/services/txpool/src/mock_db.rs b/crates/services/txpool/src/mock_db.rs index bbaa5bc15e..0b83e7a533 100644 --- a/crates/services/txpool/src/mock_db.rs +++ b/crates/services/txpool/src/mock_db.rs @@ -9,7 +9,7 @@ use fuel_core_types::{ Coin, CompressedCoin, }, - message::Message, + relayer::message::Message, }, fuel_tx::{ Contract, diff --git a/crates/services/txpool/src/ports.rs b/crates/services/txpool/src/ports.rs index 7a32746c7e..bcc9bbec35 100644 --- a/crates/services/txpool/src/ports.rs +++ b/crates/services/txpool/src/ports.rs @@ -3,7 +3,7 @@ use fuel_core_storage::Result as StorageResult; use fuel_core_types::{ entities::{ coins::coin::CompressedCoin, - message::Message, + relayer::message::Message, }, fuel_tx::{ Transaction, diff --git a/crates/services/txpool/src/txpool/test_helpers.rs b/crates/services/txpool/src/txpool/test_helpers.rs index 05499981a3..7b25ca7ef3 100644 --- a/crates/services/txpool/src/txpool/test_helpers.rs +++ b/crates/services/txpool/src/txpool/test_helpers.rs @@ -1,6 +1,6 @@ use crate::test_helpers::IntoEstimated; use fuel_core_types::{ - entities::message::{ + entities::relayer::message::{ Message, MessageV1, }, diff --git a/crates/storage/src/tables.rs b/crates/storage/src/tables.rs index 87212d945e..9d8531db55 100644 --- a/crates/storage/src/tables.rs +++ b/crates/storage/src/tables.rs @@ -14,7 +14,7 @@ use fuel_core_types::{ entities::{ coins::coin::CompressedCoin, contract::ContractUtxoInfo, - message::Message, + relayer::message::Message, }, fuel_tx::{ ConsensusParameters, diff --git a/crates/types/src/entities.rs b/crates/types/src/entities.rs index 90328f77c3..0f4b90f042 100644 --- a/crates/types/src/entities.rs +++ b/crates/types/src/entities.rs @@ -1,12 +1,15 @@ //! Higher level domain types -use crate::entities::message::MessageV1; +use crate::entities::relayer::message::MessageV1; use coins::message_coin::MessageCoin; -use message::Message; +pub use relayer::{ + message::Message, + transaction::RelayedTransaction, +}; pub mod coins; pub mod contract; -pub mod message; +pub mod relayer; impl TryFrom for MessageCoin { type Error = anyhow::Error; diff --git a/crates/types/src/entities/relayer.rs b/crates/types/src/entities/relayer.rs new file mode 100644 index 0000000000..ef1bb96613 --- /dev/null +++ b/crates/types/src/entities/relayer.rs @@ -0,0 +1,4 @@ +//! Relayed entities + +pub mod message; +pub mod transaction; diff --git a/crates/types/src/entities/message.rs b/crates/types/src/entities/relayer/message.rs similarity index 100% rename from crates/types/src/entities/message.rs rename to crates/types/src/entities/relayer/message.rs diff --git a/crates/types/src/entities/relayer/transaction.rs b/crates/types/src/entities/relayer/transaction.rs new file mode 100644 index 0000000000..625705d05b --- /dev/null +++ b/crates/types/src/entities/relayer/transaction.rs @@ -0,0 +1,128 @@ +//! Relayed (forced) transaction entity types + +use crate::{ + blockchain::primitives::DaBlockHeight, + fuel_crypto, + fuel_types::Bytes32, +}; + +/// Transaction sent from the DA layer to fuel by the relayer +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum RelayedTransaction { + /// V1 version of the relayed transaction + V1(RelayedTransactionV1), +} + +impl Default for RelayedTransaction { + fn default() -> Self { + Self::V1(Default::default()) + } +} + +/// The V1 version of the relayed transaction +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct RelayedTransactionV1 { + /// The max gas that this transaction can consume + pub max_gas: u64, + /// The serialized transaction transmitted from the bridge + pub serialized_transaction: Vec, + /// The block height from the parent da layer that originated this transaction + pub da_height: DaBlockHeight, +} + +/// The hash of a relayed transaction +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + derive_more::Display, + derive_more::From, + derive_more::Into, +)] +pub struct RelayedTransactionId(Bytes32); + +impl RelayedTransaction { + /// The hash of the relayed transaction + pub fn id(&self) -> RelayedTransactionId { + match &self { + RelayedTransaction::V1(tx) => tx.id(), + } + } + + /// Get the DA height that originated this transaction from L1 + pub fn da_height(&self) -> DaBlockHeight { + match self { + RelayedTransaction::V1(transaction) => transaction.da_height, + } + } + + #[cfg(any(test, feature = "test-helpers"))] + /// Set the da height + pub fn set_da_height(&mut self, height: DaBlockHeight) { + match self { + RelayedTransaction::V1(transaction) => { + transaction.da_height = height; + } + } + } + + #[cfg(any(test, feature = "test-helpers"))] + /// Get the max gas + pub fn max_gas(&self) -> u64 { + match self { + RelayedTransaction::V1(transaction) => transaction.max_gas, + } + } + + #[cfg(any(test, feature = "test-helpers"))] + /// Set the max gas + pub fn set_max_gas(&mut self, max_gas: u64) { + match self { + RelayedTransaction::V1(transaction) => { + transaction.max_gas = max_gas; + } + } + } + + #[cfg(any(test, feature = "test-helpers"))] + /// Get the canonically serialized transaction + pub fn serialized_transaction(&self) -> &[u8] { + match self { + RelayedTransaction::V1(transaction) => &transaction.serialized_transaction, + } + } + + #[cfg(any(test, feature = "test-helpers"))] + /// Set the serialized transaction bytes + pub fn set_serialized_transaction(&mut self, serialized_bytes: Vec) { + match self { + RelayedTransaction::V1(transaction) => { + transaction.serialized_transaction = serialized_bytes; + } + } + } +} + +impl RelayedTransactionV1 { + /// The hash of the relayed transaction (max_gas (big endian) || serialized_transaction) + pub fn id(&self) -> RelayedTransactionId { + let hasher = fuel_crypto::Hasher::default() + .chain(self.max_gas.to_be_bytes()) + .chain(self.serialized_transaction.as_slice()); + RelayedTransactionId((*hasher.finalize()).into()) + } +} + +impl From for RelayedTransaction { + fn from(relayed_transaction: RelayedTransactionV1) -> Self { + RelayedTransaction::V1(relayed_transaction) + } +} diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index b1b3f98608..f1b829452d 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -10,7 +10,7 @@ use crate::{ }, entities::{ coins::coin::Coin, - message::Message, + relayer::message::Message, }, fuel_tx::{ Receipt, diff --git a/crates/types/src/services/relayer.rs b/crates/types/src/services/relayer.rs index 2903454d11..8c2f4602ca 100644 --- a/crates/types/src/services/relayer.rs +++ b/crates/types/src/services/relayer.rs @@ -2,7 +2,10 @@ use crate::{ blockchain::primitives::DaBlockHeight, - entities::message::Message, + entities::{ + Message, + RelayedTransaction, + }, fuel_types::Bytes32, }; use std::ops::Deref; @@ -13,6 +16,8 @@ use std::ops::Deref; pub enum Event { /// The message event which was sent to the bridge. Message(Message), + /// A transaction that was forcibly included from L1 + Transaction(RelayedTransaction), } impl Event { @@ -20,6 +25,7 @@ impl Event { pub fn da_height(&self) -> DaBlockHeight { match self { Event::Message(message) => message.da_height(), + Event::Transaction(transaction) => transaction.da_height(), } } @@ -27,6 +33,7 @@ impl Event { pub fn hash(&self) -> Bytes32 { match self { Event::Message(message) => (*message.id().deref()).into(), + Event::Transaction(transaction) => transaction.id().into(), } } } @@ -36,3 +43,9 @@ impl From for Event { Event::Message(message) } } + +impl From for Event { + fn from(transaction: RelayedTransaction) -> Self { + Event::Transaction(transaction) + } +}