v2-2
Asa Oines 2 years ago committed by GitHub
parent f4a2462364
commit a23c1a8d2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 60
      rust/abacus-base/src/agent.rs
  2. 12
      rust/abacus-base/src/contract_sync/last_message.rs
  3. 139
      rust/abacus-base/src/contract_sync/mailbox.rs
  4. 8
      rust/abacus-base/src/contract_sync/metrics.rs
  5. 11
      rust/abacus-base/src/contract_sync/mod.rs
  6. 3
      rust/abacus-base/src/interchain_gas.rs
  7. 10
      rust/abacus-base/src/lib.rs
  8. 148
      rust/abacus-base/src/mailbox.rs
  9. 14
      rust/abacus-base/src/metrics/core.rs
  10. 171
      rust/abacus-base/src/outbox.rs
  11. 227
      rust/abacus-base/src/settings/chains.rs
  12. 317
      rust/abacus-base/src/settings/mod.rs
  13. 55
      rust/abacus-core/src/chain.rs
  14. 222
      rust/abacus-core/src/db/abacus_db.rs
  15. 3
      rust/abacus-core/src/lib.rs
  16. 25
      rust/abacus-core/src/test_output.rs
  17. 52
      rust/abacus-core/src/traits/common.rs
  18. 25
      rust/abacus-core/src/traits/inbox.rs
  19. 24
      rust/abacus-core/src/traits/indexer.rs
  20. 60
      rust/abacus-core/src/traits/mailbox.rs
  21. 35
      rust/abacus-core/src/traits/mod.rs
  22. 30
      rust/abacus-core/src/traits/multisig_ism.rs
  23. 78
      rust/abacus-core/src/traits/outbox.rs
  24. 46
      rust/abacus-core/src/traits/validator_manager.rs
  25. 27
      rust/abacus-core/src/types/checkpoint.rs
  26. 169
      rust/abacus-core/src/types/message.rs
  27. 8
      rust/abacus-core/src/types/mod.rs
  28. 7
      rust/abacus-core/src/utils.rs
  29. 84
      rust/abacus-test/src/mocks/inbox.rs
  30. 20
      rust/abacus-test/src/mocks/indexer.rs
  31. 123
      rust/abacus-test/src/mocks/mailbox.rs
  32. 9
      rust/abacus-test/src/mocks/mod.rs
  33. 116
      rust/abacus-test/src/mocks/outbox.rs
  34. 32
      rust/abacus-test/src/test_utils.rs
  35. 8
      rust/agents/relayer/src/checkpoint_fetcher.rs
  36. 4
      rust/agents/relayer/src/merkle_tree_builder.rs
  37. 14
      rust/agents/relayer/src/msg/gas_payment/mod.rs
  38. 28
      rust/agents/relayer/src/msg/gas_payment/policies/meets_estimated_cost.rs
  39. 9
      rust/agents/relayer/src/msg/gas_payment/policies/minimum.rs
  40. 9
      rust/agents/relayer/src/msg/gas_payment/policies/none.rs
  41. 59
      rust/agents/relayer/src/msg/gelato_submitter/mod.rs
  42. 82
      rust/agents/relayer/src/msg/gelato_submitter/sponsored_call_op.rs
  43. 15
      rust/agents/relayer/src/msg/mod.rs
  44. 132
      rust/agents/relayer/src/msg/processor.rs
  45. 105
      rust/agents/relayer/src/msg/serial_submitter.rs
  46. 164
      rust/agents/relayer/src/relayer.rs
  47. 2
      rust/agents/relayer/src/settings/mod.rs
  48. 105
      rust/agents/scraper/src/scraper/mod.rs
  49. 5
      rust/agents/scraper/src/settings.rs
  50. 2
      rust/agents/validator/src/settings.rs
  51. 57
      rust/agents/validator/src/submit.rs
  52. 13
      rust/agents/validator/src/validator.rs
  53. 239
      rust/chains/abacus-ethereum/abis/Inbox.abi.json
  54. 321
      rust/chains/abacus-ethereum/abis/InboxValidatorManager.abi.json
  55. 23
      rust/chains/abacus-ethereum/abis/InterchainGasPaymaster.abi.json
  56. 174
      rust/chains/abacus-ethereum/abis/Mailbox.abi.json
  57. 324
      rust/chains/abacus-ethereum/abis/MultisigIsm.abi.json
  58. 142
      rust/chains/abacus-ethereum/src/inbox.rs
  59. 27
      rust/chains/abacus-ethereum/src/interchain_gas.rs
  60. 16
      rust/chains/abacus-ethereum/src/lib.rs
  61. 302
      rust/chains/abacus-ethereum/src/mailbox.rs
  62. 235
      rust/chains/abacus-ethereum/src/multisig_ism.rs
  63. 327
      rust/chains/abacus-ethereum/src/outbox.rs
  64. 3
      rust/chains/abacus-ethereum/src/trait_builder.rs
  65. 207
      rust/chains/abacus-ethereum/src/validator_manager.rs
  66. 126
      rust/config/mainnet/arbitrum_config.json
  67. 123
      rust/config/mainnet/avalanche_config.json
  68. 126
      rust/config/mainnet/bsc_config.json
  69. 126
      rust/config/mainnet/celo_config.json
  70. 126
      rust/config/mainnet/ethereum_config.json
  71. 126
      rust/config/mainnet/moonbeam_config.json
  72. 126
      rust/config/mainnet/optimism_config.json
  73. 126
      rust/config/mainnet/polygon_config.json
  74. 249
      rust/config/mainnet/scraper_config.json
  75. 56
      rust/config/test/test1_config.json
  76. 56
      rust/config/test/test2_config.json
  77. 56
      rust/config/test/test3_config.json
  78. 65
      rust/config/test/test_config.json
  79. 98
      rust/config/testnet2/alfajores_config.json
  80. 98
      rust/config/testnet2/bsctestnet_config.json
  81. 98
      rust/config/testnet2/fuji_config.json
  82. 98
      rust/config/testnet2/goerli_config.json
  83. 98
      rust/config/testnet2/moonbasealpha_config.json
  84. 98
      rust/config/testnet2/mumbai_config.json
  85. 218
      rust/config/testnet2/scraper_config.json
  86. 41
      rust/utils/run-locally/src/main.rs
  87. 7
      solidity/contracts/Mailbox.sol
  88. 16
      solidity/contracts/libs/Message.sol
  89. 4
      solidity/interfaces/IMailbox.sol
  90. 6
      solidity/test/lib/mailboxes.ts
  91. 4
      typescript/infra/hardhat.config.ts
  92. 22
      typescript/infra/scripts/core.ts
  93. 20
      typescript/infra/src/config/agent.ts
  94. 2
      typescript/infra/src/config/index.ts
  95. 17
      typescript/infra/src/core/deploy.ts
  96. 4
      typescript/infra/src/utils/utils.ts
  97. 38
      typescript/sdk/src/consts/environments/test.json
  98. 16
      typescript/utils/src/utils.ts

@ -1,6 +1,7 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use abacus_core::MultisigIsm;
use async_trait::async_trait; use async_trait::async_trait;
use eyre::{Report, Result}; use eyre::{Report, Result};
use futures_util::future::select_all; use futures_util::future::select_all;
@ -10,39 +11,25 @@ use tracing::instrument::Instrumented;
use tracing::{info_span, Instrument}; use tracing::{info_span, Instrument};
use abacus_core::db::DB; use abacus_core::db::DB;
use abacus_core::InboxValidatorManager;
use crate::{ use crate::{
cancel_task, cancel_task, metrics::CoreMetrics, settings::Settings, CachingInterchainGasPaymaster,
metrics::CoreMetrics, CachingMailbox,
settings::{IndexSettings, Settings},
CachingInbox, CachingInterchainGasPaymaster, CachingOutbox,
}; };
/// Contracts relating to an inbox chain
#[derive(Clone, Debug)]
pub struct InboxContracts {
/// A boxed Inbox
pub inbox: CachingInbox,
/// A boxed InboxValidatorManager
pub validator_manager: Arc<dyn InboxValidatorManager>,
}
/// Properties shared across all abacus agents /// Properties shared across all abacus agents
#[derive(Debug)] #[derive(Debug)]
pub struct AbacusAgentCore { pub struct AbacusAgentCore {
/// A boxed Outbox /// A map of mailbox contracts by chain name
pub outbox: CachingOutbox, pub mailboxes: HashMap<String, CachingMailbox>,
/// A boxed InterchainGasPaymaster /// A map of interchain gas paymaster contracts by chain name
pub interchain_gas_paymaster: Option<CachingInterchainGasPaymaster>, pub interchain_gas_paymasters: HashMap<String, CachingInterchainGasPaymaster>,
/// A map of Inbox contracts by name /// A map of interchain gas paymaster contracts by chain name
pub inboxes: HashMap<String, InboxContracts>, pub multisig_isms: HashMap<String, Arc<dyn MultisigIsm>>,
/// A persistent KV Store (currently implemented as rocksdb) /// A persistent KV Store (currently implemented as rocksdb)
pub db: DB, pub db: DB,
/// Prometheus metrics /// Prometheus metrics
pub metrics: Arc<CoreMetrics>, pub metrics: Arc<CoreMetrics>,
/// The height at which to start indexing the Outbox
pub indexer: IndexSettings,
/// Settings this agent was created with /// Settings this agent was created with
pub settings: Settings, pub settings: Settings,
} }
@ -86,17 +73,14 @@ pub trait Agent: BaseAgent {
/// Return a handle to the DB /// Return a handle to the DB
fn db(&self) -> &DB; fn db(&self) -> &DB;
/// Return a reference to an Outbox contract /// Return a reference to a Mailbox contract
fn outbox(&self) -> &CachingOutbox; fn mailbox(&self, chain_name: &str) -> Option<&CachingMailbox>;
/// Return a reference to an InterchainGasPaymaster contract /// Return a reference to an InterchainGasPaymaster contract
fn interchain_gas_paymaster(&self) -> Option<&CachingInterchainGasPaymaster>; fn interchain_gas_paymaster(&self, chain_name: &str) -> Option<&CachingInterchainGasPaymaster>;
/// Get a reference to the inboxes map
fn inboxes(&self) -> &HashMap<String, InboxContracts>;
/// Get a reference to an inbox's contracts by its name /// Return a reference to a Multisig Ism contract
fn inbox_by_name(&self, name: &str) -> Option<&InboxContracts>; fn multisig_ism(&self, chain_name: &str) -> Option<&Arc<dyn MultisigIsm>>;
} }
#[async_trait] #[async_trait]
@ -108,20 +92,16 @@ where
&self.as_ref().db &self.as_ref().db
} }
fn outbox(&self) -> &CachingOutbox { fn mailbox(&self, chain_name: &str) -> Option<&CachingMailbox> {
&self.as_ref().outbox self.as_ref().mailboxes.get(chain_name)
}
fn interchain_gas_paymaster(&self) -> Option<&CachingInterchainGasPaymaster> {
self.as_ref().interchain_gas_paymaster.as_ref()
} }
fn inboxes(&self) -> &HashMap<String, InboxContracts> { fn interchain_gas_paymaster(&self, chain_name: &str) -> Option<&CachingInterchainGasPaymaster> {
&self.as_ref().inboxes self.as_ref().interchain_gas_paymasters.get(chain_name)
} }
fn inbox_by_name(&self, name: &str) -> Option<&InboxContracts> { fn multisig_ism(&self, chain_name: &str) -> Option<&Arc<dyn MultisigIsm>> {
self.inboxes().get(name) self.as_ref().multisig_isms.get(chain_name)
} }
} }

@ -1,4 +1,4 @@
use abacus_core::{ListValidity, RawCommittedMessage}; use abacus_core::{AbacusMessage, ListValidity};
/// Check if the list of sorted messages is a valid continuation of the /// Check if the list of sorted messages is a valid continuation of the
/// OptLatestLeafIndex. If the latest index is Some, check the validity of the /// OptLatestLeafIndex. If the latest index is Some, check the validity of the
@ -11,8 +11,8 @@ use abacus_core::{ListValidity, RawCommittedMessage};
/// still validate the new messages in the case that we have not seen any /// still validate the new messages in the case that we have not seen any
/// previous messages (None case). /// previous messages (None case).
pub fn validate_message_continuity( pub fn validate_message_continuity(
latest_message_leaf_index: Option<u32>, latest_message_nonce: Option<u32>,
sorted_messages: &[&RawCommittedMessage], sorted_messages: &[&AbacusMessage],
) -> ListValidity { ) -> ListValidity {
if sorted_messages.is_empty() { if sorted_messages.is_empty() {
return ListValidity::Empty; return ListValidity::Empty;
@ -20,10 +20,10 @@ pub fn validate_message_continuity(
// If we have seen another leaf in a previous block range, ensure // If we have seen another leaf in a previous block range, ensure
// the batch contains the consecutive next leaf // the batch contains the consecutive next leaf
if let Some(last_seen) = latest_message_leaf_index { if let Some(last_seen) = latest_message_nonce {
let has_desired_message = sorted_messages let has_desired_message = sorted_messages
.iter() .iter()
.any(|message| last_seen == message.leaf_index - 1); .any(|&message| last_seen == message.nonce - 1);
if !has_desired_message { if !has_desired_message {
return ListValidity::InvalidContinuation; return ListValidity::InvalidContinuation;
} }
@ -31,7 +31,7 @@ pub fn validate_message_continuity(
// Ensure no gaps in new batch of leaves // Ensure no gaps in new batch of leaves
for pair in sorted_messages.windows(2) { for pair in sorted_messages.windows(2) {
if pair[0].leaf_index != pair[1].leaf_index - 1 { if pair[0].nonce != pair[1].nonce - 1 {
return ListValidity::ContainsGaps; return ListValidity::ContainsGaps;
} }
} }

@ -1,11 +1,12 @@
use std::cmp::min; use std::cmp::min;
use std::time::Duration; use std::time::Duration;
use eyre::Result;
use tokio::time::sleep; use tokio::time::sleep;
use tracing::{debug, info, info_span, warn}; use tracing::{debug, info, info_span, warn};
use tracing::{instrument::Instrumented, Instrument}; use tracing::{instrument::Instrumented, Instrument};
use abacus_core::{name_from_domain_id, CommittedMessage, ListValidity, OutboxIndexer}; use abacus_core::{name_from_domain_id, ListValidity, MailboxIndexer};
use crate::contract_sync::last_message::validate_message_continuity; use crate::contract_sync::last_message::validate_message_continuity;
use crate::{contract_sync::schema::OutboxContractSyncDB, ContractSync}; use crate::{contract_sync::schema::OutboxContractSyncDB, ContractSync};
@ -14,10 +15,10 @@ const MESSAGES_LABEL: &str = "messages";
impl<I> ContractSync<I> impl<I> ContractSync<I>
where where
I: OutboxIndexer + Clone + 'static, I: MailboxIndexer + Clone + 'static,
{ {
/// Sync outbox messages /// Sync dispatched messages
pub fn sync_outbox_messages(&self) -> Instrumented<tokio::task::JoinHandle<eyre::Result<()>>> { pub fn sync_dispatched_messages(&self) -> Instrumented<tokio::task::JoinHandle<Result<()>>> {
let span = info_span!("MessageContractSync"); let span = info_span!("MessageContractSync");
let db = self.db.clone(); let db = self.db.clone();
@ -37,7 +38,7 @@ where
.missed_events .missed_events
.with_label_values(&[MESSAGES_LABEL, &self.chain_name]); .with_label_values(&[MESSAGES_LABEL, &self.chain_name]);
let message_leaf_index = self.metrics.message_leaf_index.clone(); let message_nonce = self.metrics.message_nonce.clone();
let chain_name = self.chain_name.clone(); let chain_name = self.chain_name.clone();
let config_from = self.index_settings.from(); let config_from = self.index_settings.from();
@ -120,12 +121,12 @@ where
// Get the latest known leaf index. All messages whose indices are <= this index // Get the latest known leaf index. All messages whose indices are <= this index
// have been stored in the DB. // have been stored in the DB.
let last_leaf_index = db.retrieve_latest_leaf_index()?; let last_nonce = db.retrieve_latest_nonce()?;
// Filter out any messages that have already been successfully indexed and stored. // Filter out any messages that have already been successfully indexed and stored.
// This is necessary if we're re-indexing blocks in hope of finding missing messages. // This is necessary if we're re-indexing blocks in hope of finding missing messages.
if let Some(min_index) = last_leaf_index { if let Some(min_index) = last_nonce {
sorted_messages = sorted_messages.into_iter().filter(|m| m.leaf_index > min_index).collect(); sorted_messages = sorted_messages.into_iter().filter(|m| m.nonce > min_index).collect();
} }
debug!( debug!(
@ -135,24 +136,21 @@ where
"[Messages]: filtered any messages already indexed" "[Messages]: filtered any messages already indexed"
); );
// Ensure the sorted messages are a valid continuation of last_leaf_index // Ensure the sorted messages are a valid continuation of last_nonce
match validate_message_continuity(last_leaf_index, &sorted_messages.iter().collect::<Vec<_>>()) { match validate_message_continuity(last_nonce, &sorted_messages.iter().collect::<Vec<_>>()) {
ListValidity::Valid => { ListValidity::Valid => {
// Store messages // Store messages
let max_leaf_index_of_batch = db.store_messages(&sorted_messages)?; let max_nonce_of_batch = db.store_messages(&sorted_messages)?;
// Report amount of messages stored into db // Report amount of messages stored into db
stored_messages.inc_by(sorted_messages.len() as u64); stored_messages.inc_by(sorted_messages.len() as u64);
// Report latest leaf index to gauge by dst // Report latest leaf index to gauge by dst
for raw_msg in sorted_messages.iter() { for msg in sorted_messages.iter() {
let dst = CommittedMessage::try_from(raw_msg) let dst = name_from_domain_id(msg.destination).unwrap_or_else(|| "unknown".into());
.ok() message_nonce
.and_then(|msg| name_from_domain_id(msg.message.destination))
.unwrap_or_else(|| "unknown".into());
message_leaf_index
.with_label_values(&["dispatch", &chain_name, &dst]) .with_label_values(&["dispatch", &chain_name, &dst])
.set(max_leaf_index_of_batch as i64); .set(max_nonce_of_batch as i64);
} }
// Update the latest valid start block. // Update the latest valid start block.
@ -163,12 +161,12 @@ where
from = to + 1; from = to + 1;
} }
// The index of the first message in sorted_messages is not the // The index of the first message in sorted_messages is not the
// `last_leaf_index+1`. // `last_nonce+1`.
ListValidity::InvalidContinuation => { ListValidity::InvalidContinuation => {
missed_messages.inc(); missed_messages.inc();
warn!( warn!(
last_leaf_index = ?last_leaf_index, last_nonce = ?last_nonce,
start_block = from, start_block = from,
end_block = to, end_block = to,
last_valid_range_start_block, last_valid_range_start_block,
@ -181,7 +179,7 @@ where
missed_messages.inc(); missed_messages.inc();
warn!( warn!(
last_leaf_index = ?last_leaf_index, last_nonce = ?last_nonce,
start_block = from, start_block = from,
end_block = to, end_block = to,
"[Messages]: Found gaps in the messages in range, re-indexing the same range.", "[Messages]: Found gaps in the messages in range, re-indexing the same range.",
@ -212,28 +210,32 @@ mod test {
use tokio::select; use tokio::select;
use tokio::time::{interval, timeout}; use tokio::time::{interval, timeout};
use abacus_core::{db::AbacusDB, AbacusMessage, Encode, LogMeta, RawCommittedMessage}; use abacus_core::{db::AbacusDB, AbacusMessage, LogMeta};
use abacus_test::mocks::indexer::MockAbacusIndexer; use abacus_test::mocks::indexer::MockAbacusIndexer;
use abacus_test::test_utils; use abacus_test::test_utils;
use mockall::predicate::eq; use mockall::predicate::eq;
use crate::chains::IndexSettings;
use crate::contract_sync::schema::OutboxContractSyncDB; use crate::contract_sync::schema::OutboxContractSyncDB;
use crate::ContractSync; use crate::ContractSync;
use crate::{settings::IndexSettings, ContractSyncMetrics, CoreMetrics}; use crate::{ContractSyncMetrics, CoreMetrics};
#[tokio::test] #[tokio::test]
async fn handles_missing_rpc_messages() { async fn handles_missing_rpc_messages() {
test_utils::run_test_db(|db| async move { test_utils::run_test_db(|db| async move {
let mut message_vec = vec![]; let message_gen = |nonce: u32| -> AbacusMessage {
AbacusMessage { AbacusMessage {
origin: 1000, version: 0,
destination: 2000, nonce,
sender: H256::from([10; 32]), origin: 1000,
recipient: H256::from([11; 32]), destination: 2000,
body: [10u8; 5].to_vec(), sender: H256::from([10; 32]),
} recipient: H256::from([11; 32]),
.write_to(&mut message_vec) body: [10u8; 5].to_vec(),
.expect("!write_to"); }
};
let messages = (0..10).map(message_gen).collect::<Vec<AbacusMessage>>();
let meta = || LogMeta { let meta = || LogMeta {
address: Default::default(), address: Default::default(),
@ -244,36 +246,6 @@ mod test {
log_index: Default::default(), log_index: Default::default(),
}; };
let m0 = RawCommittedMessage {
leaf_index: 0,
message: message_vec.clone(),
};
let m1 = RawCommittedMessage {
leaf_index: 1,
message: message_vec.clone(),
};
let m2 = RawCommittedMessage {
leaf_index: 2,
message: message_vec.clone(),
};
let m3 = RawCommittedMessage {
leaf_index: 3,
message: message_vec.clone(),
};
let m4 = RawCommittedMessage {
leaf_index: 4,
message: message_vec.clone(),
};
let m5 = RawCommittedMessage {
leaf_index: 5,
message: message_vec.clone(),
};
let latest_valid_message_range_start_block = 100; let latest_valid_message_range_start_block = 100;
let mut mock_indexer = MockAbacusIndexer::new(); let mut mock_indexer = MockAbacusIndexer::new();
@ -281,7 +253,7 @@ mod test {
let mut seq = Sequence::new(); let mut seq = Sequence::new();
// Return m0. // Return m0.
let m0_clone = m0.clone(); let m0 = messages[0].clone();
mock_indexer mock_indexer
.expect__get_finalized_block_number() .expect__get_finalized_block_number()
.times(1) .times(1)
@ -292,10 +264,10 @@ mod test {
.times(1) .times(1)
.with(eq(91), eq(110)) .with(eq(91), eq(110))
.in_sequence(&mut seq) .in_sequence(&mut seq)
.return_once(move |_, _| Ok(vec![(m0_clone, meta())])); .return_once(move |_, _| Ok(vec![(m0, meta())]));
// Return m1, miss m2. // Return m1, miss m2.
let m1_clone = m1.clone(); let m1 = messages[1].clone();
mock_indexer mock_indexer
.expect__get_finalized_block_number() .expect__get_finalized_block_number()
.times(1) .times(1)
@ -306,7 +278,7 @@ mod test {
.times(1) .times(1)
.with(eq(101), eq(120)) .with(eq(101), eq(120))
.in_sequence(&mut seq) .in_sequence(&mut seq)
.return_once(move |_, _| Ok(vec![(m1_clone, meta())])); .return_once(move |_, _| Ok(vec![(m1, meta())]));
// Miss m3. // Miss m3.
mock_indexer mock_indexer
@ -341,7 +313,7 @@ mod test {
.return_once(|| Ok(140)); .return_once(|| Ok(140));
// m1 --> m5 seen as an invalid continuation // m1 --> m5 seen as an invalid continuation
let m5_clone = m5.clone(); let m5 = messages[5].clone();
mock_indexer mock_indexer
.expect__get_finalized_block_number() .expect__get_finalized_block_number()
.times(1) .times(1)
@ -352,13 +324,13 @@ mod test {
.times(1) .times(1)
.with(eq(131), eq(150)) .with(eq(131), eq(150))
.in_sequence(&mut seq) .in_sequence(&mut seq)
.return_once(move |_, _| Ok(vec![(m5_clone, meta())])); .return_once(move |_, _| Ok(vec![(m5, meta())]));
// Indexer goes back to the last valid message range start block // Indexer goes back to the last valid message range start block
// and indexes the range based off the chunk size of 19. // and indexes the range based off the chunk size of 19.
// This time it gets m1 and m2 (which was previously skipped) // This time it gets m1 and m2 (which was previously skipped)
let m1_clone = m1.clone(); let m1 = messages[1].clone();
let m2_clone = m2.clone(); let m2 = messages[2].clone();
mock_indexer mock_indexer
.expect__get_finalized_block_number() .expect__get_finalized_block_number()
.times(1) .times(1)
@ -369,12 +341,12 @@ mod test {
.times(1) .times(1)
.with(eq(101), eq(120)) .with(eq(101), eq(120))
.in_sequence(&mut seq) .in_sequence(&mut seq)
.return_once(move |_, _| Ok(vec![(m1_clone, meta()), (m2_clone, meta())])); .return_once(move |_, _| Ok(vec![(m1, meta()), (m2, meta())]));
// Indexer continues, this time getting m3 and m5 message, but skipping m4, // Indexer continues, this time getting m3 and m5 message, but skipping m4,
// which means this range contains gaps // which means this range contains gaps
let m3_clone = m3.clone(); let m3 = messages[3].clone();
let m5_clone = m5.clone(); let m5 = messages[5].clone();
mock_indexer mock_indexer
.expect__get_finalized_block_number() .expect__get_finalized_block_number()
.times(1) .times(1)
@ -385,10 +357,13 @@ mod test {
.times(1) .times(1)
.with(eq(121), eq(140)) .with(eq(121), eq(140))
.in_sequence(&mut seq) .in_sequence(&mut seq)
.return_once(move |_, _| Ok(vec![(m3_clone, meta()), (m5_clone, meta())])); .return_once(move |_, _| Ok(vec![(m3, meta()), (m5, meta())]));
// Indexer retries, the same range in hope of filling the gap, // Indexer retries, the same range in hope of filling the gap,
// which it now does successfully // which it now does successfully
let m3 = messages[3].clone();
let m4 = messages[4].clone();
let m5 = messages[5].clone();
mock_indexer mock_indexer
.expect__get_finalized_block_number() .expect__get_finalized_block_number()
.times(1) .times(1)
@ -461,16 +436,16 @@ mod test {
sync_metrics, sync_metrics,
); );
let sync_task = contract_sync.sync_outbox_messages(); let sync_task = contract_sync.sync_dispatched_messages();
let test_pass_fut = timeout(Duration::from_secs(30), async move { let test_pass_fut = timeout(Duration::from_secs(30), async move {
let mut interval = interval(Duration::from_millis(20)); let mut interval = interval(Duration::from_millis(20));
loop { loop {
if abacus_db.message_by_leaf_index(0).expect("!db").is_some() if abacus_db.message_by_nonce(0).expect("!db").is_some()
&& abacus_db.message_by_leaf_index(1).expect("!db").is_some() && abacus_db.message_by_nonce(1).expect("!db").is_some()
&& abacus_db.message_by_leaf_index(2).expect("!db").is_some() && abacus_db.message_by_nonce(2).expect("!db").is_some()
&& abacus_db.message_by_leaf_index(3).expect("!db").is_some() && abacus_db.message_by_nonce(3).expect("!db").is_some()
&& abacus_db.message_by_leaf_index(4).expect("!db").is_some() && abacus_db.message_by_nonce(4).expect("!db").is_some()
&& abacus_db.message_by_leaf_index(5).expect("!db").is_some() && abacus_db.message_by_nonce(5).expect("!db").is_some()
{ {
break; break;
} }

@ -28,8 +28,8 @@ pub struct ContractSyncMetrics {
/// - `chain`: Chain the indexer is collecting data from. /// - `chain`: Chain the indexer is collecting data from.
pub missed_events: IntCounterVec, pub missed_events: IntCounterVec,
/// See `last_known_message_leaf_index` in CoreMetrics. /// See `last_known_message_nonce` in CoreMetrics.
pub message_leaf_index: IntGaugeVec, pub message_nonce: IntGaugeVec,
} }
impl ContractSyncMetrics { impl ContractSyncMetrics {
@ -59,13 +59,13 @@ impl ContractSyncMetrics {
) )
.expect("failed to register missed_events metric"); .expect("failed to register missed_events metric");
let message_leaf_index = metrics.last_known_message_leaf_index(); let message_nonce = metrics.last_known_message_nonce();
ContractSyncMetrics { ContractSyncMetrics {
indexed_height, indexed_height,
stored_events, stored_events,
missed_events, missed_events,
message_leaf_index, message_nonce,
} }
} }
} }

@ -3,23 +3,22 @@
use abacus_core::db::AbacusDB; use abacus_core::db::AbacusDB;
pub use interchain_gas::*; pub use interchain_gas::*;
pub use mailbox::*;
pub use metrics::ContractSyncMetrics; pub use metrics::ContractSyncMetrics;
pub use outbox::*;
use crate::settings::IndexSettings; use crate::chains::IndexSettings;
mod interchain_gas; mod interchain_gas;
/// Tools for working with message continuity. /// Tools for working with message continuity.
pub mod last_message; pub mod last_message;
mod mailbox;
mod metrics; mod metrics;
mod outbox;
mod schema; mod schema;
/// Entity that drives the syncing of an agent's db with on-chain data. /// Entity that drives the syncing of an agent's db with on-chain data.
/// Extracts chain-specific data (emitted checkpoints, messages, etc) from an /// Extracts chain-specific data (emitted checkpoints, messages, etc) from an
/// `indexer` and fills the agent's db with this data. A CachingOutbox or /// `indexer` and fills the agent's db with this data. A CachingMailbox
/// CachingInbox will use a contract sync to spawn syncing tasks to keep the /// will use a contract sync to spawn syncing tasks to keep the db up-to-date.
/// db up-to-date.
#[derive(Debug)] #[derive(Debug)]
pub struct ContractSync<I> { pub struct ContractSync<I> {
chain_name: String, chain_name: String,

@ -10,7 +10,8 @@ use tracing::{info_span, Instrument};
use abacus_core::db::AbacusDB; use abacus_core::db::AbacusDB;
use abacus_core::{AbacusContract, InterchainGasPaymaster, InterchainGasPaymasterIndexer}; use abacus_core::{AbacusContract, InterchainGasPaymaster, InterchainGasPaymasterIndexer};
use crate::{ContractSync, ContractSyncMetrics, IndexSettings}; use crate::chains::IndexSettings;
use crate::{ContractSync, ContractSyncMetrics};
/// Caching InterchainGasPaymaster type /// Caching InterchainGasPaymaster type
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

@ -21,13 +21,9 @@ pub use agent::*;
#[macro_use] #[macro_use]
pub mod macros; pub mod macros;
/// outbox type /// mailbox type
mod outbox; mod mailbox;
pub use outbox::*; pub use mailbox::*;
/// inbox type
mod inbox;
pub use inbox::*;
mod metrics; mod metrics;
pub use metrics::*; pub use metrics::*;

@ -0,0 +1,148 @@
use std::fmt::Debug;
use std::sync::Arc;
use async_trait::async_trait;
use ethers::core::types::H256;
use ethers::types::U256;
use eyre::Result;
use futures_util::future::select_all;
use tokio::task::JoinHandle;
use tracing::instrument::Instrumented;
use tracing::{info_span, Instrument};
use abacus_core::db::AbacusDB;
use abacus_core::{
AbacusContract, AbacusMessage, ChainCommunicationError, Checkpoint, Mailbox, MailboxIndexer,
TxCostEstimate, TxOutcome,
};
use crate::chains::IndexSettings;
use crate::{ContractSync, ContractSyncMetrics};
/// Caching Mailbox type
#[derive(Debug, Clone)]
pub struct CachingMailbox {
mailbox: Arc<dyn Mailbox>,
db: AbacusDB,
indexer: Arc<dyn MailboxIndexer>,
}
impl std::fmt::Display for CachingMailbox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl CachingMailbox {
/// Instantiate new CachingMailbox
pub fn new(mailbox: Arc<dyn Mailbox>, db: AbacusDB, indexer: Arc<dyn MailboxIndexer>) -> Self {
Self {
mailbox,
db,
indexer,
}
}
/// Return handle on mailbox object
pub fn mailbox(&self) -> &Arc<dyn Mailbox> {
&self.mailbox
}
/// Return handle on AbacusDB
pub fn db(&self) -> &AbacusDB {
&self.db
}
/// Spawn a task that syncs the CachingMailbox's db with the on-chain event
/// data
pub fn sync(
&self,
index_settings: IndexSettings,
metrics: ContractSyncMetrics,
) -> Instrumented<JoinHandle<Result<()>>> {
let span = info_span!("MailboxContractSync", self = %self);
let sync = ContractSync::new(
self.mailbox.chain_name().into(),
self.db.clone(),
self.indexer.clone(),
index_settings,
metrics,
);
tokio::spawn(async move {
let tasks = vec![sync.sync_dispatched_messages()];
let (_, _, remaining) = select_all(tasks).await;
for task in remaining.into_iter() {
cancel_task!(task);
}
Ok(())
})
.instrument(span)
}
}
#[async_trait]
impl Mailbox for CachingMailbox {
fn local_domain(&self) -> u32 {
self.mailbox.local_domain()
}
fn local_domain_hash(&self) -> H256 {
self.mailbox.local_domain_hash()
}
async fn count(&self) -> Result<u32, ChainCommunicationError> {
self.mailbox.count().await
}
/// Fetch the status of a message
async fn delivered(&self, id: H256) -> Result<bool, ChainCommunicationError> {
self.mailbox.delivered(id).await
}
async fn latest_checkpoint(
&self,
maybe_lag: Option<u64>,
) -> Result<Checkpoint, ChainCommunicationError> {
self.mailbox.latest_checkpoint(maybe_lag).await
}
/// Fetch the current default interchain security module value
async fn default_ism(&self) -> Result<H256, ChainCommunicationError> {
self.mailbox.default_ism().await
}
async fn process(
&self,
message: &AbacusMessage,
metadata: &[u8],
tx_gas_limit: Option<U256>,
) -> Result<TxOutcome, ChainCommunicationError> {
self.mailbox.process(message, metadata, tx_gas_limit).await
}
async fn process_estimate_costs(
&self,
message: &AbacusMessage,
metadata: &[u8],
) -> Result<TxCostEstimate> {
self.mailbox.process_estimate_costs(message, metadata).await
}
fn process_calldata(&self, message: &AbacusMessage, metadata: &[u8]) -> Vec<u8> {
self.mailbox.process_calldata(message, metadata)
}
}
impl AbacusContract for CachingMailbox {
fn chain_name(&self) -> &str {
self.mailbox.chain_name()
}
fn address(&self) -> H256 {
self.mailbox.address()
}
}

@ -43,7 +43,7 @@ pub struct CoreMetrics {
span_durations: HistogramVec, span_durations: HistogramVec,
span_events: IntCounterVec, span_events: IntCounterVec,
last_known_message_leaf_index: IntGaugeVec, last_known_message_nonce: IntGaugeVec,
validator_checkpoint_index: IntGaugeVec, validator_checkpoint_index: IntGaugeVec,
submitter_queue_length: IntGaugeVec, submitter_queue_length: IntGaugeVec,
submitter_queue_duration_histogram: HistogramVec, submitter_queue_duration_histogram: HistogramVec,
@ -103,9 +103,9 @@ impl CoreMetrics {
registry registry
)?; )?;
let last_known_message_leaf_index = register_int_gauge_vec_with_registry!( let last_known_message_nonce = register_int_gauge_vec_with_registry!(
opts!( opts!(
namespaced!("last_known_message_leaf_index"), namespaced!("last_known_message_nonce"),
"Last known message leaf index", "Last known message leaf index",
const_labels_ref const_labels_ref
), ),
@ -186,7 +186,7 @@ impl CoreMetrics {
span_durations, span_durations,
span_events, span_events,
last_known_message_leaf_index, last_known_message_nonce,
validator_checkpoint_index, validator_checkpoint_index,
submitter_queue_length, submitter_queue_length,
@ -315,8 +315,8 @@ impl CoreMetrics {
/// has gotten to but not attempted to send it. /// has gotten to but not attempted to send it.
/// - `message_processed`: When a leaf index was processed as part of the /// - `message_processed`: When a leaf index was processed as part of the
/// MessageProcessor loop. /// MessageProcessor loop.
pub fn last_known_message_leaf_index(&self) -> IntGaugeVec { pub fn last_known_message_nonce(&self) -> IntGaugeVec {
self.last_known_message_leaf_index.clone() self.last_known_message_nonce.clone()
} }
/// Gauge for reporting the most recent validator checkpoint index /// Gauge for reporting the most recent validator checkpoint index
@ -373,7 +373,7 @@ impl CoreMetrics {
/// lifetime. /// lifetime.
/// ///
/// The value of /// The value of
/// `abacus_last_known_message_leaf_index{phase=message_processed}` /// `abacus_last_known_message_nonce{phase=message_processed}`
/// should refer to the maximum leaf index value we ever successfully /// should refer to the maximum leaf index value we ever successfully
/// delivered. Since deliveries can happen out-of-index-order, we /// delivered. Since deliveries can happen out-of-index-order, we
/// separately track this counter referring to the number of successfully /// separately track this counter referring to the number of successfully

@ -1,171 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
use async_trait::async_trait;
use ethers::core::types::H256;
use eyre::Result;
use futures_util::future::select_all;
use tokio::task::JoinHandle;
use tokio::time::{sleep, Duration};
use tracing::instrument::Instrumented;
use tracing::{info_span, Instrument};
use abacus_core::db::AbacusDB;
use abacus_core::{
AbacusCommon, AbacusContract, ChainCommunicationError, Checkpoint, Message, Outbox,
OutboxEvents, OutboxIndexer, OutboxState, RawCommittedMessage, TxOutcome,
};
use crate::{ContractSync, ContractSyncMetrics, IndexSettings};
/// Caching Outbox type
#[derive(Debug, Clone)]
pub struct CachingOutbox {
outbox: Arc<dyn Outbox>,
db: AbacusDB,
indexer: Arc<dyn OutboxIndexer>,
}
impl std::fmt::Display for CachingOutbox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl CachingOutbox {
/// Instantiate new CachingOutbox
pub fn new(outbox: Arc<dyn Outbox>, db: AbacusDB, indexer: Arc<dyn OutboxIndexer>) -> Self {
Self {
outbox,
db,
indexer,
}
}
/// Return handle on outbox object
pub fn outbox(&self) -> &Arc<dyn Outbox> {
&self.outbox
}
/// Return handle on AbacusDB
pub fn db(&self) -> &AbacusDB {
&self.db
}
/// Spawn a task that syncs the CachingOutbox's db with the on-chain event
/// data
pub fn sync(
&self,
index_settings: IndexSettings,
metrics: ContractSyncMetrics,
) -> Instrumented<JoinHandle<Result<()>>> {
let span = info_span!("OutboxContractSync", self = %self);
let sync = ContractSync::new(
self.outbox.chain_name().into(),
self.db.clone(),
self.indexer.clone(),
index_settings,
metrics,
);
tokio::spawn(async move {
let tasks = vec![sync.sync_outbox_messages()];
let (_, _, remaining) = select_all(tasks).await;
for task in remaining.into_iter() {
cancel_task!(task);
}
Ok(())
})
.instrument(span)
}
}
#[async_trait]
impl Outbox for CachingOutbox {
async fn state(&self) -> Result<OutboxState, ChainCommunicationError> {
self.outbox.state().await
}
async fn count(&self) -> Result<u32, ChainCommunicationError> {
self.outbox.count().await
}
async fn dispatch(&self, message: &Message) -> Result<TxOutcome, ChainCommunicationError> {
self.outbox.dispatch(message).await
}
async fn cache_checkpoint(&self) -> Result<TxOutcome, ChainCommunicationError> {
self.outbox.cache_checkpoint().await
}
async fn latest_cached_root(&self) -> Result<H256, ChainCommunicationError> {
self.outbox.latest_cached_root().await
}
async fn latest_cached_checkpoint(&self) -> Result<Checkpoint, ChainCommunicationError> {
self.outbox.latest_cached_checkpoint().await
}
async fn latest_checkpoint(
&self,
maybe_lag: Option<u64>,
) -> Result<Checkpoint, ChainCommunicationError> {
self.outbox.latest_checkpoint(maybe_lag).await
}
}
#[async_trait]
impl OutboxEvents for CachingOutbox {
#[tracing::instrument(err, skip(self))]
async fn raw_message_by_leaf(
&self,
leaf: H256,
) -> Result<Option<RawCommittedMessage>, ChainCommunicationError> {
loop {
if let Some(message) = self.db.message_by_leaf(leaf)? {
return Ok(Some(message));
}
sleep(Duration::from_millis(500)).await;
}
}
async fn leaf_by_tree_index(
&self,
tree_index: usize,
) -> Result<Option<H256>, ChainCommunicationError> {
loop {
if let Some(leaf) = self.db.leaf_by_leaf_index(tree_index as u32)? {
return Ok(Some(leaf));
}
sleep(Duration::from_millis(500)).await;
}
}
}
impl AbacusContract for CachingOutbox {
fn chain_name(&self) -> &str {
self.outbox.chain_name()
}
fn address(&self) -> H256 {
self.outbox.address()
}
}
#[async_trait]
impl AbacusCommon for CachingOutbox {
fn local_domain(&self) -> u32 {
self.outbox.local_domain()
}
async fn status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError> {
self.outbox.status(txid).await
}
async fn validator_manager(&self) -> Result<H256, ChainCommunicationError> {
self.outbox.validator_manager().await
}
}

@ -2,13 +2,11 @@ use ethers::signers::Signer;
use serde::Deserialize; use serde::Deserialize;
use abacus_core::{ use abacus_core::{
AbacusAbi, ContractLocator, Inbox, InboxValidatorManager, InterchainGasPaymaster, Outbox, AbacusAbi, ContractLocator, InterchainGasPaymaster, Mailbox, MultisigIsm, Signers,
Signers,
}; };
use abacus_ethereum::{ use abacus_ethereum::{
Connection, EthereumInboxAbi, EthereumInterchainGasPaymasterAbi, EthereumOutboxAbi, Connection, EthereumInterchainGasPaymasterAbi, EthereumMailboxAbi, EthereumMultisigIsmAbi,
InboxBuilder, InboxValidatorManagerBuilder, InterchainGasPaymasterBuilder, InterchainGasPaymasterBuilder, MailboxBuilder, MakeableWithProvider, MultisigIsmBuilder,
MakeableWithProvider, OutboxBuilder,
}; };
use ethers_prometheus::middleware::{ use ethers_prometheus::middleware::{
ChainInfo, ContractInfo, PrometheusMiddlewareConf, WalletInfo, ChainInfo, ContractInfo, PrometheusMiddlewareConf, WalletInfo,
@ -54,28 +52,49 @@ pub struct GelatoConf {
/// Addresses for outbox chain contracts /// Addresses for outbox chain contracts
#[derive(Clone, Debug, Deserialize, Default)] #[derive(Clone, Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct OutboxAddresses { pub struct CoreContractAddresses {
/// Address of the Outbox contract /// Address of the mailbox contract
pub outbox: String, pub mailbox: String,
/// Address of the MultisigIsm contract
pub multisig_ism: String,
/// Address of the InterchainGasPaymaster contract /// Address of the InterchainGasPaymaster contract
pub interchain_gas_paymaster: Option<String>, pub interchain_gas_paymaster: String,
} }
/// Addresses for inbox chain contracts /// Outbox indexing settings
#[derive(Clone, Debug, Deserialize, Default)] #[derive(Debug, Deserialize, Default, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct InboxAddresses { pub struct IndexSettings {
/// Address of the Inbox contract /// The height at which to start indexing the Outbox contract
pub inbox: String, pub from: Option<String>,
/// Address of the InboxValidatorManager contract /// The number of blocks to query at once at which to start indexing the
pub validator_manager: String, /// Outbox contract
pub chunk: Option<String>,
}
impl IndexSettings {
/// Get the `from` setting
pub fn from(&self) -> u32 {
self.from
.as_ref()
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or_default()
}
/// Get the `chunk_size` setting
pub fn chunk_size(&self) -> u32 {
self.chunk
.as_ref()
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(1999)
}
} }
/// A chain setup is a domain ID, an address on that chain (where the outbox or /// A chain setup is a domain ID, an address on that chain (where the outbox or
/// inbox is deployed) and details for connecting to the chain API. /// inbox is deployed) and details for connecting to the chain API.
#[derive(Clone, Debug, Deserialize, Default)] #[derive(Clone, Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ChainSetup<T> { pub struct ChainSetup {
/// Chain name /// Chain name
pub name: String, pub name: String,
/// Chain domain identifier /// Chain domain identifier
@ -83,24 +102,24 @@ pub struct ChainSetup<T> {
/// Number of blocks until finality /// Number of blocks until finality
pub finality_blocks: String, pub finality_blocks: String,
/// Addresses of contracts on the chain /// Addresses of contracts on the chain
pub addresses: T, pub addresses: CoreContractAddresses,
/// The chain connection details /// The chain connection details
#[serde(flatten)] #[serde(flatten)]
pub chain: ChainConf, pub chain: ChainConf,
/// How transactions to this chain are submitted. /// How transactions to this chain are submitted.
#[serde(default)] #[serde(default)]
pub txsubmission: TransactionSubmissionType, pub txsubmission: TransactionSubmissionType,
/// Set this key to disable the inbox. Does nothing for outboxes.
#[serde(default)]
pub disabled: Option<String>,
/// Configure chain-specific metrics information. This will automatically /// Configure chain-specific metrics information. This will automatically
/// add all contract addresses but will not override any set explicitly. /// add all contract addresses but will not override any set explicitly.
/// Use `metrics_conf()` to get the metrics. /// Use `metrics_conf()` to get the metrics.
#[serde(default)] #[serde(default)]
pub metrics_conf: PrometheusMiddlewareConf, pub metrics_conf: PrometheusMiddlewareConf,
/// Settings for event indexing
#[serde(default)]
pub index: IndexSettings,
} }
impl<T> ChainSetup<T> { impl ChainSetup {
/// Get the number of blocks until finality /// Get the number of blocks until finality
pub fn finality_blocks(&self) -> u32 { pub fn finality_blocks(&self) -> u32 {
self.finality_blocks self.finality_blocks
@ -109,32 +128,20 @@ impl<T> ChainSetup<T> {
} }
} }
impl ChainSetup<OutboxAddresses> { impl ChainSetup {
/// Try to convert the chain setting into an Outbox contract /// Try to convert the chain setting into a Mailbox contract
pub async fn try_into_outbox( pub async fn try_into_mailbox(
&self, &self,
signer: Option<Signers>, signer: Option<Signers>,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn Outbox>> { ) -> eyre::Result<Box<dyn Mailbox>> {
match &self.chain { self.try_into_contract(
ChainConf::Ethereum(conf) => Ok(OutboxBuilder {} signer,
.make_with_connection( metrics,
conf.clone(), MailboxBuilder {},
&ContractLocator { self.addresses.mailbox.clone(),
chain_name: self.name.clone(), )
domain: self.domain.parse().expect("invalid uint"), .await
address: self
.addresses
.outbox
.parse::<ethers::types::Address>()?
.into(),
},
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), self.metrics_conf())),
)
.await?),
}
} }
/// Try to convert the chain setting into an InterchainGasPaymaster contract /// Try to convert the chain setting into an InterchainGasPaymaster contract
@ -142,109 +149,48 @@ impl ChainSetup<OutboxAddresses> {
&self, &self,
signer: Option<Signers>, signer: Option<Signers>,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<Option<Box<dyn InterchainGasPaymaster>>> { ) -> eyre::Result<Box<dyn InterchainGasPaymaster>> {
let paymaster_address = if let Some(address) = &self.addresses.interchain_gas_paymaster { self.try_into_contract(
address signer,
} else { metrics,
return Ok(None); InterchainGasPaymasterBuilder {},
}; self.addresses.interchain_gas_paymaster.clone(),
match &self.chain { )
ChainConf::Ethereum(conf) => Ok(Some( .await
InterchainGasPaymasterBuilder {}
.make_with_connection(
conf.clone(),
&ContractLocator {
chain_name: self.name.clone(),
domain: self.domain.parse().expect("invalid uint"),
address: paymaster_address.parse::<ethers::types::Address>()?.into(),
},
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), self.metrics_conf())),
)
.await?,
)),
}
} }
/// Get a clone of the metrics conf with correctly configured contract /// Try to convert the chain setting into a Multisig Ism contract
/// information. pub async fn try_into_multisig_ism(
pub fn metrics_conf(&self) -> PrometheusMiddlewareConf {
let mut cfg = self.metrics_conf.clone();
if cfg.chain.is_none() {
cfg.chain = Some(ChainInfo {
name: Some(self.name.clone()),
});
}
if let Ok(addr) = self.addresses.outbox.parse() {
cfg.contracts.entry(addr).or_insert_with(|| ContractInfo {
name: Some("outbox".into()),
functions: EthereumOutboxAbi::fn_map_owned(),
});
}
if let Some(igp) = &self.addresses.interchain_gas_paymaster {
if let Ok(addr) = igp.parse() {
cfg.contracts.entry(addr).or_insert_with(|| ContractInfo {
name: Some("igp".into()),
functions: EthereumInterchainGasPaymasterAbi::fn_map_owned(),
});
}
}
cfg
}
}
impl ChainSetup<InboxAddresses> {
/// Try to convert the chain setting into an inbox contract
pub async fn try_into_inbox(
&self, &self,
signer: Option<Signers>, signer: Option<Signers>,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn Inbox>> { ) -> eyre::Result<Box<dyn MultisigIsm>> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer); self.try_into_contract(
match &self.chain { signer,
ChainConf::Ethereum(conf) => Ok(InboxBuilder {} metrics,
.make_with_connection( MultisigIsmBuilder {},
conf.clone(), self.addresses.multisig_ism.clone(),
&ContractLocator { )
chain_name: self.name.clone(), .await
domain: self.domain.parse().expect("invalid uint"),
address: self
.addresses
.inbox
.parse::<ethers::types::Address>()?
.into(),
},
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), metrics_conf)),
)
.await?),
}
} }
/// Try to convert the chain setting into an InboxValidatorManager contract /// Try to convert the chain setting into a contract
pub async fn try_into_inbox_validator_manager( pub async fn try_into_contract<T: MakeableWithProvider>(
&self, &self,
signer: Option<Signers>, signer: Option<Signers>,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn InboxValidatorManager>> { builder: T,
let inbox_address = self.addresses.inbox.parse::<ethers::types::Address>()?; address: String,
) -> eyre::Result<T::Output> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer); let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
match &self.chain { match &self.chain {
ChainConf::Ethereum(conf) => Ok(InboxValidatorManagerBuilder { inbox_address } ChainConf::Ethereum(conf) => Ok(builder
.make_with_connection( .make_with_connection(
conf.clone(), conf.clone(),
&ContractLocator { &ContractLocator {
chain_name: self.name.clone(), chain_name: self.name.clone(),
domain: self.domain.parse().expect("invalid uint"), domain: self.domain.parse().expect("invalid uint"),
address: self address: address.parse::<ethers::types::Address>()?.into(),
.addresses
.validator_manager
.parse::<ethers::types::Address>()?
.into(),
}, },
signer, signer,
Some(|| metrics.json_rpc_client_metrics()), Some(|| metrics.json_rpc_client_metrics()),
@ -276,16 +222,23 @@ impl ChainSetup<InboxAddresses> {
name: Some(agent_name.into()), name: Some(agent_name.into()),
}); });
} }
if let Ok(addr) = self.addresses.inbox.parse() {
if let Ok(addr) = self.addresses.mailbox.parse() {
cfg.contracts.entry(addr).or_insert_with(|| ContractInfo {
name: Some("mailbox".into()),
functions: EthereumMailboxAbi::fn_map_owned(),
});
}
if let Ok(addr) = self.addresses.interchain_gas_paymaster.parse() {
cfg.contracts.entry(addr).or_insert_with(|| ContractInfo { cfg.contracts.entry(addr).or_insert_with(|| ContractInfo {
name: Some("inbox".into()), name: Some("igp".into()),
functions: EthereumInboxAbi::fn_map_owned(), functions: EthereumInterchainGasPaymasterAbi::fn_map_owned(),
}); });
} }
if let Ok(addr) = self.addresses.validator_manager.parse() { if let Ok(addr) = self.addresses.multisig_ism.parse() {
cfg.contracts.entry(addr).or_insert_with(|| ContractInfo { cfg.contracts.entry(addr).or_insert_with(|| ContractInfo {
name: Some("ivm".into()), name: Some("msm".into()),
functions: EthereumOutboxAbi::fn_map_owned(), functions: EthereumMultisigIsmAbi::fn_map_owned(),
}); });
} }
cfg cfg

@ -89,16 +89,13 @@ use tracing::instrument;
use abacus_core::{ use abacus_core::{
db::{AbacusDB, DB}, db::{AbacusDB, DB},
utils::HexString, utils::HexString,
AbacusContract, ContractLocator, InboxValidatorManager, InterchainGasPaymasterIndexer, InterchainGasPaymasterIndexer, MailboxIndexer, MultisigIsm, Signers,
OutboxIndexer, Signers,
}; };
use abacus_ethereum::{ use abacus_ethereum::{InterchainGasPaymasterIndexerBuilder, MailboxIndexerBuilder};
InterchainGasPaymasterIndexerBuilder, MakeableWithProvider, OutboxIndexerBuilder, pub use chains::{ChainConf, ChainSetup, CoreContractAddresses};
};
pub use chains::{ChainConf, ChainSetup, InboxAddresses, OutboxAddresses};
use crate::{settings::trace::TracingConfig, CachingInterchainGasPaymaster}; use crate::{settings::trace::TracingConfig, CachingInterchainGasPaymaster};
use crate::{AbacusAgentCore, CachingInbox, CachingOutbox, CoreMetrics, InboxContracts}; use crate::{AbacusAgentCore, CachingMailbox, CoreMetrics};
use self::chains::GelatoConf; use self::chains::GelatoConf;
@ -163,35 +160,6 @@ impl SignerConf {
} }
} }
/// Outbox indexing settings
#[derive(Debug, Deserialize, Default, Clone)]
#[serde(rename_all = "camelCase")]
pub struct IndexSettings {
/// The height at which to start indexing the Outbox contract
pub from: Option<String>,
/// The number of blocks to query at once at which to start indexing the
/// Outbox contract
pub chunk: Option<String>,
}
impl IndexSettings {
/// Get the `from` setting
pub fn from(&self) -> u32 {
self.from
.as_ref()
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or_default()
}
/// Get the `chunk_size` setting
pub fn chunk_size(&self) -> u32 {
self.chunk
.as_ref()
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(1999)
}
}
/// Settings. Usually this should be treated as a base config and used as /// Settings. Usually this should be treated as a base config and used as
/// follows: /// follows:
/// ///
@ -223,13 +191,8 @@ pub struct Settings {
pub db: String, pub db: String,
/// Port to listen for prometheus scrape requests /// Port to listen for prometheus scrape requests
pub metrics: Option<String>, pub metrics: Option<String>,
/// Settings for the outbox indexer /// Configuration for contracts on each chain
#[serde(default)] pub chains: HashMap<String, ChainSetup>,
pub index: IndexSettings,
/// Configurations for contracts on the outbox chain
pub outbox: ChainSetup<OutboxAddresses>,
/// Configurations for contracts on inbox chains
pub inboxes: HashMap<String, ChainSetup<InboxAddresses>>,
/// The tracing configuration /// The tracing configuration
pub tracing: TracingConfig, pub tracing: TracingConfig,
/// Transaction signers /// Transaction signers
@ -245,9 +208,7 @@ impl Settings {
Self { Self {
db: self.db.clone(), db: self.db.clone(),
metrics: self.metrics.clone(), metrics: self.metrics.clone(),
index: self.index.clone(), chains: self.chains.clone(),
outbox: self.outbox.clone(),
inboxes: self.inboxes.clone(),
tracing: self.tracing.clone(), tracing: self.tracing.clone(),
signers: self.signers.clone(), signers: self.signers.clone(),
gelato: self.gelato.clone(), gelato: self.gelato.clone(),
@ -261,180 +222,155 @@ impl Settings {
self.signers.get(name)?.try_into_signer().await.ok() self.signers.get(name)?.try_into_signer().await.ok()
} }
/// Try to get a map of inbox name -> inbox contracts /// Try to get a map of chain name -> mailbox contract
pub async fn try_inbox_contracts( pub async fn try_into_mailboxes(
&self, &self,
db: DB, db: DB,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<HashMap<String, InboxContracts>> { chain_names: &[&str],
) -> eyre::Result<HashMap<String, CachingMailbox>> {
let mut result = HashMap::new(); let mut result = HashMap::new();
for (k, v) in self.inboxes.iter().filter(|(_, v)| { for &chain_name in chain_names {
!v.disabled if let Some(x) = self.chains.get(chain_name) {
.as_ref() let mailbox = self.try_caching_mailbox(x, db.clone(), metrics).await?;
.and_then(|d| d.parse::<bool>().ok()) result.insert(chain_name.into(), mailbox);
.unwrap_or(false) } else {
}) { bail!("No chain setup found for {}", chain_name)
if k != &v.name {
bail!(
"Inbox key does not match inbox name:\n key: {} name: {}",
k,
v.name
);
} }
let caching_inbox = self.try_caching_inbox(v, db.clone(), metrics).await?;
let validator_manager = self.try_inbox_validator_manager(v, metrics).await?;
result.insert(
v.name.clone(),
InboxContracts {
inbox: caching_inbox,
validator_manager: validator_manager.into(),
},
);
} }
Ok(result) Ok(result)
} }
/// Try to get a CachingInbox /// Try to get a map of chain name -> interchain gas paymaster contract
async fn try_caching_inbox( pub async fn try_into_interchain_gas_paymasters(
&self, &self,
chain_setup: &ChainSetup<InboxAddresses>,
db: DB, db: DB,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<CachingInbox> { chain_names: &[&str],
let signer = self.get_signer(&chain_setup.name).await; ) -> eyre::Result<HashMap<String, CachingInterchainGasPaymaster>> {
let inbox = chain_setup.try_into_inbox(signer, metrics).await?; let mut result = HashMap::new();
let abacus_db = AbacusDB::new(inbox.chain_name(), db); for &chain_name in chain_names {
Ok(CachingInbox::new(inbox.into(), abacus_db)) if let Some(x) = self.chains.get(chain_name) {
let mailbox = self
.try_caching_interchain_gas_paymaster(x, db.clone(), metrics)
.await?;
result.insert(chain_name.into(), mailbox);
} else {
bail!("No chain setup found for {}", chain_name)
}
}
Ok(result)
} }
/// Try to get an InboxValidatorManager /// Try to get a map of chain name -> multisig ism contract
async fn try_inbox_validator_manager( pub async fn try_into_multisig_isms(
&self, &self,
chain_setup: &ChainSetup<InboxAddresses>, db: DB,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn InboxValidatorManager>> { chain_names: &[&str],
let signer = self.get_signer(&chain_setup.name).await; ) -> eyre::Result<HashMap<String, Arc<dyn MultisigIsm>>> {
let mut result: HashMap<String, Arc<dyn MultisigIsm>> = HashMap::new();
chain_setup for &chain_name in chain_names {
.try_into_inbox_validator_manager(signer, metrics) if let Some(x) = self.chains.get(chain_name) {
.await let multisig_ism = self.try_multisig_ism(x, db.clone(), metrics).await?;
result.insert(chain_name.into(), multisig_ism.into());
} else {
bail!("No chain setup found for {}", chain_name)
}
}
Ok(result)
} }
/// Try to get a CachingOutbox /// Try to get a CachingMailbox
pub async fn try_caching_outbox( async fn try_caching_mailbox(
&self, &self,
chain_setup: &ChainSetup,
db: DB, db: DB,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<CachingOutbox> { ) -> eyre::Result<CachingMailbox> {
let signer = self.get_signer(&self.outbox.name).await; let signer = self.get_signer(&chain_setup.name).await;
let outbox = self.outbox.try_into_outbox(signer, metrics).await?; let mailbox = chain_setup.try_into_mailbox(signer, metrics).await?;
let indexer = self.try_outbox_indexer(metrics).await?; let indexer = self.try_mailbox_indexer(metrics, chain_setup).await?;
let abacus_db = AbacusDB::new(outbox.chain_name(), db); let abacus_db = AbacusDB::new(&chain_setup.name, db);
Ok(CachingOutbox::new(outbox.into(), abacus_db, indexer.into())) Ok(CachingMailbox::new(
mailbox.into(),
abacus_db,
indexer.into(),
))
} }
/// Try to get a CachingInterchainGasPaymaster /// Try to get a CachingInterchainGasPaymaster
pub async fn try_caching_interchain_gas_paymaster( async fn try_caching_interchain_gas_paymaster(
&self, &self,
chain_setup: &ChainSetup,
db: DB, db: DB,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<Option<CachingInterchainGasPaymaster>> { ) -> eyre::Result<CachingInterchainGasPaymaster> {
let signer = self.get_signer(&self.outbox.name).await; let signer = self.get_signer(&chain_setup.name).await;
match self let interchain_gas_paymaster = chain_setup
.outbox
.try_into_interchain_gas_paymaster(signer, metrics) .try_into_interchain_gas_paymaster(signer, metrics)
.await? .await?;
{ let indexer = self
Some(paymaster) => { .try_interchain_gas_paymaster_indexer(metrics, chain_setup)
let indexer = self.try_interchain_gas_paymaster_indexer(metrics).await?; .await?;
let abacus_db = AbacusDB::new(paymaster.chain_name(), db); let abacus_db = AbacusDB::new(&chain_setup.name, db);
Ok(Some(CachingInterchainGasPaymaster::new( Ok(CachingInterchainGasPaymaster::new(
paymaster.into(), interchain_gas_paymaster.into(),
abacus_db, abacus_db,
indexer.into(), indexer.into(),
))) ))
}
None => Ok(None),
}
} }
/// Try to get an indexer object for a given outbox /// Try to get a MultisigIsm
pub async fn try_outbox_indexer_from_config( async fn try_multisig_ism(
&self, &self,
chain_setup: &ChainSetup,
_db: DB,
metrics: &CoreMetrics, metrics: &CoreMetrics,
outbox: &ChainSetup<OutboxAddresses>, ) -> eyre::Result<Box<dyn MultisigIsm>> {
) -> eyre::Result<Box<dyn OutboxIndexer>> { let signer = self.get_signer(&chain_setup.name).await;
match &outbox.chain { let multisig_ism = chain_setup.try_into_multisig_ism(signer, metrics).await?;
ChainConf::Ethereum(conn) => Ok(OutboxIndexerBuilder { Ok(multisig_ism)
finality_blocks: outbox.finality_blocks(),
}
.make_with_connection(
conn.clone(),
&ContractLocator {
chain_name: outbox.name.clone(),
domain: outbox.domain.parse().expect("invalid uint"),
address: outbox
.addresses
.outbox
.parse::<ethers::types::Address>()?
.into(),
},
self.get_signer(&outbox.name).await,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), outbox.metrics_conf())),
)
.await?),
}
} }
/// Try to get an indexer object for the outbox /// Try to get an indexer object for a given mailbox
pub async fn try_outbox_indexer( pub async fn try_mailbox_indexer(
&self, &self,
metrics: &CoreMetrics, metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn OutboxIndexer>> { chain_setup: &ChainSetup,
self.try_outbox_indexer_from_config(metrics, &self.outbox) ) -> eyre::Result<Box<dyn MailboxIndexer>> {
chain_setup
.try_into_contract(
self.get_signer(&chain_setup.name).await,
metrics,
MailboxIndexerBuilder {
finality_blocks: chain_setup.finality_blocks(),
},
chain_setup.addresses.mailbox.clone(),
)
.await .await
} }
/// Try to get an indexer object for an interchain gas paymaster. /// Try to get an indexer object for a given interchain gas paymaster
/// This function is only expected to be called when it's already been async fn try_interchain_gas_paymaster_indexer(
/// confirmed that the interchain gas paymaster address was provided in
/// settings.
pub async fn try_interchain_gas_paymaster_indexer(
&self, &self,
metrics: &CoreMetrics, metrics: &CoreMetrics,
chain_setup: &ChainSetup,
) -> eyre::Result<Box<dyn InterchainGasPaymasterIndexer>> { ) -> eyre::Result<Box<dyn InterchainGasPaymasterIndexer>> {
match &self.outbox.chain { chain_setup
ChainConf::Ethereum(conn) => Ok(InterchainGasPaymasterIndexerBuilder { .try_into_contract(
outbox_address: self self.get_signer(&chain_setup.name).await,
.outbox metrics,
.addresses InterchainGasPaymasterIndexerBuilder {
.outbox mailbox_address: chain_setup
.parse::<ethers::types::Address>()?,
from_height: self.index.from(),
chunk_size: self.index.chunk_size(),
finality_blocks: self.outbox.finality_blocks(),
}
.make_with_connection(
conn.clone(),
&ContractLocator {
chain_name: self.outbox.name.clone(),
domain: self.outbox.domain.parse().expect("invalid uint"),
address: self
.outbox
.addresses .addresses
.interchain_gas_paymaster .mailbox
.as_ref() .parse::<ethers::types::Address>()?,
.expect("interchain_gas_paymaster not provided") finality_blocks: chain_setup.finality_blocks(),
.parse::<ethers::types::Address>()?
.into(),
}, },
self.get_signer(&self.outbox.name).await, chain_setup.addresses.interchain_gas_paymaster.clone(),
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), self.outbox.metrics_conf())),
) )
.await?), .await
}
} }
/// Create the core metrics from the settings given the name of the agent. /// Create the core metrics from the settings given the name of the agent.
@ -453,27 +389,32 @@ impl Settings {
pub async fn try_into_abacus_core( pub async fn try_into_abacus_core(
&self, &self,
metrics: Arc<CoreMetrics>, metrics: Arc<CoreMetrics>,
parse_inboxes: bool, chain_names: Option<Vec<&str>>,
) -> eyre::Result<AbacusAgentCore> { ) -> eyre::Result<AbacusAgentCore> {
let db = DB::from_path(&self.db)?; let db = DB::from_path(&self.db)?;
let outbox = self.try_caching_outbox(db.clone(), &metrics).await?;
let interchain_gas_paymaster = self
.try_caching_interchain_gas_paymaster(db.clone(), &metrics)
.await?;
let inbox_contracts = if parse_inboxes { // If not provided, default to using every chain listed in self.chains.
self.try_inbox_contracts(db.clone(), &metrics).await? let chain_names = match chain_names {
} else { Some(x) => x,
HashMap::new() None => Vec::from_iter(self.chains.keys().map(String::as_str)),
}; };
let mailboxes = self
.try_into_mailboxes(db.clone(), &metrics, chain_names.as_slice())
.await?;
let interchain_gas_paymasters = self
.try_into_interchain_gas_paymasters(db.clone(), &metrics, chain_names.as_slice())
.await?;
let multisig_isms = self
.try_into_multisig_isms(db.clone(), &metrics, chain_names.as_slice())
.await?;
Ok(AbacusAgentCore { Ok(AbacusAgentCore {
outbox, mailboxes,
inboxes: inbox_contracts, interchain_gas_paymasters,
interchain_gas_paymaster, multisig_isms,
db, db,
metrics, metrics,
indexer: self.index.clone(),
settings: self.clone(), settings: self.clone(),
}) })
} }

@ -191,7 +191,6 @@ pub fn domain_id_from_name(name: &'static str) -> Option<u32> {
mod tests { mod tests {
use abacus_base::Settings; use abacus_base::Settings;
use config::{Config, File, FileFormat}; use config::{Config, File, FileFormat};
use num_traits::identities::Zero;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::path::Path; use std::path::Path;
@ -216,9 +215,7 @@ mod tests {
const BLACKLISTED_DIRS: &[&str] = &[ const BLACKLISTED_DIRS: &[&str] = &[
// Ignore only-local names of fake chains used by // Ignore only-local names of fake chains used by
// e.g. test suites. // e.g. test suites.
"test/test1_config.json", "test/test_config.json",
"test/test2_config.json",
"test/test3_config.json",
]; ];
fn is_blacklisted(path: &Path) -> bool { fn is_blacklisted(path: &Path) -> bool {
@ -280,35 +277,11 @@ mod tests {
.collect() .collect()
} }
fn outbox_chain_names() -> BTreeSet<String> { fn chain_name_domain_records() -> BTreeSet<ChainCoordinate> {
abacus_settings()
.iter()
.map(|x| x.outbox.name.clone())
.collect()
}
fn inbox_chain_names() -> BTreeSet<String> {
abacus_settings()
.iter()
.flat_map(|x: &Settings| x.inboxes.iter().map(|(k, _)| String::from(k)))
.collect()
}
fn outbox_name_domain_coords() -> BTreeSet<ChainCoordinate> {
abacus_settings()
.iter()
.map(|x| ChainCoordinate {
name: x.outbox.name.clone(),
domain: x.outbox.domain.parse().unwrap(),
})
.collect()
}
fn inbox_name_domain_records() -> BTreeSet<ChainCoordinate> {
abacus_settings() abacus_settings()
.iter() .iter()
.flat_map(|x: &Settings| { .flat_map(|x: &Settings| {
x.inboxes.iter().map(|(_, v)| ChainCoordinate { x.chains.iter().map(|(_, v)| ChainCoordinate {
name: v.name.clone(), name: v.name.clone(),
domain: v.domain.parse().unwrap(), domain: v.domain.parse().unwrap(),
}) })
@ -318,24 +291,6 @@ mod tests {
#[test] #[test]
fn agent_json_config_consistency_checks() { fn agent_json_config_consistency_checks() {
// Inbox/outbox and chain-presence equality
// (sanity checks that we have a complete list of
// relevant chains).
let inbox_chains = inbox_chain_names();
let outbox_chains = outbox_chain_names();
assert!(inbox_chains.symmetric_difference(&outbox_chains).count() == usize::zero());
assert_eq!(&inbox_chains.len(), &outbox_chains.len());
// Verify that the the outbox-associative chain-name
// and domain-number records agree with the
// inbox-associative chain-name and domain-number
// records, since our configuration data is /not/
// normalized and could drift out of sync.
let inbox_coords = inbox_name_domain_records();
let outbox_coords = outbox_name_domain_coords();
assert!(inbox_coords.symmetric_difference(&outbox_coords).count() == usize::zero());
assert_eq!(&inbox_coords.len(), &outbox_coords.len());
// TODO(webbhorn): Also verify with this functionality // TODO(webbhorn): Also verify with this functionality
// we have entries for all of the Gelato contract // we have entries for all of the Gelato contract
// addresses we need hardcoded in the binary for now. // addresses we need hardcoded in the binary for now.
@ -345,8 +300,8 @@ mod tests {
// by the macro `domain_and_chain` is complete // by the macro `domain_and_chain` is complete
// and in agreement with our on-disk json-based // and in agreement with our on-disk json-based
// configuration data. // configuration data.
let chain_coords = chain_name_domain_records();
for ChainCoordinate { name, domain } in inbox_coords.iter().chain(outbox_coords.iter()) { for ChainCoordinate { name, domain } in chain_coords.iter() {
assert_eq!( assert_eq!(
AbacusDomain::try_from(domain.to_owned()) AbacusDomain::try_from(domain.to_owned())
.unwrap() .unwrap()

@ -1,7 +1,7 @@
use crate::db::{DbError, TypedDB, DB}; use crate::db::{DbError, TypedDB, DB};
use crate::{ use crate::{
accumulator::merkle::Proof, AbacusMessage, CommittedMessage, Decode, InterchainGasPayment, accumulator::merkle::Proof, AbacusMessage, InterchainGasPayment, InterchainGasPaymentMeta,
InterchainGasPaymentMeta, InterchainGasPaymentWithMeta, RawCommittedMessage, InterchainGasPaymentWithMeta,
}; };
use ethers::core::types::{H256, U256}; use ethers::core::types::{H256, U256};
use eyre::Result; use eyre::Result;
@ -11,19 +11,16 @@ use tracing::{debug, info, trace};
use std::future::Future; use std::future::Future;
use std::time::Duration; use std::time::Duration;
use crate::db::iterator::PrefixIterator; static MESSAGE_ID: &str = "message_id_";
static LEAF_IDX: &str = "leaf_index_";
static LEAF: &str = "leaf_";
static PROOF: &str = "proof_"; static PROOF: &str = "proof_";
static MESSAGE: &str = "message_"; static MESSAGE: &str = "message_";
static LATEST_LEAF_INDEX: &str = "latest_known_leaf_index_"; static LATEST_NONCE: &str = "latest_known_nonce_";
static LATEST_LEAF_INDEX_FOR_DESTINATION: &str = "latest_known_leaf_index_for_destination_"; static LATEST_NONCE_FOR_DESTINATION: &str = "latest_known_nonce_for_destination_";
static LEAF_PROCESS_STATUS: &str = "leaf_process_status_"; static NONCE_PROCESSED: &str = "nonce_processed_";
static GAS_PAYMENT_FOR_LEAF: &str = "gas_payment_for_leaf_"; static GAS_PAYMENT_FOR_MESSAGE_ID: &str = "gas_payment_for_message_id_";
static GAS_PAYMENT_META_PROCESSED: &str = "gas_payment_meta_processed_"; static GAS_PAYMENT_META_PROCESSED: &str = "gas_payment_meta_processed_";
/// DB handle for storing data tied to a specific Outbox. /// DB handle for storing data tied to a specific Mailbox.
/// ///
/// Key structure: ```<entity>_<additional_prefix(es)>_<key>``` /// Key structure: ```<entity>_<additional_prefix(es)>_<key>```
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -56,182 +53,163 @@ impl AbacusDB {
} }
/// Store list of messages /// Store list of messages
pub fn store_messages(&self, messages: &[RawCommittedMessage]) -> Result<u32> { pub fn store_messages(&self, messages: &[AbacusMessage]) -> Result<u32> {
let mut latest_leaf_index: u32 = 0; let mut latest_nonce: u32 = 0;
for message in messages { for message in messages {
self.store_latest_message(message)?; self.store_latest_message(message)?;
let committed_message: CommittedMessage = message.try_into()?; latest_nonce = message.nonce;
info!(
leaf_index = &committed_message.leaf_index,
origin = &committed_message.message.origin,
destination = &committed_message.message.destination,
"Stored new message in db.",
);
latest_leaf_index = committed_message.leaf_index;
} }
Ok(latest_leaf_index) Ok(latest_nonce)
} }
/// Store a raw committed message building off of the latest leaf index /// Store a raw committed message building off of the latest leaf index
pub fn store_latest_message(&self, message: &RawCommittedMessage) -> Result<()> { pub fn store_latest_message(&self, message: &AbacusMessage) -> Result<()> {
// If this message is not building off the latest leaf index, log it. // If this message is not building off the latest nonce, log it.
if let Some(idx) = self.retrieve_latest_leaf_index()? { if let Some(nonce) = self.retrieve_latest_nonce()? {
if idx != message.leaf_index - 1 { if nonce != message.nonce - 1 {
debug!( debug!(
"Attempted to store message not building off latest leaf index. Latest leaf index: {}. Attempted leaf index: {}.", "Attempted to store message not building off latest nonce. Latest nonce: {}. Message nonce: {}.",
idx, nonce,
message.leaf_index, message.nonce,
) )
} }
} }
self.store_raw_committed_message(message) self.store_message(message)
} }
/// Store a raw committed message /// Store a raw committed message
/// ///
/// Keys --> Values: /// Keys --> Values:
/// - `leaf_index` --> `leaf` /// - `nonce` --> `id`
/// - `leaf` --> `message` /// - `id` --> `message`
pub fn store_raw_committed_message(&self, message: &RawCommittedMessage) -> Result<()> { pub fn store_message(&self, message: &AbacusMessage) -> Result<()> {
let parsed = AbacusMessage::read_from(&mut message.message.clone().as_slice())?; let id = message.id();
let leaf = message.leaf(); info!(
id = ?id,
debug!( nonce = &message.nonce,
leaf = ?leaf, origin = &message.origin,
destination = parsed.destination, destination = &message.destination,
leaf_index = message.leaf_index, "Storing new message in db.",
"storing raw committed message in db"
); );
self.store_leaf(message.leaf_index, parsed.destination, leaf)?; self.store_message_id(message.nonce, message.destination, id)?;
self.store_keyed_encodable(MESSAGE, &leaf, message)?; self.store_keyed_encodable(MESSAGE, &id, message)?;
Ok(()) Ok(())
} }
/// Store the latest known leaf_index /// Store the latest known nonce
/// ///
/// Key --> value: `LATEST_LEAF_INDEX` --> `leaf_index` /// Key --> value: `LATEST_NONCE` --> `nonce`
pub fn update_latest_leaf_index(&self, leaf_index: u32) -> Result<(), DbError> { pub fn update_latest_nonce(&self, nonce: u32) -> Result<(), DbError> {
if let Ok(Some(idx)) = self.retrieve_latest_leaf_index() { if let Ok(Some(n)) = self.retrieve_latest_nonce() {
if leaf_index <= idx { if nonce <= n {
return Ok(()); return Ok(());
} }
} }
self.store_encodable("", LATEST_LEAF_INDEX, &leaf_index) self.store_encodable("", LATEST_NONCE, &nonce)
} }
/// Retrieve the highest known leaf_index /// Retrieve the highest known nonce
pub fn retrieve_latest_leaf_index(&self) -> Result<Option<u32>, DbError> { pub fn retrieve_latest_nonce(&self) -> Result<Option<u32>, DbError> {
self.retrieve_decodable("", LATEST_LEAF_INDEX) self.retrieve_decodable("", LATEST_NONCE)
} }
/// Store the latest known leaf_index for a destination /// Store the latest known nonce for a destination
/// ///
/// Key --> value: `destination` --> `leaf_index` /// Key --> value: `destination` --> `nonce`
pub fn update_latest_leaf_index_for_destination( pub fn update_latest_nonce_for_destination(
&self, &self,
destination: u32, destination: u32,
leaf_index: u32, nonce: u32,
) -> Result<(), DbError> { ) -> Result<(), DbError> {
if let Ok(Some(idx)) = self.retrieve_latest_leaf_index_for_destination(destination) { if let Ok(Some(n)) = self.retrieve_latest_nonce_for_destination(destination) {
if leaf_index <= idx { if nonce <= n {
return Ok(()); return Ok(());
} }
} }
self.store_keyed_encodable(LATEST_LEAF_INDEX_FOR_DESTINATION, &destination, &leaf_index) self.store_keyed_encodable(LATEST_NONCE_FOR_DESTINATION, &destination, &nonce)
} }
/// Retrieve the highest known leaf_index for a destination /// Retrieve the highest known nonce for a destination
pub fn retrieve_latest_leaf_index_for_destination( pub fn retrieve_latest_nonce_for_destination(
&self, &self,
destination: u32, destination: u32,
) -> Result<Option<u32>, DbError> { ) -> Result<Option<u32>, DbError> {
self.retrieve_keyed_decodable(LATEST_LEAF_INDEX_FOR_DESTINATION, &destination) self.retrieve_keyed_decodable(LATEST_NONCE_FOR_DESTINATION, &destination)
} }
/// Store the leaf keyed by leaf_index /// Store the message id keyed by nonce
fn store_leaf(&self, leaf_index: u32, destination: u32, leaf: H256) -> Result<(), DbError> { fn store_message_id(&self, nonce: u32, destination: u32, id: H256) -> Result<(), DbError> {
debug!( debug!(
leaf_index, nonce,
leaf = ?leaf, id = ?id,
"storing leaf hash keyed by index" "storing leaf hash keyed by index"
); );
self.store_keyed_encodable(LEAF, &leaf_index, &leaf)?; self.store_keyed_encodable(MESSAGE_ID, &nonce, &id)?;
self.update_latest_leaf_index(leaf_index)?; self.update_latest_nonce(nonce)?;
self.update_latest_leaf_index_for_destination(destination, leaf_index) self.update_latest_nonce_for_destination(destination, nonce)
} }
/// Retrieve a raw committed message by its leaf hash /// Retrieve a message by its id
pub fn message_by_leaf(&self, leaf: H256) -> Result<Option<RawCommittedMessage>, DbError> { pub fn message_by_id(&self, id: H256) -> Result<Option<AbacusMessage>, DbError> {
self.retrieve_keyed_decodable(MESSAGE, &leaf) self.retrieve_keyed_decodable(MESSAGE, &id)
} }
/// Retrieve the leaf hash keyed by leaf index /// Retrieve the message id keyed by nonce
pub fn leaf_by_leaf_index(&self, leaf_index: u32) -> Result<Option<H256>, DbError> { pub fn message_id_by_nonce(&self, nonce: u32) -> Result<Option<H256>, DbError> {
self.retrieve_keyed_decodable(LEAF, &leaf_index) self.retrieve_keyed_decodable(MESSAGE_ID, &nonce)
} }
/// Retrieve a raw committed message by its leaf index /// Retrieve a message by its nonce
pub fn message_by_leaf_index( pub fn message_by_nonce(&self, nonce: u32) -> Result<Option<AbacusMessage>, DbError> {
&self, let id: Option<H256> = self.message_id_by_nonce(nonce)?;
index: u32, match id {
) -> Result<Option<RawCommittedMessage>, DbError> {
let leaf: Option<H256> = self.leaf_by_leaf_index(index)?;
match leaf {
None => Ok(None), None => Ok(None),
Some(leaf) => self.message_by_leaf(leaf), Some(id) => self.message_by_id(id),
} }
} }
/// Iterate over all leaves /// Store a proof by its nonce
pub fn leaf_iterator(&self) -> PrefixIterator<H256> {
PrefixIterator::new(self.0.as_ref().prefix_iterator(LEAF_IDX), LEAF_IDX.as_ref())
}
/// Store a proof by its leaf index
/// ///
/// Keys --> Values: /// Keys --> Values:
/// - `leaf_index` --> `proof` /// - `nonce` --> `proof`
pub fn store_proof(&self, leaf_index: u32, proof: &Proof) -> Result<(), DbError> { pub fn store_proof(&self, nonce: u32, proof: &Proof) -> Result<(), DbError> {
debug!(leaf_index, "storing proof in DB"); debug!(nonce, "storing proof in DB");
self.store_keyed_encodable(PROOF, &leaf_index, proof) self.store_keyed_encodable(PROOF, &nonce, proof)
} }
/// Retrieve a proof by its leaf index /// Retrieve a proof by its nonce
pub fn proof_by_leaf_index(&self, leaf_index: u32) -> Result<Option<Proof>, DbError> { pub fn proof_by_nonce(&self, nonce: u32) -> Result<Option<Proof>, DbError> {
self.retrieve_keyed_decodable(PROOF, &leaf_index) self.retrieve_keyed_decodable(PROOF, &nonce)
} }
// TODO(james): this is a quick-fix for the prover_sync and I don't like it // TODO(james): this is a quick-fix for the prover_sync and I don't like it
/// poll db ever 100 milliseconds waitinf for a leaf. /// poll db ever 100 milliseconds waitinf for a leaf.
pub fn wait_for_leaf(&self, leaf_index: u32) -> impl Future<Output = Result<H256, DbError>> { pub fn wait_for_message_id(&self, nonce: u32) -> impl Future<Output = Result<H256, DbError>> {
let slf = self.clone(); let slf = self.clone();
async move { async move {
loop { loop {
if let Some(leaf) = slf.leaf_by_leaf_index(leaf_index)? { if let Some(id) = slf.message_id_by_nonce(nonce)? {
return Ok(leaf); return Ok(id);
} }
sleep(Duration::from_millis(100)).await sleep(Duration::from_millis(100)).await
} }
} }
} }
/// Mark leaf as processed /// Mark nonce as processed
pub fn mark_leaf_as_processed(&self, leaf_index: u32) -> Result<(), DbError> { pub fn mark_nonce_as_processed(&self, nonce: u32) -> Result<(), DbError> {
debug!(leaf_index = ?leaf_index, "mark leaf as processed"); debug!(nonce = ?nonce, "mark nonce as processed");
self.store_keyed_encodable(LEAF_PROCESS_STATUS, &leaf_index, &(1_u32)) self.store_keyed_encodable(NONCE_PROCESSED, &nonce, &true)
} }
/// Retrieve leaf processing status /// Retrieve nonce processed status
pub fn retrieve_leaf_processing_status( pub fn retrieve_message_processed(&self, nonce: u32) -> Result<Option<bool>, DbError> {
&self, let value: Option<bool> = self.retrieve_keyed_decodable(NONCE_PROCESSED, &nonce)?;
leaf_index: u32, Ok(value)
) -> Result<Option<bool>, DbError> {
let value: Option<u32> = self.retrieve_keyed_decodable(LEAF_PROCESS_STATUS, &leaf_index)?;
Ok(value.map(|x| x == 1))
} }
/// If the provided gas payment, identified by its metadata, has not been processed, /// If the provided gas payment, identified by its metadata, has not been processed,
@ -251,8 +229,8 @@ impl AbacusDB {
// Set the gas payment as processed // Set the gas payment as processed
self.store_gas_payment_meta_processed(meta)?; self.store_gas_payment_meta_processed(meta)?;
// Update the total gas payment for the leaf to include the payment // Update the total gas payment for the message to include the payment
self.update_gas_payment_for_leaf(&gas_payment_with_meta.payment)?; self.update_gas_payment_for_message_id(&gas_payment_with_meta.payment)?;
// Return true to indicate the gas payment was processed for the first time // Return true to indicate the gas payment was processed for the first time
Ok(true) Ok(true)
@ -276,25 +254,25 @@ impl AbacusDB {
.unwrap_or(false)) .unwrap_or(false))
} }
/// Update the total gas payment for a leaf index to include gas_payment /// Update the total gas payment for a message to include gas_payment
fn update_gas_payment_for_leaf( fn update_gas_payment_for_message_id(
&self, &self,
gas_payment: &InterchainGasPayment, gas_payment: &InterchainGasPayment,
) -> Result<(), DbError> { ) -> Result<(), DbError> {
let InterchainGasPayment { leaf_index, amount } = gas_payment; let InterchainGasPayment { message_id, amount } = gas_payment;
let existing_payment = self.retrieve_gas_payment_for_leaf(*leaf_index)?; let existing_payment = self.retrieve_gas_payment_for_message_id(*message_id)?;
let total = existing_payment + amount; let total = existing_payment + amount;
info!(leaf_index=?leaf_index, gas_payment_amount=?amount, new_total_gas_payment=?total, "Storing gas payment"); info!(message_id=?message_id, gas_payment_amount=?amount, new_total_gas_payment=?total, "Storing gas payment");
self.store_keyed_encodable(GAS_PAYMENT_FOR_LEAF, &gas_payment.leaf_index, &total)?; self.store_keyed_encodable(GAS_PAYMENT_FOR_MESSAGE_ID, &gas_payment.message_id, &total)?;
Ok(()) Ok(())
} }
/// Retrieve the total gas payment for a leaf index /// Retrieve the total gas payment for a message
pub fn retrieve_gas_payment_for_leaf(&self, leaf_index: u32) -> Result<U256, DbError> { pub fn retrieve_gas_payment_for_message_id(&self, message_id: H256) -> Result<U256, DbError> {
Ok(self Ok(self
.retrieve_keyed_decodable(GAS_PAYMENT_FOR_LEAF, &leaf_index)? .retrieve_keyed_decodable(GAS_PAYMENT_FOR_MESSAGE_ID, &message_id)?
.unwrap_or(U256::zero())) .unwrap_or(U256::zero()))
} }
} }

@ -192,7 +192,8 @@ mod test {
.parse() .parse()
.unwrap(); .unwrap();
let message = Checkpoint { let message = Checkpoint {
outbox_domain: 5, mailbox_address: H256::repeat_byte(2),
mailbox_domain: 5,
root: H256::repeat_byte(1), root: H256::repeat_byte(1),
index: 123, index: 123,
}; };

@ -24,8 +24,10 @@ pub mod output_functions {
use super::*; use super::*;
/// Output proof to /vector/message.json /// Output proof to /vector/message.json
pub fn output_message_and_leaf() { pub fn output_message() {
let abacus_message = AbacusMessage { let abacus_message = AbacusMessage {
nonce: 0,
version: 0,
origin: 1000, origin: 1000,
sender: H256::from( sender: H256::from(
H160::from_str("0x1111111111111111111111111111111111111111").unwrap(), H160::from_str("0x1111111111111111111111111111111111111111").unwrap(),
@ -38,12 +40,14 @@ pub mod output_functions {
}; };
let message_json = json!({ let message_json = json!({
"nonce": abacus_message.nonce,
"version": abacus_message.version,
"origin": abacus_message.origin, "origin": abacus_message.origin,
"sender": abacus_message.sender, "sender": abacus_message.sender,
"destination": abacus_message.destination, "destination": abacus_message.destination,
"recipient": abacus_message.recipient, "recipient": abacus_message.recipient,
"body": abacus_message.body, "body": abacus_message.body,
"messageHash": abacus_message.to_leaf(0), "id": abacus_message.id(),
}); });
let json = json!([message_json]).to_string(); let json = json!([message_json]).to_string();
@ -64,7 +68,7 @@ pub mod output_functions {
let index = 1; let index = 1;
// kludge. this is a manual entry of the hash of the messages sent by the cross-chain governance upgrade tests // kludge. these are random message ids
tree.push_leaf( tree.push_leaf(
"0xd89959d277019eee21f1c3c270a125964d63b71876880724d287fbb8b8de55f1" "0xd89959d277019eee21f1c3c270a125964d63b71876880724d287fbb8b8de55f1"
.parse() .parse()
@ -97,11 +101,14 @@ pub mod output_functions {
/// Outputs domain hash test cases in /vector/domainHash.json /// Outputs domain hash test cases in /vector/domainHash.json
pub fn output_domain_hashes() { pub fn output_domain_hashes() {
let mailbox =
H256::from(H160::from_str("0x2222222222222222222222222222222222222222").unwrap());
let test_cases: Vec<Value> = (1..=3) let test_cases: Vec<Value> = (1..=3)
.map(|i| { .map(|i| {
json!({ json!({
"outboxDomain": i, "domain": i,
"expectedDomainHash": domain_hash(i) "mailbox": mailbox,
"expectedDomainHash": domain_hash(mailbox, i)
}) })
}) })
.collect(); .collect();
@ -121,6 +128,8 @@ pub mod output_functions {
/// Outputs signed checkpoint test cases in /vector/signedCheckpoint.json /// Outputs signed checkpoint test cases in /vector/signedCheckpoint.json
pub fn output_signed_checkpoints() { pub fn output_signed_checkpoints() {
let mailbox =
H256::from(H160::from_str("0x2222222222222222222222222222222222222222").unwrap());
let t = async { let t = async {
let signer: ethers::signers::LocalWallet = let signer: ethers::signers::LocalWallet =
"1111111111111111111111111111111111111111111111111111111111111111" "1111111111111111111111111111111111111111111111111111111111111111"
@ -132,7 +141,8 @@ pub mod output_functions {
// test suite // test suite
for i in 1..=3 { for i in 1..=3 {
let signed_checkpoint = Checkpoint { let signed_checkpoint = Checkpoint {
outbox_domain: 1000, mailbox_address: mailbox,
mailbox_domain: 1000,
root: H256::repeat_byte(i + 1), root: H256::repeat_byte(i + 1),
index: i as u32, index: i as u32,
} }
@ -141,7 +151,8 @@ pub mod output_functions {
.expect("!sign_with"); .expect("!sign_with");
test_cases.push(json!({ test_cases.push(json!({
"outboxDomain": signed_checkpoint.checkpoint.outbox_domain, "mailbox": signed_checkpoint.checkpoint.mailbox_address,
"domain": signed_checkpoint.checkpoint.mailbox_domain,
"root": signed_checkpoint.checkpoint.root, "root": signed_checkpoint.checkpoint.root,
"index": signed_checkpoint.checkpoint.index, "index": signed_checkpoint.checkpoint.index,
"signature": signed_checkpoint.signature, "signature": signed_checkpoint.signature,

@ -1,52 +0,0 @@
use eyre::bail;
/// Contract states
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum OutboxState {
/// Before initialize function is called.
/// Note: the contract is initialized at deploy time, so it should never be in this state
UnInitialized = 0,
/// As long as the contract has not become fraudulent.
Active = 1,
/// After a valid fraud proof has been submitted; contract will no longer accept updates or new
/// messages
Failed = 2,
}
impl TryFrom<u8> for OutboxState {
type Error = eyre::Report;
fn try_from(value: u8) -> Result<Self, Self::Error> {
use OutboxState::*;
Ok(match value {
0 => UnInitialized,
1 => Active,
2 => Failed,
_ => bail!("Invalid state value"),
})
}
}
/// The status of a message in the inbox
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum MessageStatus {
/// Message is unknown
None = 0,
/// Message has been processed
Processed = 1,
}
impl TryFrom<u8> for MessageStatus {
type Error = eyre::Report;
fn try_from(value: u8) -> Result<Self, Self::Error> {
use MessageStatus::*;
Ok(match value {
0 => None,
1 => Processed,
_ => bail!("Invalid message status value"),
})
}
}

@ -1,25 +0,0 @@
use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers::core::types::H256;
use eyre::Result;
use crate::{
traits::{AbacusCommon, ChainCommunicationError},
Address, MessageStatus,
};
/// Interface for on-chain inboxes
#[async_trait]
#[auto_impl(Box, Arc)]
pub trait Inbox: AbacusCommon + Send + Sync + Debug {
/// Return the domain of the inbox's linked outbox
async fn remote_domain(&self) -> Result<u32, ChainCommunicationError>;
/// Fetch the status of a message
async fn message_status(&self, leaf: H256) -> Result<MessageStatus, ChainCommunicationError>;
/// The on-chain address of the inbox contract.
fn contract_address(&self) -> Address;
}

@ -3,8 +3,8 @@
//! way to retrieve data such as the chain's latest block number or a list of //! way to retrieve data such as the chain's latest block number or a list of
//! checkpoints/messages emitted within a certain block range by calling out to //! checkpoints/messages emitted within a certain block range by calling out to
//! a chain-specific library and provider (e.g. ethers::provider). A //! a chain-specific library and provider (e.g. ethers::provider). A
//! chain-specific outbox or inbox should implement one or both of the Indexer //! chain-specific mailbox or inbox should implement one or both of the Indexer
//! traits (CommonIndexer or OutboxIndexer) to provide an common interface which //! traits (CommonIndexer or MailboxIndexer) to provide an common interface which
//! other entities can retrieve this chain-specific info. //! other entities can retrieve this chain-specific info.
use std::fmt::Debug; use std::fmt::Debug;
@ -13,7 +13,7 @@ use async_trait::async_trait;
use auto_impl::auto_impl; use auto_impl::auto_impl;
use eyre::Result; use eyre::Result;
use crate::{Checkpoint, InterchainGasPaymentWithMeta, LogMeta, RawCommittedMessage}; use crate::{AbacusMessage, InterchainGasPaymentWithMeta, LogMeta};
/// Interface for an indexer. /// Interface for an indexer.
#[async_trait] #[async_trait]
@ -23,31 +23,23 @@ pub trait Indexer: Send + Sync + Debug {
async fn get_finalized_block_number(&self) -> Result<u32>; async fn get_finalized_block_number(&self) -> Result<u32>;
} }
/// Interface for Outbox contract indexer. Interface for allowing other /// Interface for Mailbox contract indexer. Interface for allowing other
/// entities to retrieve chain-specific data from an outbox. /// entities to retrieve chain-specific data from an mailbox.
#[async_trait] #[async_trait]
#[auto_impl(Box, Arc)] #[auto_impl(Box, Arc)]
pub trait OutboxIndexer: Indexer + Send + Sync + Debug { pub trait MailboxIndexer: Indexer {
/// Fetch list of messages between blocks `from` and `to`. /// Fetch list of messages between blocks `from` and `to`.
async fn fetch_sorted_messages( async fn fetch_sorted_messages(
&self, &self,
from: u32, from: u32,
to: u32, to: u32,
) -> Result<Vec<(RawCommittedMessage, LogMeta)>>; ) -> Result<Vec<(AbacusMessage, LogMeta)>>;
/// Fetch sequentially sorted list of cached checkpoints between blocks
/// `from` and `to`
async fn fetch_sorted_cached_checkpoints(
&self,
from: u32,
to: u32,
) -> Result<Vec<(Checkpoint, LogMeta)>>;
} }
/// Interface for InterchainGasPaymaster contract indexer. /// Interface for InterchainGasPaymaster contract indexer.
#[async_trait] #[async_trait]
#[auto_impl(Box, Arc)] #[auto_impl(Box, Arc)]
pub trait InterchainGasPaymasterIndexer: Indexer + Send + Sync + Debug { pub trait InterchainGasPaymasterIndexer: Indexer {
/// Fetch list of gas payments between `from_block` and `to_block`, /// Fetch list of gas payments between `from_block` and `to_block`,
/// inclusive /// inclusive
async fn fetch_gas_payments( async fn fetch_gas_payments(

@ -0,0 +1,60 @@
use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers::{core::types::H256, types::U256};
use eyre::Result;
use crate::{
traits::{ChainCommunicationError, TxOutcome},
utils::domain_hash,
AbacusContract, AbacusMessage, Checkpoint, TxCostEstimate,
};
/// Interface for the Mailbox chain contract. Allows abstraction over different
/// chains
#[async_trait]
#[auto_impl(Box, Arc)]
pub trait Mailbox: AbacusContract + Send + Sync + Debug {
/// Return the domain ID
fn local_domain(&self) -> u32;
/// Return the domain hash
fn local_domain_hash(&self) -> H256 {
domain_hash(self.address(), self.local_domain())
}
/// Gets the current leaf count of the merkle tree
async fn count(&self) -> Result<u32, ChainCommunicationError>;
/// Fetch the status of a message
async fn delivered(&self, id: H256) -> Result<bool, ChainCommunicationError>;
/// Get the latest checkpoint.
async fn latest_checkpoint(
&self,
lag: Option<u64>,
) -> Result<Checkpoint, ChainCommunicationError>;
/// Fetch the current default interchain security module value
async fn default_ism(&self) -> Result<H256, ChainCommunicationError>;
/// Process a message with a proof against the provided signed checkpoint
async fn process(
&self,
message: &AbacusMessage,
metadata: &[u8],
tx_gas_limit: Option<U256>,
) -> Result<TxOutcome, ChainCommunicationError>;
/// Estimate transaction costs to process a message.
async fn process_estimate_costs(
&self,
message: &AbacusMessage,
metadata: &[u8],
) -> Result<TxCostEstimate>;
/// Get the calldata for a transaction to process a message with a proof
/// against the provided signed checkpoint
fn process_calldata(&self, message: &AbacusMessage, metadata: &[u8]) -> Vec<u8>;
}

@ -2,7 +2,6 @@ use std::collections::HashMap;
use std::error::Error as StdError; use std::error::Error as StdError;
use std::fmt::Debug; use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl; use auto_impl::auto_impl;
use ethers::prelude::Selector; use ethers::prelude::Selector;
use ethers::{ use ethers::{
@ -10,25 +9,20 @@ use ethers::{
core::types::{TransactionReceipt, H256}, core::types::{TransactionReceipt, H256},
providers::{Middleware, ProviderError}, providers::{Middleware, ProviderError},
}; };
use eyre::Result;
pub use common::*;
pub use encode::*; pub use encode::*;
pub use inbox::*;
pub use indexer::*; pub use indexer::*;
pub use interchain_gas::*; pub use interchain_gas::*;
pub use outbox::*; pub use mailbox::*;
pub use validator_manager::*; pub use multisig_ism::*;
use crate::{db::DbError, utils::domain_hash, AbacusError}; use crate::{db::DbError, AbacusError};
mod common;
mod encode; mod encode;
mod inbox;
mod indexer; mod indexer;
mod interchain_gas; mod interchain_gas;
mod outbox; mod mailbox;
mod validator_manager; mod multisig_ism;
/// The result of a transaction /// The result of a transaction
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -98,25 +92,6 @@ pub trait AbacusContract {
fn address(&self) -> H256; fn address(&self) -> H256;
} }
/// Interface for attributes shared by Outbox and Inbox
#[async_trait]
#[auto_impl(Box, Arc)]
pub trait AbacusCommon: AbacusContract + Sync + Send + Debug {
/// Return the domain ID
fn local_domain(&self) -> u32;
/// Return the domain hash
fn local_domain_hash(&self) -> H256 {
domain_hash(self.local_domain())
}
/// Get the status of a transaction.
async fn status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError>;
/// Fetch the current validator manager value
async fn validator_manager(&self) -> Result<H256, ChainCommunicationError>;
}
/// Static contract ABI information. /// Static contract ABI information.
#[auto_impl(Box, Arc)] #[auto_impl(Box, Arc)]
pub trait AbacusAbi { pub trait AbacusAbi {

@ -0,0 +1,30 @@
use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers::types::{H160, U256};
use eyre::Result;
use crate::{
accumulator::merkle::Proof, traits::ChainCommunicationError, AbacusContract,
MultisigSignedCheckpoint,
};
/// Interface for the MultisigIsm chain contract. Allows abstraction over different
/// chains
#[async_trait]
#[auto_impl(Box, Arc)]
pub trait MultisigIsm: AbacusContract + Send + Sync + Debug {
/// Returns the metadata needed by the contract's verify function
async fn format_metadata(
&self,
checkpoint: &MultisigSignedCheckpoint,
proof: Proof,
) -> Result<Vec<u8>, ChainCommunicationError>;
/// Fetch the threshold for the provided domain
async fn threshold(&self, domain: u32) -> Result<U256, ChainCommunicationError>;
/// Fetch the validators for the provided domain
async fn validators(&self, domain: u32) -> Result<Vec<H160>, ChainCommunicationError>;
}

@ -1,78 +0,0 @@
use std::convert::TryFrom;
use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers::core::types::H256;
use eyre::Result;
use crate::{
traits::{ChainCommunicationError, TxOutcome},
AbacusCommon, Checkpoint, CommittedMessage, Message, OutboxState, RawCommittedMessage,
};
/// Interface for the Outbox chain contract. Allows abstraction over different
/// chains
#[async_trait]
#[auto_impl(Box, Arc)]
pub trait Outbox: AbacusCommon + Send + Sync + Debug {
/// Fetch the current state.
async fn state(&self) -> Result<OutboxState, ChainCommunicationError>;
/// Gets the current leaf count of the merkle tree
async fn count(&self) -> Result<u32, ChainCommunicationError>;
/// Dispatch a message.
async fn dispatch(&self, message: &Message) -> Result<TxOutcome, ChainCommunicationError>;
/// Caches the latest checkpoint.
async fn cache_checkpoint(&self) -> Result<TxOutcome, ChainCommunicationError>;
/// Fetch the latest cached root.
async fn latest_cached_root(&self) -> Result<H256, ChainCommunicationError>;
/// Return the latest cached checkpoint.
async fn latest_cached_checkpoint(&self) -> Result<Checkpoint, ChainCommunicationError>;
/// Get the latest checkpoint.
async fn latest_checkpoint(
&self,
lag: Option<u64>,
) -> Result<Checkpoint, ChainCommunicationError>;
}
/// Interface for retrieving event data emitted specifically by the outbox
#[async_trait]
#[auto_impl(Box, Arc)]
pub trait OutboxEvents: Outbox + Send + Sync + Debug {
/// Look up a message by its hash.
/// This should fetch events from the chain API
async fn raw_message_by_leaf(
&self,
leaf: H256,
) -> Result<Option<RawCommittedMessage>, ChainCommunicationError>;
/// Look up a message by its hash.
/// This should fetch events from the chain API
async fn message_by_leaf(
&self,
leaf: H256,
) -> Result<Option<CommittedMessage>, ChainCommunicationError> {
self.raw_message_by_leaf(leaf)
.await?
.map(CommittedMessage::try_from)
.transpose()
.map_err(Into::into)
}
/// Fetch the tree_index-th leaf inserted into the merkle tree.
/// Returns `Ok(None)` if no leaf exists for given `tree_size` (`Ok(None)`
/// serves as the return value for an index error). If tree_index == 0,
/// this will return the first inserted leaf. This is because the Outbox
/// emits the index at which the leaf was inserted in (`tree.count() - 1`),
/// thus the first inserted leaf has an index of 0.
async fn leaf_by_tree_index(
&self,
tree_index: usize,
) -> Result<Option<H256>, ChainCommunicationError>;
}

@ -1,46 +0,0 @@
use std::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers::types::U256;
use eyre::Result;
use crate::{
accumulator::merkle::Proof,
traits::{ChainCommunicationError, TxOutcome},
AbacusMessage, Address, MultisigSignedCheckpoint, TxCostEstimate,
};
/// Interface for an InboxValidatorManager
#[async_trait]
#[auto_impl(Box, Arc)]
pub trait InboxValidatorManager: Send + Sync + Debug {
/// Process a message with a proof against the provided signed checkpoint
async fn process(
&self,
multisig_signed_checkpoint: &MultisigSignedCheckpoint,
message: &AbacusMessage,
proof: &Proof,
tx_gas_limit: Option<U256>,
) -> Result<TxOutcome, ChainCommunicationError>;
/// Estimate transaction costs to process a message.
async fn process_estimate_costs(
&self,
multisig_signed_checkpoint: &MultisigSignedCheckpoint,
message: &AbacusMessage,
proof: &Proof,
) -> Result<TxCostEstimate>;
/// Get the calldata for a transaction to process a message with a proof
/// against the provided signed checkpoint
fn process_calldata(
&self,
multisig_signed_checkpoint: &MultisigSignedCheckpoint,
message: &AbacusMessage,
proof: &Proof,
) -> Vec<u8>;
/// The on-chain address of the inbox validator manager contract.
fn contract_address(&self) -> Address;
}

@ -11,8 +11,10 @@ use sha3::{Digest, Keccak256};
/// An Abacus checkpoint /// An Abacus checkpoint
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Checkpoint { pub struct Checkpoint {
/// The outbox chain /// The mailbox address
pub outbox_domain: u32, pub mailbox_address: H256,
/// The mailbox chain
pub mailbox_domain: u32,
/// The checkpointed root /// The checkpointed root
pub root: H256, pub root: H256,
/// The index of the checkpoint /// The index of the checkpoint
@ -24,7 +26,7 @@ impl std::fmt::Display for Checkpoint {
write!( write!(
f, f,
"Checkpoint(domain {} moved from {} to {})", "Checkpoint(domain {} moved from {} to {})",
self.outbox_domain, self.root, self.index self.mailbox_domain, self.root, self.index
) )
} }
} }
@ -34,10 +36,11 @@ impl Encode for Checkpoint {
where where
W: std::io::Write, W: std::io::Write,
{ {
writer.write_all(&self.outbox_domain.to_be_bytes())?; writer.write_all(self.mailbox_address.as_ref())?;
writer.write_all(&self.mailbox_domain.to_be_bytes())?;
writer.write_all(self.root.as_ref())?; writer.write_all(self.root.as_ref())?;
writer.write_all(&self.index.to_be_bytes())?; writer.write_all(&self.index.to_be_bytes())?;
Ok(4 + 32 + 4) Ok(32 + 4 + 32 + 4)
} }
} }
@ -47,8 +50,11 @@ impl Decode for Checkpoint {
R: std::io::Read, R: std::io::Read,
Self: Sized, Self: Sized,
{ {
let mut outbox_domain = [0u8; 4]; let mut mailbox_address = H256::zero();
reader.read_exact(&mut outbox_domain)?; reader.read_exact(mailbox_address.as_mut())?;
let mut mailbox_domain = [0u8; 4];
reader.read_exact(&mut mailbox_domain)?;
let mut root = H256::zero(); let mut root = H256::zero();
reader.read_exact(root.as_mut())?; reader.read_exact(root.as_mut())?;
@ -57,7 +63,8 @@ impl Decode for Checkpoint {
reader.read_exact(&mut index)?; reader.read_exact(&mut index)?;
Ok(Self { Ok(Self {
outbox_domain: u32::from_be_bytes(outbox_domain), mailbox_address,
mailbox_domain: u32::from_be_bytes(mailbox_domain),
root, root,
index: u32::from_be_bytes(index), index: u32::from_be_bytes(index),
}) })
@ -68,10 +75,10 @@ impl Checkpoint {
fn signing_hash(&self) -> H256 { fn signing_hash(&self) -> H256 {
let buffer = [0u8; 28]; let buffer = [0u8; 28];
// sign: // sign:
// domain_hash(outbox_domain) || root || index (as u256) // domain_hash(mailbox_address, mailbox_domain) || root || index (as u256)
H256::from_slice( H256::from_slice(
Keccak256::new() Keccak256::new()
.chain(domain_hash(self.outbox_domain)) .chain(domain_hash(self.mailbox_address, self.mailbox_domain))
.chain(self.root) .chain(self.root)
.chain(buffer) .chain(buffer)
.chain(self.index.to_be_bytes()) .chain(self.index.to_be_bytes())

@ -3,114 +3,31 @@ use sha3::{Digest, Keccak256};
use crate::{AbacusError, Decode, Encode}; use crate::{AbacusError, Decode, Encode};
const ABACUS_MESSAGE_PREFIX_LEN: usize = 72; const ABACUS_MESSAGE_PREFIX_LEN: usize = 77;
/// A Stamped message that has been committed at some leaf index /// A Stamped message that has been committed at some leaf index
#[derive(Debug, Default, Clone, PartialEq, Eq)] pub type RawAbacusMessage = Vec<u8>;
pub struct RawCommittedMessage {
/// The index at which the message is committed
pub leaf_index: u32,
/// The fully detailed message that was committed
pub message: Vec<u8>,
}
impl RawCommittedMessage {
/// Return the `leaf` for this raw message
///
/// The leaf is the keccak256 digest of the message, which is committed
/// in the message tree
pub fn leaf(&self) -> H256 {
let buffer = [0u8; 28];
H256::from_slice(
Keccak256::new()
.chain(&self.message)
.chain(buffer)
.chain(self.leaf_index.to_be_bytes())
.finalize()
.as_slice(),
)
}
}
impl Encode for RawCommittedMessage {
fn write_to<W>(&self, writer: &mut W) -> std::io::Result<usize>
where
W: std::io::Write,
{
writer.write_all(&self.leaf_index.to_be_bytes())?;
writer.write_all(&self.message)?;
Ok(4 + 32 + self.message.len())
}
}
impl Decode for RawCommittedMessage {
fn read_from<R>(reader: &mut R) -> Result<Self, AbacusError>
where
R: std::io::Read,
Self: Sized,
{
let mut idx = [0u8; 4];
reader.read_exact(&mut idx)?;
let mut message = vec![];
reader.read_to_end(&mut message)?;
Ok(Self {
leaf_index: u32::from_be_bytes(idx),
message,
})
}
}
/// A Stamped message that has been committed at some leaf index
#[derive(Debug, Default, Clone)]
pub struct CommittedMessage {
/// The index at which the message is committed
pub leaf_index: u32,
/// The fully detailed message that was committed
pub message: AbacusMessage,
}
impl CommittedMessage {
/// Return the leaf associated with the message
pub fn to_leaf(&self) -> H256 {
self.message.to_leaf(self.leaf_index)
}
}
impl AsRef<AbacusMessage> for CommittedMessage {
fn as_ref(&self) -> &AbacusMessage {
&self.message
}
}
impl TryFrom<RawCommittedMessage> for CommittedMessage {
type Error = AbacusError;
fn try_from(raw: RawCommittedMessage) -> Result<Self, Self::Error> {
(&raw).try_into()
}
}
impl TryFrom<&RawCommittedMessage> for CommittedMessage { impl From<&AbacusMessage> for RawAbacusMessage {
type Error = AbacusError; fn from(m: &AbacusMessage) -> Self {
let mut message_vec = vec![];
fn try_from(raw: &RawCommittedMessage) -> Result<Self, Self::Error> { m.write_to(&mut message_vec).expect("!write_to");
Ok(Self { message_vec
leaf_index: raw.leaf_index,
message: AbacusMessage::read_from(&mut raw.message.as_slice())?,
})
} }
} }
/// A full Abacus message between chains /// A full Abacus message between chains
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct AbacusMessage { pub struct AbacusMessage {
/// 4 SLIP-44 ID /// 1 Abacus version number
pub version: u8,
/// 4 Message nonce
pub nonce: u32,
/// 4 Origin domain ID
pub origin: u32, pub origin: u32,
/// 32 Address in Outbox convention /// 32 Address in origin convention
pub sender: H256, pub sender: H256,
/// 4 SLIP-44 ID /// 4 Destination domain ID
pub destination: u32, pub destination: u32,
/// 32 Address in destination convention /// 32 Address in destination convention
pub recipient: H256, pub recipient: H256,
@ -118,15 +35,31 @@ pub struct AbacusMessage {
pub body: Vec<u8>, pub body: Vec<u8>,
} }
/// A partial Abacus message between chains impl From<RawAbacusMessage> for AbacusMessage {
#[derive(Debug, Default, Clone)] fn from(m: RawAbacusMessage) -> Self {
pub struct Message { AbacusMessage::from(&m)
/// 4 SLIP-44 ID }
pub destination: u32, }
/// 32 Address in destination convention
pub recipient: H256, impl From<&RawAbacusMessage> for AbacusMessage {
/// 0+ Message contents fn from(m: &RawAbacusMessage) -> Self {
pub body: Vec<u8>, let version = m[0];
let nonce: [u8; 4] = m[1..5].try_into().unwrap();
let origin: [u8; 4] = m[5..9].try_into().unwrap();
let sender: [u8; 32] = m[9..41].try_into().unwrap();
let destination: [u8; 4] = m[41..45].try_into().unwrap();
let recipient: [u8; 32] = m[45..77].try_into().unwrap();
let body = m[77..].try_into().unwrap();
Self {
version,
nonce: u32::from_be_bytes(nonce),
origin: u32::from_be_bytes(origin),
sender: H256::from(sender),
destination: u32::from_be_bytes(destination),
recipient: H256::from(recipient),
body,
}
}
} }
impl Encode for AbacusMessage { impl Encode for AbacusMessage {
@ -134,6 +67,8 @@ impl Encode for AbacusMessage {
where where
W: std::io::Write, W: std::io::Write,
{ {
writer.write_all(&self.version.to_be_bytes())?;
writer.write_all(&self.nonce.to_be_bytes())?;
writer.write_all(&self.origin.to_be_bytes())?; writer.write_all(&self.origin.to_be_bytes())?;
writer.write_all(self.sender.as_ref())?; writer.write_all(self.sender.as_ref())?;
writer.write_all(&self.destination.to_be_bytes())?; writer.write_all(&self.destination.to_be_bytes())?;
@ -148,6 +83,12 @@ impl Decode for AbacusMessage {
where where
R: std::io::Read, R: std::io::Read,
{ {
let mut version = [0u8; 1];
reader.read_exact(&mut version)?;
let mut nonce = [0u8; 4];
reader.read_exact(&mut nonce)?;
let mut origin = [0u8; 4]; let mut origin = [0u8; 4];
reader.read_exact(&mut origin)?; reader.read_exact(&mut origin)?;
@ -164,6 +105,8 @@ impl Decode for AbacusMessage {
reader.read_to_end(&mut body)?; reader.read_to_end(&mut body)?;
Ok(Self { Ok(Self {
version: u8::from_be_bytes(version),
nonce: u32::from_be_bytes(nonce),
origin: u32::from_be_bytes(origin), origin: u32::from_be_bytes(origin),
sender, sender,
destination: u32::from_be_bytes(destination), destination: u32::from_be_bytes(destination),
@ -174,17 +117,9 @@ impl Decode for AbacusMessage {
} }
impl AbacusMessage { impl AbacusMessage {
/// Convert the message to a leaf /// Convert the message to a message id
pub fn to_leaf(&self, leaf_index: u32) -> H256 { pub fn id(&self) -> H256 {
let buffer = [0u8; 28]; H256::from_slice(Keccak256::new().chain(&self.to_vec()).finalize().as_slice())
H256::from_slice(
Keccak256::new()
.chain(&self.to_vec())
.chain(buffer)
.chain(leaf_index.to_be_bytes())
.finalize()
.as_slice(),
)
} }
} }

@ -14,12 +14,12 @@ pub use message::*;
use crate::{AbacusError, Decode, Encode}; use crate::{AbacusError, Decode, Encode};
/// A payment of Outbox native tokens for a message /// A payment of native tokens for a message
#[derive(Debug)] #[derive(Debug)]
pub struct InterchainGasPayment { pub struct InterchainGasPayment {
/// The index of the message's leaf in the merkle tree /// The id of the message
pub leaf_index: u32, pub message_id: H256,
/// The payment amount, in Outbox native token wei /// The payment amount, in origin chain native token wei
pub amount: U256, pub amount: U256,
} }

@ -17,12 +17,13 @@ pub fn strip_0x_prefix(s: &str) -> &str {
} }
} }
/// Computes hash of domain concatenated with "ABACUS" /// Computes hash of domain concatenated with "HYPERLANE"
pub fn domain_hash(domain: u32) -> H256 { pub fn domain_hash(address: H256, domain: u32) -> H256 {
H256::from_slice( H256::from_slice(
Keccak256::new() Keccak256::new()
.chain(domain.to_be_bytes()) .chain(domain.to_be_bytes())
.chain("ABACUS".as_bytes()) .chain(address.as_ref())
.chain("HYPERLANE".as_bytes())
.finalize() .finalize()
.as_slice(), .as_slice(),
) )

@ -1,84 +0,0 @@
#![allow(non_snake_case)]
use async_trait::async_trait;
use mockall::*;
use ethers::types::H256;
use abacus_core::{accumulator::merkle::Proof, *};
mock! {
pub InboxContract {
// Inbox
pub fn _address(&self) -> H256 {}
pub fn _local_domain(&self) -> u32 {}
pub fn _contract_address(&self) -> Address {}
pub fn _remote_domain(&self) -> Result<u32, ChainCommunicationError> {}
pub fn _prove(&self, proof: &Proof) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn _checkpoint(
&self,
signed_checkpoint: &SignedCheckpoint,
) -> Result<TxOutcome, ChainCommunicationError> {}
// AbacusCommon
pub fn _status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError> {}
pub fn _validator_manager(&self) -> Result<H256, ChainCommunicationError> {}
pub fn _message_status(&self, leaf: H256) -> Result<MessageStatus, ChainCommunicationError> {}
// AbacusContract
pub fn _chain_name(&self) -> &str {}
}
}
impl std::fmt::Debug for MockInboxContract {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MockInboxContract")
}
}
#[async_trait]
impl Inbox for MockInboxContract {
async fn remote_domain(&self) -> Result<u32, ChainCommunicationError> {
self._remote_domain()
}
async fn message_status(&self, leaf: H256) -> Result<MessageStatus, ChainCommunicationError> {
self._message_status(leaf)
}
fn contract_address(&self) -> Address {
self._contract_address()
}
}
impl AbacusContract for MockInboxContract {
fn chain_name(&self) -> &str {
self._chain_name()
}
fn address(&self) -> H256 {
self._address()
}
}
#[async_trait]
impl AbacusCommon for MockInboxContract {
fn local_domain(&self) -> u32 {
self._local_domain()
}
async fn status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError> {
self._status(txid)
}
async fn validator_manager(&self) -> Result<H256, ChainCommunicationError> {
self._validator_manager()
}
}

@ -4,13 +4,13 @@ use async_trait::async_trait;
use eyre::Result; use eyre::Result;
use mockall::*; use mockall::*;
use abacus_core::{Indexer, OutboxIndexer, *}; use abacus_core::{Indexer, MailboxIndexer, *};
mock! { mock! {
pub Indexer { pub Indexer {
pub fn _get_finalized_block_number(&self) -> Result<u32> {} pub fn _get_finalized_block_number(&self) -> Result<u32> {}
pub fn _fetch_sorted_messages(&self, from: u32, to: u32) -> Result<Vec<(RawCommittedMessage, LogMeta)>> {} pub fn _fetch_sorted_messages(&self, from: u32, to: u32) -> Result<Vec<(AbacusMessage, LogMeta)>> {}
} }
} }
@ -24,9 +24,7 @@ mock! {
pub AbacusIndexer { pub AbacusIndexer {
pub fn _get_finalized_block_number(&self) -> Result<u32> {} pub fn _get_finalized_block_number(&self) -> Result<u32> {}
pub fn _fetch_sorted_cached_checkpoints(&self, from: u32, to: u32) -> Result<Vec<(Checkpoint, LogMeta)>> {} pub fn _fetch_sorted_messages(&self, from: u32, to: u32) -> Result<Vec<(AbacusMessage, LogMeta)>> {}
pub fn _fetch_sorted_messages(&self, from: u32, to: u32) -> Result<Vec<(RawCommittedMessage, LogMeta)>> {}
} }
} }
@ -44,20 +42,12 @@ impl Indexer for MockAbacusIndexer {
} }
#[async_trait] #[async_trait]
impl OutboxIndexer for MockAbacusIndexer { impl MailboxIndexer for MockAbacusIndexer {
async fn fetch_sorted_messages( async fn fetch_sorted_messages(
&self, &self,
from: u32, from: u32,
to: u32, to: u32,
) -> Result<Vec<(RawCommittedMessage, LogMeta)>> { ) -> Result<Vec<(AbacusMessage, LogMeta)>> {
self._fetch_sorted_messages(from, to) self._fetch_sorted_messages(from, to)
} }
async fn fetch_sorted_cached_checkpoints(
&self,
from: u32,
to: u32,
) -> Result<Vec<(Checkpoint, LogMeta)>> {
self._fetch_sorted_cached_checkpoints(from, to)
}
} }

@ -0,0 +1,123 @@
#![allow(non_snake_case)]
use async_trait::async_trait;
use eyre::Result;
use mockall::*;
use ethers::{core::types::H256, types::U256};
use abacus_core::*;
mock! {
pub MailboxContract {
// Mailbox
pub fn _address(&self) -> H256 {}
pub fn _local_domain(&self) -> u32 {}
pub fn _domain_hash(&self) -> H256 {}
pub fn _raw_message_by_id(
&self,
leaf: H256,
) -> Result<Option<RawAbacusMessage>, ChainCommunicationError> {}
pub fn _id_by_nonce(
&self,
nonce: usize,
) -> Result<Option<H256>, ChainCommunicationError> {}
pub fn _count(&self) -> Result<u32, ChainCommunicationError> {}
pub fn _latest_checkpoint(&self, maybe_lag: Option<u64>) -> Result<Checkpoint, ChainCommunicationError> {}
pub fn _default_ism(&self) -> Result<H256, ChainCommunicationError> {}
pub fn _delivered(&self, id: H256) -> Result<bool, ChainCommunicationError> {}
pub fn process(
&self,
message: &AbacusMessage,
metadata: &[u8],
tx_gas_limit: Option<U256>,
) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn process_estimate_costs(
&self,
message: &AbacusMessage,
metadata: &[u8],
) -> Result<TxCostEstimate> {}
pub fn process_calldata(
&self,
message: &AbacusMessage,
metadata: &[u8],
) -> Vec<u8> {}
// AbacusContract
pub fn _chain_name(&self) -> &str {}
}
}
impl std::fmt::Debug for MockMailboxContract {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MockMailboxContract")
}
}
#[async_trait]
impl Mailbox for MockMailboxContract {
async fn count(&self) -> Result<u32, ChainCommunicationError> {
self._count()
}
async fn latest_checkpoint(
&self,
maybe_lag: Option<u64>,
) -> Result<Checkpoint, ChainCommunicationError> {
self._latest_checkpoint(maybe_lag)
}
fn local_domain(&self) -> u32 {
self._local_domain()
}
async fn default_ism(&self) -> Result<H256, ChainCommunicationError> {
self._default_ism()
}
async fn delivered(&self, id: H256) -> Result<bool, ChainCommunicationError> {
self._delivered(id)
}
async fn process(
&self,
message: &AbacusMessage,
metadata: &[u8],
tx_gas_limit: Option<U256>,
) -> Result<TxOutcome, ChainCommunicationError> {
self.process(message, metadata, tx_gas_limit)
}
async fn process_estimate_costs(
&self,
message: &AbacusMessage,
metadata: &[u8],
) -> Result<TxCostEstimate> {
self.process_estimate_costs(message, metadata)
}
fn process_calldata(&self, message: &AbacusMessage, metadata: &[u8]) -> Vec<u8> {
self.process_calldata(message, metadata)
}
}
impl AbacusContract for MockMailboxContract {
fn chain_name(&self) -> &str {
self._chain_name()
}
fn address(&self) -> H256 {
self._address()
}
}

@ -1,11 +1,8 @@
/// Mock outbox contract /// Mock mailbox contract
pub mod outbox; pub mod mailbox;
/// Mock inbox contract
pub mod inbox;
/// Mock indexer /// Mock indexer
pub mod indexer; pub mod indexer;
pub use indexer::MockIndexer; pub use indexer::MockIndexer;
pub use outbox::MockOutboxContract; pub use mailbox::MockMailboxContract;

@ -1,116 +0,0 @@
#![allow(non_snake_case)]
use async_trait::async_trait;
use mockall::*;
use ethers::core::types::H256;
use abacus_core::*;
mock! {
pub OutboxContract {
// Outbox
pub fn _address(&self) -> H256 {}
pub fn _local_domain(&self) -> u32 {}
pub fn _domain_hash(&self) -> H256 {}
pub fn _raw_message_by_leaf(
&self,
leaf: H256,
) -> Result<Option<RawCommittedMessage>, ChainCommunicationError> {}
pub fn _leaf_by_tree_index(
&self,
tree_index: usize,
) -> Result<Option<H256>, ChainCommunicationError> {}
pub fn _dispatch(&self, message: &Message) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn _count(&self) -> Result<u32, ChainCommunicationError> {}
pub fn _cache_checkpoint(&self) -> Result<TxOutcome, ChainCommunicationError> {}
pub fn _latest_cached_root(&self) -> Result<H256, ChainCommunicationError> {}
pub fn _latest_cached_checkpoint(&self) -> Result<Checkpoint, ChainCommunicationError> {}
pub fn _latest_checkpoint(&self, maybe_lag: Option<u64>) -> Result<Checkpoint, ChainCommunicationError> {}
// AbacusCommon
pub fn _status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError> {}
pub fn _validator_manager(&self) -> Result<H256, ChainCommunicationError> {}
pub fn _state(&self) -> Result<OutboxState, ChainCommunicationError> {}
// AbacusContract
pub fn _chain_name(&self) -> &str {}
}
}
impl std::fmt::Debug for MockOutboxContract {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MockOutboxContract")
}
}
#[async_trait]
impl Outbox for MockOutboxContract {
async fn state(&self) -> Result<OutboxState, ChainCommunicationError> {
self._state()
}
async fn count(&self) -> Result<u32, ChainCommunicationError> {
self._count()
}
async fn dispatch(&self, message: &Message) -> Result<TxOutcome, ChainCommunicationError> {
self._dispatch(message)
}
async fn cache_checkpoint(&self) -> Result<TxOutcome, ChainCommunicationError> {
self._cache_checkpoint()
}
async fn latest_cached_root(&self) -> Result<H256, ChainCommunicationError> {
self._latest_cached_root()
}
async fn latest_cached_checkpoint(&self) -> Result<Checkpoint, ChainCommunicationError> {
self._latest_cached_checkpoint()
}
async fn latest_checkpoint(
&self,
maybe_lag: Option<u64>,
) -> Result<Checkpoint, ChainCommunicationError> {
self._latest_checkpoint(maybe_lag)
}
}
impl AbacusContract for MockOutboxContract {
fn chain_name(&self) -> &str {
self._chain_name()
}
fn address(&self) -> H256 {
self._address()
}
}
#[async_trait]
impl AbacusCommon for MockOutboxContract {
fn local_domain(&self) -> u32 {
self._local_domain()
}
async fn status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError> {
self._status(txid)
}
async fn validator_manager(&self) -> Result<H256, ChainCommunicationError> {
self._validator_manager()
}
}

@ -30,9 +30,7 @@ where
mod test { mod test {
use ethers::types::H256; use ethers::types::H256;
use abacus_core::{ use abacus_core::{accumulator::merkle::Proof, db::AbacusDB, AbacusMessage, RawAbacusMessage};
accumulator::merkle::Proof, db::AbacusDB, AbacusMessage, Encode, RawCommittedMessage,
};
use super::*; use super::*;
@ -42,8 +40,9 @@ mod test {
let outbox_name = "outbox_1".to_owned(); let outbox_name = "outbox_1".to_owned();
let db = AbacusDB::new(outbox_name, db); let db = AbacusDB::new(outbox_name, db);
let leaf_index = 100;
let m = AbacusMessage { let m = AbacusMessage {
nonce: 100,
version: 0,
origin: 10, origin: 10,
sender: H256::from_low_u64_be(4), sender: H256::from_low_u64_be(4),
destination: 12, destination: 12,
@ -51,23 +50,16 @@ mod test {
body: vec![1, 2, 3], body: vec![1, 2, 3],
}; };
let message = RawCommittedMessage { db.store_message(&m).unwrap();
leaf_index,
message: m.to_vec(),
};
assert_eq!(m.to_leaf(leaf_index), message.leaf());
db.store_raw_committed_message(&message).unwrap();
let by_leaf = db.message_by_leaf(message.leaf()).unwrap().unwrap(); let by_id = db.message_by_id(m.id()).unwrap().unwrap();
assert_eq!(by_leaf, message); assert_eq!(RawAbacusMessage::from(&by_id), RawAbacusMessage::from(&m));
let by_index = db let by_nonce = db.message_by_nonce(m.nonce).unwrap().unwrap();
.message_by_leaf_index(message.leaf_index) assert_eq!(
.unwrap() RawAbacusMessage::from(&by_nonce),
.unwrap(); RawAbacusMessage::from(&m)
assert_eq!(by_index, message); );
}) })
.await; .await;
} }
@ -85,7 +77,7 @@ mod test {
}; };
db.store_proof(13, &proof).unwrap(); db.store_proof(13, &proof).unwrap();
let by_index = db.proof_by_leaf_index(13).unwrap().unwrap(); let by_index = db.proof_by_nonce(13).unwrap().unwrap();
assert_eq!(by_index, proof); assert_eq!(by_index, proof);
}) })
.await; .await;

@ -6,7 +6,7 @@ use tokio::{sync::watch::Sender, task::JoinHandle, time::sleep};
use tracing::{debug, info, info_span, instrument, instrument::Instrumented, Instrument}; use tracing::{debug, info, info_span, instrument, instrument::Instrumented, Instrument};
use abacus_base::MultisigCheckpointSyncer; use abacus_base::MultisigCheckpointSyncer;
use abacus_core::{MultisigSignedCheckpoint, Outbox}; use abacus_core::{Mailbox, MultisigSignedCheckpoint};
pub(crate) struct CheckpointFetcher { pub(crate) struct CheckpointFetcher {
polling_interval: u64, polling_interval: u64,
@ -18,7 +18,7 @@ pub(crate) struct CheckpointFetcher {
impl CheckpointFetcher { impl CheckpointFetcher {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn new( pub(crate) fn new(
outbox: &dyn Outbox, mailbox: &dyn Mailbox,
polling_interval: u64, polling_interval: u64,
multisig_checkpoint_syncer: MultisigCheckpointSyncer, multisig_checkpoint_syncer: MultisigCheckpointSyncer,
signed_checkpoint_sender: Sender<Option<MultisigSignedCheckpoint>>, signed_checkpoint_sender: Sender<Option<MultisigSignedCheckpoint>>,
@ -26,7 +26,7 @@ impl CheckpointFetcher {
) -> Self { ) -> Self {
let signed_checkpoint_gauge = leaf_index_gauge.with_label_values(&[ let signed_checkpoint_gauge = leaf_index_gauge.with_label_values(&[
"signed_offchain_checkpoint", "signed_offchain_checkpoint",
outbox.chain_name(), mailbox.chain_name(),
"unknown", // Checkpoints are not remote-specific "unknown", // Checkpoints are not remote-specific
]); ]);
Self { Self {
@ -51,7 +51,7 @@ impl CheckpointFetcher {
.fetch_checkpoint(signed_checkpoint_index) .fetch_checkpoint(signed_checkpoint_index)
.await? .await?
{ {
debug!( info!(
signed_checkpoint_index = signed_checkpoint_index, signed_checkpoint_index = signed_checkpoint_index,
"Sending a newly fetched signed checkpoint via channel" "Sending a newly fetched signed checkpoint via channel"
); );

@ -87,7 +87,7 @@ impl MerkleTreeBuilder {
} }
fn ingest_leaf_index(&mut self, leaf_index: u32) -> Result<(), MerkleTreeBuilderError> { fn ingest_leaf_index(&mut self, leaf_index: u32) -> Result<(), MerkleTreeBuilderError> {
match self.db.leaf_by_leaf_index(leaf_index) { match self.db.message_id_by_nonce(leaf_index) {
Ok(Some(leaf)) => { Ok(Some(leaf)) => {
debug!(leaf_index = leaf_index, "Ingesting leaf"); debug!(leaf_index = leaf_index, "Ingesting leaf");
self.prover.ingest(leaf).expect("!tree full"); self.prover.ingest(leaf).expect("!tree full");
@ -117,7 +117,7 @@ impl MerkleTreeBuilder {
} }
let starting_index = self.prover.count() as u32; let starting_index = self.prover.count() as u32;
for i in starting_index..=checkpoint.index { for i in starting_index..=checkpoint.index {
self.db.wait_for_leaf(i).await?; self.db.wait_for_message_id(i).await?;
self.ingest_leaf_index(i)?; self.ingest_leaf_index(i)?;
} }

@ -2,10 +2,10 @@ use std::fmt::Debug;
use abacus_core::{ use abacus_core::{
db::{AbacusDB, DbError}, db::{AbacusDB, DbError},
CommittedMessage, TxCostEstimate, AbacusMessage, TxCostEstimate,
}; };
use async_trait::async_trait; use async_trait::async_trait;
use ethers::types::U256; use ethers::types::{H256, U256};
use eyre::Result; use eyre::Result;
use crate::settings::GasPaymentEnforcementPolicy; use crate::settings::GasPaymentEnforcementPolicy;
@ -20,7 +20,7 @@ mod policies;
pub trait GasPaymentPolicy: Debug + Send + Sync { pub trait GasPaymentPolicy: Debug + Send + Sync {
async fn message_meets_gas_payment_requirement( async fn message_meets_gas_payment_requirement(
&self, &self,
message: &CommittedMessage, message: &AbacusMessage,
current_payment: &U256, current_payment: &U256,
tx_cost_estimate: &TxCostEstimate, tx_cost_estimate: &TxCostEstimate,
) -> Result<bool>; ) -> Result<bool>;
@ -52,10 +52,10 @@ impl GasPaymentEnforcer {
/// Returns (gas payment requirement met, current payment according to the DB) /// Returns (gas payment requirement met, current payment according to the DB)
pub async fn message_meets_gas_payment_requirement( pub async fn message_meets_gas_payment_requirement(
&self, &self,
message: &CommittedMessage, message: &AbacusMessage,
tx_cost_estimate: &TxCostEstimate, tx_cost_estimate: &TxCostEstimate,
) -> Result<(bool, U256)> { ) -> Result<(bool, U256)> {
let current_payment = self.get_message_gas_payment(message.leaf_index)?; let current_payment = self.get_message_gas_payment(message.id())?;
let meets_requirement = self let meets_requirement = self
.policy .policy
@ -65,7 +65,7 @@ impl GasPaymentEnforcer {
Ok((meets_requirement, current_payment)) Ok((meets_requirement, current_payment))
} }
fn get_message_gas_payment(&self, msg_leaf_index: u32) -> Result<U256, DbError> { fn get_message_gas_payment(&self, msg_id: H256) -> Result<U256, DbError> {
self.db.retrieve_gas_payment_for_leaf(msg_leaf_index) self.db.retrieve_gas_payment_for_message_id(msg_id)
} }
} }

@ -3,7 +3,7 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use abacus_core::{AbacusDomain, CommittedMessage, TxCostEstimate}; use abacus_core::{AbacusDomain, AbacusMessage, TxCostEstimate};
use async_trait::async_trait; use async_trait::async_trait;
use coingecko::CoinGeckoClient; use coingecko::CoinGeckoClient;
use ethers::types::U256; use ethers::types::U256;
@ -181,7 +181,7 @@ impl GasPaymentPolicy for GasPaymentPolicyMeetsEstimatedCost {
/// Returns (gas payment requirement met, current payment according to the DB) /// Returns (gas payment requirement met, current payment according to the DB)
async fn message_meets_gas_payment_requirement( async fn message_meets_gas_payment_requirement(
&self, &self,
message: &CommittedMessage, message: &AbacusMessage,
current_payment: &U256, current_payment: &U256,
tx_cost_estimate: &TxCostEstimate, tx_cost_estimate: &TxCostEstimate,
) -> Result<bool> { ) -> Result<bool> {
@ -191,14 +191,15 @@ impl GasPaymentPolicy for GasPaymentPolicyMeetsEstimatedCost {
let origin_token_tx_cost = self let origin_token_tx_cost = self
.convert_native_tokens( .convert_native_tokens(
destination_token_tx_cost, destination_token_tx_cost,
message.message.destination, message.destination,
message.message.origin, message.origin,
) )
.await?; .await?;
let meets_requirement = *current_payment >= origin_token_tx_cost; let meets_requirement = *current_payment >= origin_token_tx_cost;
tracing::info!( tracing::info!(
message_leaf_index=?message.leaf_index, message_id=?message.id(),
message_nonce=?message.nonce,
tx_cost_estimate=?tx_cost_estimate, tx_cost_estimate=?tx_cost_estimate,
destination_token_tx_cost=?destination_token_tx_cost, destination_token_tx_cost=?destination_token_tx_cost,
origin_token_tx_cost=?origin_token_tx_cost, origin_token_tx_cost=?origin_token_tx_cost,
@ -259,15 +260,14 @@ async fn test_gas_payment_policy_meets_estimated_cost() {
usd_prices.insert(polygon_coingecko_id, polygon_price.into()); usd_prices.insert(polygon_coingecko_id, polygon_price.into());
} }
let message = CommittedMessage { let message = AbacusMessage {
leaf_index: 10u32, version: 0,
message: AbacusMessage { nonce: 10u32,
origin: celo_domain_id, origin: celo_domain_id,
destination: polygon_domain_id, destination: polygon_domain_id,
sender: H256::zero(), sender: H256::zero(),
recipient: H256::zero(), recipient: H256::zero(),
body: vec![], body: vec![],
},
}; };
let tx_cost_estimate = TxCostEstimate { let tx_cost_estimate = TxCostEstimate {
// 1M gas // 1M gas

@ -1,4 +1,4 @@
use abacus_core::{CommittedMessage, TxCostEstimate}; use abacus_core::{AbacusMessage, TxCostEstimate};
use async_trait::async_trait; use async_trait::async_trait;
use ethers::types::U256; use ethers::types::U256;
use eyre::Result; use eyre::Result;
@ -21,7 +21,7 @@ impl GasPaymentPolicy for GasPaymentPolicyMinimum {
/// Returns (gas payment requirement met, current payment according to the DB) /// Returns (gas payment requirement met, current payment according to the DB)
async fn message_meets_gas_payment_requirement( async fn message_meets_gas_payment_requirement(
&self, &self,
_message: &CommittedMessage, _message: &AbacusMessage,
current_payment: &U256, current_payment: &U256,
_tx_cost_estimate: &TxCostEstimate, _tx_cost_estimate: &TxCostEstimate,
) -> Result<bool> { ) -> Result<bool> {
@ -37,10 +37,7 @@ async fn test_gas_payment_policy_none() {
let policy = GasPaymentPolicyMinimum::new(min); let policy = GasPaymentPolicyMinimum::new(min);
let message = CommittedMessage { let message = AbacusMessage::default();
leaf_index: 100,
message: AbacusMessage::default(),
};
// If the payment is less than the minimum, returns false // If the payment is less than the minimum, returns false
assert_eq!( assert_eq!(

@ -1,4 +1,4 @@
use abacus_core::{CommittedMessage, TxCostEstimate}; use abacus_core::{AbacusMessage, TxCostEstimate};
use async_trait::async_trait; use async_trait::async_trait;
use ethers::types::U256; use ethers::types::U256;
use eyre::Result; use eyre::Result;
@ -19,7 +19,7 @@ impl GasPaymentPolicy for GasPaymentPolicyNone {
/// Returns (gas payment requirement met, current payment according to the DB) /// Returns (gas payment requirement met, current payment according to the DB)
async fn message_meets_gas_payment_requirement( async fn message_meets_gas_payment_requirement(
&self, &self,
_message: &CommittedMessage, _message: &AbacusMessage,
_current_payment: &U256, _current_payment: &U256,
_tx_cost_estimate: &TxCostEstimate, _tx_cost_estimate: &TxCostEstimate,
) -> Result<bool> { ) -> Result<bool> {
@ -33,10 +33,7 @@ async fn test_gas_payment_policy_none() {
let policy = GasPaymentPolicyNone::new(); let policy = GasPaymentPolicyNone::new();
let message = CommittedMessage { let message = AbacusMessage::default();
leaf_index: 100,
message: AbacusMessage::default(),
};
// Always returns true // Always returns true
assert_eq!( assert_eq!(

@ -1,9 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use abacus_base::chains::GelatoConf; use abacus_base::chains::GelatoConf;
use abacus_base::{CoreMetrics, InboxContracts}; use abacus_base::{CachingMailbox, CoreMetrics};
use abacus_core::db::AbacusDB; use abacus_core::db::AbacusDB;
use abacus_core::{AbacusCommon, AbacusDomain}; use abacus_core::{AbacusDomain, Mailbox, MultisigIsm};
use eyre::{bail, Result}; use eyre::{bail, Result};
use gelato::types::Chain; use gelato::types::Chain;
use prometheus::{Histogram, IntCounter, IntGauge}; use prometheus::{Histogram, IntCounter, IntGauge};
@ -29,10 +29,12 @@ pub(crate) struct GelatoSubmitter {
gelato_config: GelatoConf, gelato_config: GelatoConf,
/// Source of messages to submit. /// Source of messages to submit.
message_receiver: mpsc::UnboundedReceiver<SubmitMessageArgs>, message_receiver: mpsc::UnboundedReceiver<SubmitMessageArgs>,
/// Inbox / InboxValidatorManager on the destination chain. /// Mailbox on the destination chain.
inbox_contracts: InboxContracts, mailbox: CachingMailbox,
/// The inbox chain in the format expected by the Gelato crate. /// Multisig ISM on the destination chain.
inbox_gelato_chain: Chain, multisig_ism: Arc<dyn MultisigIsm>,
/// The destination chain in the format expected by the Gelato crate.
destination_gelato_chain: Chain,
/// Interface to agent rocks DB for e.g. writing delivery status upon completion. /// Interface to agent rocks DB for e.g. writing delivery status upon completion.
db: AbacusDB, db: AbacusDB,
/// Shared reqwest HTTP client to use for any ops to Gelato endpoints. /// Shared reqwest HTTP client to use for any ops to Gelato endpoints.
@ -50,7 +52,8 @@ pub(crate) struct GelatoSubmitter {
impl GelatoSubmitter { impl GelatoSubmitter {
pub fn new( pub fn new(
message_receiver: mpsc::UnboundedReceiver<SubmitMessageArgs>, message_receiver: mpsc::UnboundedReceiver<SubmitMessageArgs>,
inbox_contracts: InboxContracts, mailbox: CachingMailbox,
multisig_ism: Arc<dyn MultisigIsm>,
abacus_db: AbacusDB, abacus_db: AbacusDB,
gelato_config: GelatoConf, gelato_config: GelatoConf,
metrics: GelatoSubmitterMetrics, metrics: GelatoSubmitterMetrics,
@ -64,11 +67,10 @@ impl GelatoSubmitter {
.unwrap(); .unwrap();
Self { Self {
message_receiver, message_receiver,
inbox_gelato_chain: abacus_domain_id_to_gelato_chain( destination_gelato_chain: abacus_domain_id_to_gelato_chain(mailbox.local_domain())
inbox_contracts.inbox.local_domain(), .unwrap(),
) mailbox,
.unwrap(), multisig_ism,
inbox_contracts,
db: abacus_db, db: abacus_db,
gelato_config, gelato_config,
http_client, http_client,
@ -116,9 +118,10 @@ impl GelatoSubmitter {
opts: SponsoredCallOptions::default(), opts: SponsoredCallOptions::default(),
http: self.http_client.clone(), http: self.http_client.clone(),
message: msg, message: msg,
inbox_contracts: self.inbox_contracts.clone(), mailbox: self.mailbox.clone(),
multisig_ism: self.multisig_ism.clone(),
sponsor_api_key: self.gelato_config.sponsorapikey.clone(), sponsor_api_key: self.gelato_config.sponsorapikey.clone(),
destination_chain: self.inbox_gelato_chain, destination_chain: self.destination_gelato_chain,
message_processed_sender: self.message_processed_sender.clone(), message_processed_sender: self.message_processed_sender.clone(),
gas_payment_enforcer: self.gas_payment_enforcer.clone(), gas_payment_enforcer: self.gas_payment_enforcer.clone(),
}); });
@ -152,17 +155,17 @@ impl GelatoSubmitter {
/// this message again, even after the relayer restarts. /// this message again, even after the relayer restarts.
fn record_message_process_success(&mut self, msg: &SubmitMessageArgs) -> Result<()> { fn record_message_process_success(&mut self, msg: &SubmitMessageArgs) -> Result<()> {
tracing::info!(msg=?msg, "Recording message as successfully processed"); tracing::info!(msg=?msg, "Recording message as successfully processed");
self.db.mark_leaf_as_processed(msg.leaf_index)?; self.db.mark_nonce_as_processed(msg.message.nonce)?;
self.metrics.active_sponsored_call_ops_gauge.sub(1); self.metrics.active_sponsored_call_ops_gauge.sub(1);
self.metrics self.metrics
.queue_duration_hist .queue_duration_hist
.observe((Instant::now() - msg.enqueue_time).as_secs_f64()); .observe((Instant::now() - msg.enqueue_time).as_secs_f64());
self.metrics.highest_submitted_leaf_index = self.metrics.highest_submitted_nonce =
std::cmp::max(self.metrics.highest_submitted_leaf_index, msg.leaf_index); std::cmp::max(self.metrics.highest_submitted_nonce, msg.message.nonce);
self.metrics self.metrics
.processed_gauge .processed_gauge
.set(self.metrics.highest_submitted_leaf_index as i64); .set(self.metrics.highest_submitted_nonce as i64);
self.metrics.messages_processed_count.inc(); self.metrics.messages_processed_count.inc();
Ok(()) Ok(())
} }
@ -175,29 +178,29 @@ pub(crate) struct GelatoSubmitterMetrics {
messages_processed_count: IntCounter, messages_processed_count: IntCounter,
active_sponsored_call_ops_gauge: IntGauge, active_sponsored_call_ops_gauge: IntGauge,
/// Private state used to update actual metrics each tick. /// Private state used to update actual metrics each tick.
highest_submitted_leaf_index: u32, highest_submitted_nonce: u32,
} }
impl GelatoSubmitterMetrics { impl GelatoSubmitterMetrics {
pub fn new(metrics: &CoreMetrics, outbox_chain: &str, inbox_chain: &str) -> Self { pub fn new(metrics: &CoreMetrics, origin_chain: &str, destination_chain: &str) -> Self {
Self { Self {
queue_duration_hist: metrics queue_duration_hist: metrics
.submitter_queue_duration_histogram() .submitter_queue_duration_histogram()
.with_label_values(&[outbox_chain, inbox_chain]), .with_label_values(&[origin_chain, destination_chain]),
messages_processed_count: metrics messages_processed_count: metrics
.messages_processed_count() .messages_processed_count()
.with_label_values(&[outbox_chain, inbox_chain]), .with_label_values(&[origin_chain, destination_chain]),
processed_gauge: metrics.last_known_message_leaf_index().with_label_values(&[ processed_gauge: metrics.last_known_message_nonce().with_label_values(&[
"message_processed", "message_processed",
outbox_chain, origin_chain,
inbox_chain, destination_chain,
]), ]),
active_sponsored_call_ops_gauge: metrics.submitter_queue_length().with_label_values(&[ active_sponsored_call_ops_gauge: metrics.submitter_queue_length().with_label_values(&[
outbox_chain, origin_chain,
inbox_chain, destination_chain,
"active_sponsored_call_ops", "active_sponsored_call_ops",
]), ]),
highest_submitted_leaf_index: 0, highest_submitted_nonce: 0,
} }
} }
} }

@ -4,8 +4,8 @@ use std::{
time::Duration, time::Duration,
}; };
use abacus_base::InboxContracts; use abacus_base::CachingMailbox;
use abacus_core::{ChainCommunicationError, Inbox, InboxValidatorManager, MessageStatus}; use abacus_core::{AbacusContract, ChainCommunicationError, Mailbox, MultisigIsm};
use eyre::Result; use eyre::Result;
use gelato::{ use gelato::{
sponsored_call::{SponsoredCallApiCall, SponsoredCallApiCallResult, SponsoredCallArgs}, sponsored_call::{SponsoredCallApiCall, SponsoredCallApiCallResult, SponsoredCallArgs},
@ -29,7 +29,8 @@ pub struct SponsoredCallOpArgs {
pub http: reqwest::Client, pub http: reqwest::Client,
pub message: SubmitMessageArgs, pub message: SubmitMessageArgs,
pub inbox_contracts: InboxContracts, pub mailbox: CachingMailbox,
pub multisig_ism: Arc<dyn MultisigIsm>,
pub sponsor_api_key: String, pub sponsor_api_key: String,
pub destination_chain: Chain, pub destination_chain: Chain,
@ -61,11 +62,11 @@ impl SponsoredCallOp {
Self(args) Self(args)
} }
#[instrument(skip(self), fields(msg_leaf_index=self.message.leaf_index))] #[instrument(skip(self), fields(msg_nonce=self.message.message.nonce))]
pub async fn run(&mut self) { pub async fn run(&mut self) {
loop { loop {
match self.tick().await { match self.tick().await {
Ok(MessageStatus::Processed) => { Ok(true) => {
// If the message was processed, send it over the channel and // If the message was processed, send it over the channel and
// stop running. // stop running.
if let Err(err) = self.send_message_processed() { if let Err(err) = self.send_message_processed() {
@ -92,44 +93,40 @@ impl SponsoredCallOp {
/// One tick will submit a sponsored call to Gelato and wait for a terminal state /// One tick will submit a sponsored call to Gelato and wait for a terminal state
/// or timeout. /// or timeout.
async fn tick(&self) -> Result<MessageStatus> { async fn tick(&self) -> Result<bool> {
// Before doing anything, first check if the message has already been processed. // Before doing anything, first check if the message has already been processed.
if let Ok(MessageStatus::Processed) = self.message_status().await { if let Ok(true) = self.message_delivered().await {
return Ok(MessageStatus::Processed); return Ok(true);
} }
let metadata = self
.multisig_ism
.format_metadata(&self.message.checkpoint, self.message.proof)
.await?;
// Estimate transaction costs for the process call. If there are issues, it's likely // Estimate transaction costs for the process call. If there are issues, it's likely
// that gas estimation has failed because the message is reverting. This is defined behavior, // that gas estimation has failed because the message is reverting. This is defined behavior,
// so we just log the error and move onto the next tick. // so we just log the error and move onto the next tick.
let tx_cost_estimate = match self let tx_cost_estimate = match self
.inbox_contracts .mailbox
.validator_manager .process_estimate_costs(&self.message.message, &metadata)
.process_estimate_costs(
&self.message.checkpoint,
&self.message.committed_message.message,
&self.message.proof,
)
.await .await
{ {
Ok(tx_cost_estimate) => tx_cost_estimate, Ok(tx_cost_estimate) => tx_cost_estimate,
Err(err) => { Err(err) => {
tracing::info!(error=?err, "Error estimating process costs"); tracing::info!(error=?err, "Error estimating process costs");
return Ok(MessageStatus::None); return Ok(false);
} }
}; };
// If the gas payment requirement hasn't been met, sleep briefly and wait for the next tick. // If the gas payment requirement hasn't been met, sleep briefly and wait for the next tick.
let (meets_gas_requirement, gas_payment) = self let (meets_gas_requirement, gas_payment) = self
.gas_payment_enforcer .gas_payment_enforcer
.message_meets_gas_payment_requirement( .message_meets_gas_payment_requirement(&self.message.message, &tx_cost_estimate)
&self.message.committed_message,
&tx_cost_estimate,
)
.await?; .await?;
if !meets_gas_requirement { if !meets_gas_requirement {
tracing::info!(gas_payment=?gas_payment, "Gas payment requirement not met yet"); tracing::info!(gas_payment=?gas_payment, "Gas payment requirement not met yet");
return Ok(MessageStatus::None); return Ok(false);
} }
// Send the sponsored call. // Send the sponsored call.
@ -155,22 +152,22 @@ impl SponsoredCallOp {
// and set ourselves up for the next tick. // and set ourselves up for the next tick.
Err(err) => { Err(err) => {
tracing::info!(err=?err, "Sponsored call timed out, reattempting"); tracing::info!(err=?err, "Sponsored call timed out, reattempting");
Ok(MessageStatus::None) Ok(false)
} }
} }
} }
// Waits until the message has either been processed or the task id has been cancelled // Waits until the message has either been processed or the task id has been cancelled
// by Gelato. // by Gelato.
async fn poll_for_terminal_state(&self, task_id: String) -> Result<MessageStatus> { async fn poll_for_terminal_state(&self, task_id: String) -> Result<bool> {
loop { loop {
sleep(self.opts.poll_interval).await; sleep(self.opts.poll_interval).await;
// Check if the message has been processed. Checking with the Inbox directly // Check if the message has been processed. Checking with the Inbox directly
// is the best source of truth, and is the only way in which a message can be // is the best source of truth, and is the only way in which a message can be
// marked as processed. // marked as processed.
if let Ok(MessageStatus::Processed) = self.message_status().await { if let Ok(true) = self.message_delivered().await {
return Ok(MessageStatus::Processed); return Ok(true);
} }
// Get the status of the SponsoredCall task from Gelato for debugging. // Get the status of the SponsoredCall task from Gelato for debugging.
@ -196,7 +193,7 @@ impl SponsoredCallOp {
// Gelato has reached the max # of retries for a task. Currently, the default is // Gelato has reached the max # of retries for a task. Currently, the default is
// after about 30 seconds. // after about 30 seconds.
if let TaskState::Cancelled = task_state { if let TaskState::Cancelled = task_state {
return Ok(MessageStatus::None); return Ok(false);
} }
} }
} }
@ -205,7 +202,7 @@ impl SponsoredCallOp {
// the DB here. This is why sponsored call args are created and signed for each // the DB here. This is why sponsored call args are created and signed for each
// sponsored call call. // sponsored call call.
async fn send_sponsored_call_api_call(&self) -> Result<SponsoredCallApiCallResult> { async fn send_sponsored_call_api_call(&self) -> Result<SponsoredCallApiCallResult> {
let args = self.create_sponsored_call_args(); let args = self.create_sponsored_call_args().await?;
let sponsored_call_api_call = SponsoredCallApiCall { let sponsored_call_api_call = SponsoredCallApiCall {
args: &args, args: &args,
@ -216,30 +213,25 @@ impl SponsoredCallOp {
Ok(sponsored_call_api_call.run().await?) Ok(sponsored_call_api_call.run().await?)
} }
fn create_sponsored_call_args(&self) -> SponsoredCallArgs { async fn create_sponsored_call_args(&self) -> Result<SponsoredCallArgs> {
let calldata = self.inbox_contracts.validator_manager.process_calldata( let metadata = self
&self.message.checkpoint, .multisig_ism
&self.message.committed_message.message, .format_metadata(&self.message.checkpoint, self.message.proof)
&self.message.proof, .await?;
); let calldata = self
SponsoredCallArgs { .mailbox
.process_calldata(&self.message.message, &metadata);
Ok(SponsoredCallArgs {
chain_id: self.destination_chain, chain_id: self.destination_chain,
target: self target: self.mailbox.address().into(),
.inbox_contracts
.validator_manager
.contract_address()
.into(),
data: calldata.into(), data: calldata.into(),
gas_limit: None, // Gelato will handle gas estimation gas_limit: None, // Gelato will handle gas estimation
retries: None, // Use Gelato's default of 5 retries, each ~5 seconds apart retries: None, // Use Gelato's default of 5 retries, each ~5 seconds apart
} })
} }
async fn message_status(&self) -> Result<MessageStatus, ChainCommunicationError> { async fn message_delivered(&self) -> Result<bool, ChainCommunicationError> {
self.inbox_contracts self.mailbox.delivered(self.message.message.id()).await
.inbox
.message_status(self.message.committed_message.to_leaf())
.await
} }
fn send_message_processed( fn send_message_processed(

@ -1,6 +1,6 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use abacus_core::{accumulator::merkle::Proof, CommittedMessage, MultisigSignedCheckpoint}; use abacus_core::{accumulator::merkle::Proof, AbacusMessage, MultisigSignedCheckpoint};
use tokio::time::Instant; use tokio::time::Instant;
@ -30,8 +30,7 @@ pub mod serial_submitter;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SubmitMessageArgs { pub struct SubmitMessageArgs {
pub leaf_index: u32, pub message: AbacusMessage,
pub committed_message: CommittedMessage,
pub checkpoint: MultisigSignedCheckpoint, pub checkpoint: MultisigSignedCheckpoint,
pub proof: Proof, pub proof: Proof,
pub enqueue_time: Instant, pub enqueue_time: Instant,
@ -40,15 +39,13 @@ pub struct SubmitMessageArgs {
impl SubmitMessageArgs { impl SubmitMessageArgs {
pub fn new( pub fn new(
leaf_index: u32, message: AbacusMessage,
committed_message: CommittedMessage,
checkpoint: MultisigSignedCheckpoint, checkpoint: MultisigSignedCheckpoint,
proof: Proof, proof: Proof,
enqueue_time: Instant, enqueue_time: Instant,
) -> Self { ) -> Self {
SubmitMessageArgs { SubmitMessageArgs {
leaf_index, message,
committed_message,
checkpoint, checkpoint,
proof, proof,
enqueue_time, enqueue_time,
@ -67,7 +64,7 @@ impl SubmitMessageArgs {
impl Ord for SubmitMessageArgs { impl Ord for SubmitMessageArgs {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
match self.num_retries.cmp(&other.num_retries) { match self.num_retries.cmp(&other.num_retries) {
Ordering::Equal => match self.leaf_index.cmp(&other.leaf_index) { Ordering::Equal => match self.message.nonce.cmp(&other.message.nonce) {
Ordering::Equal => Ordering::Equal, Ordering::Equal => Ordering::Equal,
Ordering::Less => Ordering::Greater, Ordering::Less => Ordering::Greater,
Ordering::Greater => Ordering::Less, Ordering::Greater => Ordering::Less,
@ -86,7 +83,7 @@ impl PartialOrd for SubmitMessageArgs {
impl PartialEq for SubmitMessageArgs { impl PartialEq for SubmitMessageArgs {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.num_retries == other.num_retries && self.leaf_index == other.leaf_index self.num_retries == other.num_retries && self.message.nonce == other.message.nonce
} }
} }

@ -9,10 +9,8 @@ use tokio::{
}; };
use tracing::{debug, info_span, instrument, instrument::Instrumented, warn, Instrument}; use tracing::{debug, info_span, instrument, instrument::Instrumented, warn, Instrument};
use abacus_base::{CoreMetrics, InboxContracts}; use abacus_base::{CachingMailbox, CoreMetrics};
use abacus_core::{ use abacus_core::{db::AbacusDB, AbacusContract, AbacusMessage, Mailbox, MultisigSignedCheckpoint};
db::AbacusDB, AbacusCommon, AbacusContract, CommittedMessage, MultisigSignedCheckpoint,
};
use crate::{merkle_tree_builder::MerkleTreeBuilder, settings::matching_list::MatchingList}; use crate::{merkle_tree_builder::MerkleTreeBuilder, settings::matching_list::MatchingList};
@ -21,21 +19,21 @@ use super::SubmitMessageArgs;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct MessageProcessor { pub(crate) struct MessageProcessor {
db: AbacusDB, db: AbacusDB,
inbox_contracts: InboxContracts, destination_mailbox: CachingMailbox,
whitelist: Arc<MatchingList>, whitelist: Arc<MatchingList>,
blacklist: Arc<MatchingList>, blacklist: Arc<MatchingList>,
metrics: MessageProcessorMetrics, metrics: MessageProcessorMetrics,
tx_msg: mpsc::UnboundedSender<SubmitMessageArgs>, tx_msg: mpsc::UnboundedSender<SubmitMessageArgs>,
ckpt_rx: watch::Receiver<Option<MultisigSignedCheckpoint>>, ckpt_rx: watch::Receiver<Option<MultisigSignedCheckpoint>>,
prover_sync: MerkleTreeBuilder, prover_sync: MerkleTreeBuilder,
message_leaf_index: u32, message_nonce: u32,
} }
impl MessageProcessor { impl MessageProcessor {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn new( pub(crate) fn new(
db: AbacusDB, db: AbacusDB,
inbox_contracts: InboxContracts, destination_mailbox: CachingMailbox,
whitelist: Arc<MatchingList>, whitelist: Arc<MatchingList>,
blacklist: Arc<MatchingList>, blacklist: Arc<MatchingList>,
metrics: MessageProcessorMetrics, metrics: MessageProcessorMetrics,
@ -44,14 +42,14 @@ impl MessageProcessor {
) -> Self { ) -> Self {
Self { Self {
db: db.clone(), db: db.clone(),
inbox_contracts, destination_mailbox,
whitelist, whitelist,
blacklist, blacklist,
metrics, metrics,
tx_msg, tx_msg,
ckpt_rx, ckpt_rx,
prover_sync: MerkleTreeBuilder::new(db), prover_sync: MerkleTreeBuilder::new(db),
message_leaf_index: 0, message_nonce: 0,
} }
} }
@ -60,7 +58,7 @@ impl MessageProcessor {
tokio::spawn(async move { self.main_loop().await }).instrument(span) tokio::spawn(async move { self.main_loop().await }).instrument(span)
} }
#[instrument(ret, err, skip(self), fields(inbox_name=self.inbox_contracts.inbox.chain_name(), local_domain=?self.inbox_contracts.inbox.local_domain()), level = "info")] #[instrument(ret, err, skip(self), fields(chain=self.destination_mailbox.chain_name(), domain=?self.destination_mailbox.local_domain()), level = "info")]
async fn main_loop(mut self) -> Result<()> { async fn main_loop(mut self) -> Result<()> {
// Ensure that there is at least one valid, known checkpoint before starting // Ensure that there is at least one valid, known checkpoint before starting
// work loop. // work loop.
@ -72,8 +70,8 @@ impl MessageProcessor {
} }
// Forever, scan AbacusDB looking for new messages to send. When criteria are // Forever, scan AbacusDB looking for new messages to send. When criteria are
// satisfied or the message is disqualified, push the message onto // satisfied or the message is disqualified, push the message onto
// self.tx_msg and then continue the scan at the next outbox highest // self.tx_msg and then continue the scan at the next highest
// leaf index. // nonce.
loop { loop {
self.tick().await?; self.tick().await?;
} }
@ -84,35 +82,31 @@ impl MessageProcessor {
async fn tick(&mut self) -> Result<()> { async fn tick(&mut self) -> Result<()> {
self.metrics self.metrics
.processor_loop_gauge .processor_loop_gauge
.set(self.message_leaf_index as i64); .set(self.message_nonce as i64);
// Scan until we find next index without delivery confirmation. // Scan until we find next nonce without delivery confirmation.
if self if self
.db .db
.retrieve_leaf_processing_status(self.message_leaf_index)? .retrieve_message_processed(self.message_nonce)?
.is_some() .is_some()
{ {
debug!( debug!(
inbox_name=?self.inbox_contracts.inbox.chain_name(), chain=?self.destination_mailbox.chain_name(),
local_domain=?self.inbox_contracts.inbox.local_domain(), domain=?self.destination_mailbox.local_domain(),
idx=?self.message_leaf_index, nonce=?self.message_nonce,
"Skipping since message_index already in DB"); "Skipping since message_nonce already in DB");
self.message_leaf_index += 1; self.message_nonce += 1;
return Ok(()); return Ok(());
} }
let message = if let Some(msg) = self let message = if let Some(msg) = self
.db .db
.message_by_leaf_index(self.message_leaf_index)? .message_by_nonce(self.message_nonce)?
.map(CommittedMessage::try_from) .map(AbacusMessage::from)
.transpose()?
{ {
debug!(msg=?msg, "Working on msg"); debug!(msg=?msg, "Working on msg");
msg msg
} else { } else {
debug!( debug!("Leaf in db without message nonce: {}", self.message_nonce);
"Leaf in db without message idx: {}",
self.message_leaf_index
);
// Not clear what the best thing to do here is, but there is seemingly an // Not clear what the best thing to do here is, but there is seemingly an
// existing race wherein an indexer might non-atomically write leaf // existing race wherein an indexer might non-atomically write leaf
// info to rocksdb across a few records, so we might see the leaf // info to rocksdb across a few records, so we might see the leaf
@ -125,51 +119,48 @@ impl MessageProcessor {
}; };
// Skip if for different inbox. // Skip if for different inbox.
if message.message.destination != self.inbox_contracts.inbox.local_domain() { if message.destination != self.destination_mailbox.local_domain() {
debug!( debug!(
inbox_name=?self.inbox_contracts.inbox.chain_name(), id=?message.id(),
local_domain=?self.inbox_contracts.inbox.local_domain(), destination=message.destination,
dst=?message.message.destination, nonce=message.nonce,
msg=?message, "Message destined for other domain, skipping");
"Message not for local domain, skipping idx {}", self.message_leaf_index); self.message_nonce += 1;
self.message_leaf_index += 1;
return Ok(()); return Ok(());
} }
// Skip if not whitelisted. // Skip if not whitelisted.
if !self.whitelist.msg_matches(&message.message, true) { if !self.whitelist.msg_matches(&message, true) {
debug!( debug!(
inbox_name=?self.inbox_contracts.inbox.chain_name(), id=?message.id(),
local_domain=?self.inbox_contracts.inbox.local_domain(), destination=message.destination,
dst=?message.message.destination, nonce=message.nonce,
whitelist=?self.whitelist, whitelist=?self.whitelist,
msg=?message, "Message not whitelisted, skipping");
"Message not whitelisted, skipping idx {}", self.message_leaf_index); self.message_nonce += 1;
self.message_leaf_index += 1;
return Ok(()); return Ok(());
} }
// skip if the message is blacklisted // Skip if the message is blacklisted
if self.blacklist.msg_matches(&message.message, false) { if self.blacklist.msg_matches(&message, false) {
debug!( debug!(
inbox_name=?self.inbox_contracts.inbox.chain_name(), id=?message.id(),
local_domain=?self.inbox_contracts.inbox.local_domain(), destination=message.destination,
dst=?message.message.destination, nonce=message.nonce,
blacklist=?self.blacklist, blacklist=?self.blacklist,
msg=?message, "Message blacklisted, skipping");
"Message blacklisted, skipping idx {}", self.message_leaf_index); self.message_nonce += 1;
self.message_leaf_index += 1;
return Ok(()); return Ok(());
} }
// If validator hasn't published checkpoint covering self.message_leaf_index // If validator hasn't published checkpoint covering self.message_nonce
// yet, wait until it has, before forwarding the message to the // yet, wait until it has, before forwarding the message to the
// submitter channel. // submitter channel.
let mut ckpt; let mut ckpt;
loop { loop {
ckpt = self.ckpt_rx.borrow().clone(); ckpt = self.ckpt_rx.borrow().clone();
match &ckpt { match &ckpt {
Some(ckpt) if ckpt.checkpoint.index >= self.message_leaf_index => { Some(ckpt) if ckpt.checkpoint.index >= self.message_nonce => {
break; break;
} }
_ => { _ => {
@ -178,7 +169,7 @@ impl MessageProcessor {
} }
} }
let checkpoint = ckpt.unwrap(); let checkpoint = ckpt.unwrap();
assert!(checkpoint.checkpoint.index >= self.message_leaf_index); assert!(checkpoint.checkpoint.index >= self.message_nonce);
// Include proof against checkpoint for message in the args provided to the // Include proof against checkpoint for message in the args provided to the
// submitter. // submitter.
@ -188,32 +179,23 @@ impl MessageProcessor {
.await?; .await?;
} }
assert_eq!(checkpoint.checkpoint.index + 1, self.prover_sync.count()); assert_eq!(checkpoint.checkpoint.index + 1, self.prover_sync.count());
let proof = self.prover_sync.get_proof(self.message_leaf_index)?; let proof = self.prover_sync.get_proof(self.message_nonce)?;
if self if self.db.message_id_by_nonce(self.message_nonce)?.is_some() {
.db
.leaf_by_leaf_index(self.message_leaf_index)?
.is_some()
{
debug!( debug!(
"Sending message at idx {} to submitter", id=?message.id(),
self.message_leaf_index nonce=message.nonce,
"Sending message to submitter"
); );
// Finally, build the submit arg and dispatch it to the submitter. // Finally, build the submit arg and dispatch it to the submitter.
let submit_args = SubmitMessageArgs::new( let submit_args = SubmitMessageArgs::new(message, checkpoint, proof, Instant::now());
self.message_leaf_index,
message,
checkpoint,
proof,
Instant::now(),
);
self.tx_msg.send(submit_args)?; self.tx_msg.send(submit_args)?;
self.message_leaf_index += 1; self.message_nonce += 1;
} else { } else {
warn!( warn!(
idx=self.message_leaf_index, nonce=self.message_nonce,
inbox_name=?self.inbox_contracts.inbox.chain_name(), chain=?self.destination_mailbox.chain_name(),
"Unexpected missing leaf_by_leaf_index"); "Unexpected missing message_id_by_nonce");
} }
Ok(()) Ok(())
} }
@ -225,12 +207,12 @@ pub(crate) struct MessageProcessorMetrics {
} }
impl MessageProcessorMetrics { impl MessageProcessorMetrics {
pub fn new(metrics: &CoreMetrics, outbox_chain: &str, inbox_chain: &str) -> Self { pub fn new(metrics: &CoreMetrics, origin_chain: &str, destination_chain: &str) -> Self {
Self { Self {
processor_loop_gauge: metrics.last_known_message_leaf_index().with_label_values(&[ processor_loop_gauge: metrics.last_known_message_nonce().with_label_values(&[
"processor_loop", "processor_loop",
outbox_chain, origin_chain,
inbox_chain, destination_chain,
]), ]),
} }
} }

@ -1,13 +1,10 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::Arc; use std::sync::Arc;
use abacus_base::CachingMailbox;
use abacus_base::CoreMetrics; use abacus_base::CoreMetrics;
use abacus_base::InboxContracts;
use abacus_core::db::AbacusDB; use abacus_core::db::AbacusDB;
use abacus_core::AbacusContract; use abacus_core::{AbacusContract, Mailbox, MultisigIsm};
use abacus_core::Inbox;
use abacus_core::InboxValidatorManager;
use abacus_core::MessageStatus;
use eyre::{bail, Result}; use eyre::{bail, Result};
use prometheus::{Histogram, IntCounter, IntGauge}; use prometheus::{Histogram, IntCounter, IntGauge};
use tokio::sync::mpsc; use tokio::sync::mpsc;
@ -121,8 +118,10 @@ pub(crate) struct SerialSubmitter {
/// to be dispatched. The SerialSubmitter can only dispatch one message at a time, so this /// to be dispatched. The SerialSubmitter can only dispatch one message at a time, so this
/// queue could grow. /// queue could grow.
run_queue: VecDeque<SubmitMessageArgs>, run_queue: VecDeque<SubmitMessageArgs>,
/// Inbox / InboxValidatorManager on the destination chain. /// Mailbox on the destination chain.
inbox_contracts: InboxContracts, mailbox: CachingMailbox,
/// Multisig ism on the destination chain.
multisig_ism: Arc<dyn MultisigIsm>,
/// Interface to agent rocks DB for e.g. writing delivery status upon completion. /// Interface to agent rocks DB for e.g. writing delivery status upon completion.
db: AbacusDB, db: AbacusDB,
/// Metrics for serial submitter. /// Metrics for serial submitter.
@ -134,7 +133,8 @@ pub(crate) struct SerialSubmitter {
impl SerialSubmitter { impl SerialSubmitter {
pub(crate) fn new( pub(crate) fn new(
rx: mpsc::UnboundedReceiver<SubmitMessageArgs>, rx: mpsc::UnboundedReceiver<SubmitMessageArgs>,
inbox_contracts: InboxContracts, mailbox: CachingMailbox,
multisig_ism: Arc<dyn MultisigIsm>,
db: AbacusDB, db: AbacusDB,
metrics: SerialSubmitterMetrics, metrics: SerialSubmitterMetrics,
gas_payment_enforcer: Arc<GasPaymentEnforcer>, gas_payment_enforcer: Arc<GasPaymentEnforcer>,
@ -143,7 +143,8 @@ impl SerialSubmitter {
rx, rx,
wait_queue: Vec::new(), wait_queue: Vec::new(),
run_queue: VecDeque::new(), run_queue: VecDeque::new(),
inbox_contracts, mailbox,
multisig_ism,
db, db,
metrics, metrics,
gas_payment_enforcer, gas_payment_enforcer,
@ -155,7 +156,7 @@ impl SerialSubmitter {
.instrument(info_span!("serial submitter work loop")) .instrument(info_span!("serial submitter work loop"))
} }
#[instrument(skip_all, fields(ibx=self.inbox_contracts.inbox.inbox().chain_name()))] #[instrument(skip_all, fields(mbx=self.mailbox.chain_name()))]
async fn work_loop(&mut self) -> Result<()> { async fn work_loop(&mut self) -> Result<()> {
loop { loop {
self.tick().await?; self.tick().await?;
@ -212,18 +213,18 @@ impl SerialSubmitter {
}; };
match self.process_message(&msg).await { match self.process_message(&msg).await {
Ok(MessageStatus::Processed) => { Ok(true) => {
info!(msg=?msg, msg_leaf_index=msg.leaf_index, "Message processed"); info!(id=?msg.message.id(), nonce=msg.message.nonce, "Message processed");
self.record_message_process_success(&msg)?; self.record_message_process_success(&msg)?;
return Ok(()); return Ok(());
} }
Ok(MessageStatus::None) => { Ok(false) => {
info!(msg=?msg, msg_leaf_index=msg.leaf_index, "Message not processed"); info!(id=?msg.message.id(), nonce=msg.message.nonce, "Message not processed");
} }
// We expect this branch to be hit when there is unexpected behavior - // We expect this branch to be hit when there is unexpected behavior -
// defined behavior like gas estimation failing will not hit this branch. // defined behavior like gas estimation failing will not hit this branch.
Err(err) => { Err(err) => {
warn!(msg=?msg, msg_leaf_index=msg.leaf_index, error=?err, "Error occurred when attempting to process message"); warn!(id=?msg.message.id(), nonce=msg.message.nonce, error=?err, "Error occurred when attempting to process message");
} }
} }
@ -240,46 +241,44 @@ impl SerialSubmitter {
/// been processed, Ok(MessageStatus::Processed) is returned. If this message is unable to /// been processed, Ok(MessageStatus::Processed) is returned. If this message is unable to
/// be processed, either due to failed gas estimation or an insufficient gas payment, /// be processed, either due to failed gas estimation or an insufficient gas payment,
/// Ok(MessageStatus::None) is returned. /// Ok(MessageStatus::None) is returned.
#[instrument(skip(self, msg), fields(msg_leaf_index=msg.leaf_index))] #[instrument(skip(self, msg), fields(msg_nonce=msg.message.nonce))]
async fn process_message(&self, msg: &SubmitMessageArgs) -> Result<MessageStatus> { async fn process_message(&self, msg: &SubmitMessageArgs) -> Result<bool> {
// If the message has already been processed according to message_status call on // If the message has already been processed according to message_status call on
// inbox, e.g. due to another relayer having already processed, then mark it as // inbox, e.g. due to another relayer having already processed, then mark it as
// already-processed, and move on to the next tick. // already-processed, and move on to the next tick.
// TODO(webbhorn): Make this robust to re-orgs on inbox. // TODO(webbhorn): Make this robust to re-orgs on inbox.
if let MessageStatus::Processed = self if self.mailbox.delivered(msg.message.id()).await? {
.inbox_contracts
.inbox
.message_status(msg.committed_message.to_leaf())
.await?
{
info!("Message already processed"); info!("Message already processed");
return Ok(MessageStatus::Processed); return Ok(true);
} }
let metadata = self
.multisig_ism
.format_metadata(&msg.checkpoint, msg.proof)
.await?;
// Estimate transaction costs for the process call. If there are issues, it's likely // Estimate transaction costs for the process call. If there are issues, it's likely
// that gas estimation has failed because the message is reverting. This is defined behavior, // that gas estimation has failed because the message is reverting. This is defined behavior,
// so we just log the error and move onto the next tick. // so we just log the error and move onto the next tick.
let tx_cost_estimate = match self let tx_cost_estimate = match self
.inbox_contracts .mailbox
.validator_manager .process_estimate_costs(&msg.message, &metadata)
.process_estimate_costs(&msg.checkpoint, &msg.committed_message.message, &msg.proof)
.await .await
{ {
Ok(tx_cost_estimate) => tx_cost_estimate, Ok(tx_cost_estimate) => tx_cost_estimate,
Err(err) => { Err(err) => {
info!(msg=?msg, error=?err, "Error estimating process costs"); info!(msg=?msg, error=?err, "Error estimating process costs");
return Ok(MessageStatus::None); return Ok(false);
} }
}; };
// If the gas payment requirement hasn't been met, move to the next tick. // If the gas payment requirement hasn't been met, move to the next tick.
let (meets_gas_requirement, gas_payment) = self let (meets_gas_requirement, gas_payment) = self
.gas_payment_enforcer .gas_payment_enforcer
.message_meets_gas_payment_requirement(&msg.committed_message, &tx_cost_estimate) .message_meets_gas_payment_requirement(&msg.message, &tx_cost_estimate)
.await?; .await?;
if !meets_gas_requirement { if !meets_gas_requirement {
tracing::info!(gas_payment=?gas_payment, "Gas payment requirement not met yet"); tracing::info!(gas_payment=?gas_payment, "Gas payment requirement not met yet");
return Ok(MessageStatus::None); return Ok(false);
} }
// Go ahead and attempt processing of message to destination chain. // Go ahead and attempt processing of message to destination chain.
@ -293,14 +292,8 @@ impl SerialSubmitter {
// We use the estimated gas limit from the prior call to `process_estimate_costs` to // We use the estimated gas limit from the prior call to `process_estimate_costs` to
// avoid a second gas estimation. // avoid a second gas estimation.
let process_result = self let process_result = self
.inbox_contracts .mailbox
.validator_manager .process(&msg.message, &metadata, Some(tx_cost_estimate.gas_limit))
.process(
&msg.checkpoint,
&msg.committed_message.message,
&msg.proof,
Some(tx_cost_estimate.gas_limit),
)
.await; .await;
match process_result { match process_result {
// TODO(trevor): Instead of immediately marking as processed, move to a verification // TODO(trevor): Instead of immediately marking as processed, move to a verification
@ -312,11 +305,11 @@ impl SerialSubmitter {
info!(hash=?outcome.txid, info!(hash=?outcome.txid,
wq_sz=?self.wait_queue.len(), rq_sz=?self.run_queue.len(), wq_sz=?self.wait_queue.len(), rq_sz=?self.run_queue.len(),
"Message successfully processed by transaction"); "Message successfully processed by transaction");
Ok(MessageStatus::Processed) Ok(true)
} }
Ok(outcome) => { Ok(outcome) => {
info!(hash=?outcome.txid, "Transaction attempting to process transaction reverted"); info!(hash=?outcome.txid, "Transaction attempting to process transaction reverted");
Ok(MessageStatus::None) Ok(false)
} }
Err(e) => Err(e.into()), Err(e) => Err(e.into()),
} }
@ -328,15 +321,15 @@ impl SerialSubmitter {
/// return 'Ok(())', then without a wiped AbacusDB, we will never re-attempt processing for /// return 'Ok(())', then without a wiped AbacusDB, we will never re-attempt processing for
/// this message again, even after the relayer restarts. /// this message again, even after the relayer restarts.
fn record_message_process_success(&mut self, msg: &SubmitMessageArgs) -> Result<()> { fn record_message_process_success(&mut self, msg: &SubmitMessageArgs) -> Result<()> {
self.db.mark_leaf_as_processed(msg.leaf_index)?; self.db.mark_nonce_as_processed(msg.message.nonce)?;
self.metrics self.metrics
.queue_duration_hist .queue_duration_hist
.observe((Instant::now() - msg.enqueue_time).as_secs_f64()); .observe((Instant::now() - msg.enqueue_time).as_secs_f64());
self.metrics.max_submitted_leaf_index = self.metrics.max_submitted_nonce =
std::cmp::max(self.metrics.max_submitted_leaf_index, msg.leaf_index); std::cmp::max(self.metrics.max_submitted_nonce, msg.message.nonce);
self.metrics self.metrics
.processed_gauge .processed_gauge
.set(self.metrics.max_submitted_leaf_index as i64); .set(self.metrics.max_submitted_nonce as i64);
self.metrics.messages_processed_count.inc(); self.metrics.messages_processed_count.inc();
Ok(()) Ok(())
} }
@ -351,34 +344,34 @@ pub(crate) struct SerialSubmitterMetrics {
messages_processed_count: IntCounter, messages_processed_count: IntCounter,
/// Private state used to update actual metrics each tick. /// Private state used to update actual metrics each tick.
max_submitted_leaf_index: u32, max_submitted_nonce: u32,
} }
impl SerialSubmitterMetrics { impl SerialSubmitterMetrics {
pub fn new(metrics: &CoreMetrics, outbox_chain: &str, inbox_chain: &str) -> Self { pub fn new(metrics: &CoreMetrics, origin_chain: &str, destination_chain: &str) -> Self {
Self { Self {
run_queue_length_gauge: metrics.submitter_queue_length().with_label_values(&[ run_queue_length_gauge: metrics.submitter_queue_length().with_label_values(&[
outbox_chain, origin_chain,
inbox_chain, destination_chain,
"run_queue", "run_queue",
]), ]),
wait_queue_length_gauge: metrics.submitter_queue_length().with_label_values(&[ wait_queue_length_gauge: metrics.submitter_queue_length().with_label_values(&[
outbox_chain, origin_chain,
inbox_chain, destination_chain,
"wait_queue", "wait_queue",
]), ]),
queue_duration_hist: metrics queue_duration_hist: metrics
.submitter_queue_duration_histogram() .submitter_queue_duration_histogram()
.with_label_values(&[outbox_chain, inbox_chain]), .with_label_values(&[origin_chain, destination_chain]),
messages_processed_count: metrics messages_processed_count: metrics
.messages_processed_count() .messages_processed_count()
.with_label_values(&[outbox_chain, inbox_chain]), .with_label_values(&[origin_chain, destination_chain]),
processed_gauge: metrics.last_known_message_leaf_index().with_label_values(&[ processed_gauge: metrics.last_known_message_nonce().with_label_values(&[
"message_processed", "message_processed",
outbox_chain, origin_chain,
inbox_chain, destination_chain,
]), ]),
max_submitted_leaf_index: 0, max_submitted_nonce: 0,
} }
} }
} }

@ -1,18 +1,17 @@
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use abacus_base::chains::TransactionSubmissionType; use abacus_base::chains::TransactionSubmissionType;
use abacus_base::CachingMailbox;
use async_trait::async_trait; use async_trait::async_trait;
use eyre::Result; use eyre::Result;
use tokio::time::MissedTickBehavior;
use tokio::{sync::mpsc, sync::watch, task::JoinHandle}; use tokio::{sync::mpsc, sync::watch, task::JoinHandle};
use tracing::{info, info_span, instrument::Instrumented, Instrument}; use tracing::{info, info_span, instrument::Instrumented, Instrument};
use abacus_base::{ use abacus_base::{
chains::GelatoConf, run_all, AbacusAgentCore, Agent, BaseAgent, CachingInterchainGasPaymaster, chains::GelatoConf, run_all, AbacusAgentCore, Agent, BaseAgent, ContractSyncMetrics,
ContractSyncMetrics, CoreMetrics, InboxContracts, MultisigCheckpointSyncer, CoreMetrics, MultisigCheckpointSyncer,
}; };
use abacus_core::{AbacusContract, MultisigSignedCheckpoint, Signers}; use abacus_core::{AbacusContract, MultisigIsm, MultisigSignedCheckpoint, Signers};
use crate::msg::gas_payment::GasPaymentEnforcer; use crate::msg::gas_payment::GasPaymentEnforcer;
use crate::msg::gelato_submitter::{GelatoSubmitter, GelatoSubmitterMetrics}; use crate::msg::gelato_submitter::{GelatoSubmitter, GelatoSubmitterMetrics};
@ -26,6 +25,7 @@ use crate::{checkpoint_fetcher::CheckpointFetcher, msg::serial_submitter::Serial
/// A relayer agent /// A relayer agent
#[derive(Debug)] #[derive(Debug)]
pub struct Relayer { pub struct Relayer {
origin_chain_name: String,
signed_checkpoint_polling_interval: u64, signed_checkpoint_polling_interval: u64,
multisig_checkpoint_syncer: MultisigCheckpointSyncer, multisig_checkpoint_syncer: MultisigCheckpointSyncer,
core: AbacusAgentCore, core: AbacusAgentCore,
@ -53,13 +53,13 @@ impl BaseAgent for Relayer {
{ {
let core = settings let core = settings
.as_ref() .as_ref()
.try_into_abacus_core(metrics, true) .try_into_abacus_core(metrics, None)
.await?; .await?;
let multisig_checkpoint_syncer: MultisigCheckpointSyncer = settings let multisig_checkpoint_syncer: MultisigCheckpointSyncer = settings
.multisigcheckpointsyncer .multisigcheckpointsyncer
.try_into_multisig_checkpoint_syncer( .try_into_multisig_checkpoint_syncer(
core.outbox.outbox().chain_name(), &settings.originchainname,
core.metrics.validator_checkpoint_index(), core.metrics.validator_checkpoint_index(),
)?; )?;
@ -68,6 +68,7 @@ impl BaseAgent for Relayer {
info!(whitelist = %whitelist, blacklist = %blacklist, "Whitelist configuration"); info!(whitelist = %whitelist, blacklist = %blacklist, "Whitelist configuration");
Ok(Self { Ok(Self {
origin_chain_name: settings.originchainname,
signed_checkpoint_polling_interval: settings signed_checkpoint_polling_interval: settings
.signedcheckpointpollinginterval .signedcheckpointpollinginterval
.parse() .parse()
@ -85,26 +86,33 @@ impl BaseAgent for Relayer {
let (signed_checkpoint_sender, signed_checkpoint_receiver) = let (signed_checkpoint_sender, signed_checkpoint_receiver) =
watch::channel::<Option<MultisigSignedCheckpoint>>(None); watch::channel::<Option<MultisigSignedCheckpoint>>(None);
let inboxes = self.inboxes(); let num_mailboxes = self.core.mailboxes.len();
let mut tasks = Vec::with_capacity(inboxes.len() + 3); let mut tasks = Vec::with_capacity(num_mailboxes + 2);
let gas_payment_enforcer = Arc::new(GasPaymentEnforcer::new( let gas_payment_enforcer = Arc::new(GasPaymentEnforcer::new(
self.gas_payment_enforcement_policy.clone(), self.gas_payment_enforcement_policy.clone(),
self.outbox().db().clone(), self.mailbox(&self.origin_chain_name).unwrap().db().clone(),
)); ));
for (inbox_name, inbox_contracts) in inboxes { for chain_name in self.core.mailboxes.keys() {
if chain_name.eq(&self.origin_chain_name) {
continue;
}
let signer = self let signer = self
.core .core
.settings .settings
.get_signer(inbox_name) .get_signer(chain_name)
.await .await
.expect("expected signer for inbox"); .expect("expected signer for mailbox");
tasks.push(self.run_inbox( let mailbox = self.mailbox(chain_name).unwrap();
inbox_contracts.clone(), let multisig_ism = self.multisig_ism(chain_name).unwrap();
tasks.push(self.run_destination_mailbox(
mailbox.clone(),
multisig_ism.clone(),
signed_checkpoint_receiver.clone(), signed_checkpoint_receiver.clone(),
self.core.settings.inboxes[inbox_name].txsubmission, self.core.settings.chains[chain_name].txsubmission,
self.core.settings.gelato.as_ref(), self.core.settings.gelato.as_ref(),
signer, signer,
gas_payment_enforcer.clone(), gas_payment_enforcer.clone(),
@ -114,36 +122,43 @@ impl BaseAgent for Relayer {
tasks.push(self.run_checkpoint_fetcher(signed_checkpoint_sender)); tasks.push(self.run_checkpoint_fetcher(signed_checkpoint_sender));
let sync_metrics = ContractSyncMetrics::new(self.core.metrics.clone()); let sync_metrics = ContractSyncMetrics::new(self.core.metrics.clone());
tasks.push(self.run_outbox_sync(sync_metrics.clone())); tasks.push(self.run_origin_mailbox_sync(sync_metrics.clone()));
if let Some(paymaster) = self.interchain_gas_paymaster() {
tasks.push(self.run_interchain_gas_paymaster_sync(paymaster.clone(), sync_metrics));
} else {
info!("Interchain Gas Paymaster not provided, not running sync");
}
tasks.push(self.run_outbox_metrics_loop()); tasks.push(self.run_interchain_gas_paymaster_sync(sync_metrics));
run_all(tasks) run_all(tasks)
} }
} }
impl Relayer { impl Relayer {
fn run_outbox_sync( fn run_origin_mailbox_sync(
&self, &self,
sync_metrics: ContractSyncMetrics, sync_metrics: ContractSyncMetrics,
) -> Instrumented<JoinHandle<Result<()>>> { ) -> Instrumented<JoinHandle<Result<()>>> {
let outbox = self.outbox(); let mailbox = self.mailbox(&self.origin_chain_name).unwrap();
let sync = outbox.sync(self.as_ref().indexer.clone(), sync_metrics); let sync = mailbox.sync(
self.as_ref().settings.chains[&self.origin_chain_name]
.index
.clone(),
sync_metrics,
);
sync sync
} }
fn run_interchain_gas_paymaster_sync( fn run_interchain_gas_paymaster_sync(
&self, &self,
paymaster: CachingInterchainGasPaymaster,
sync_metrics: ContractSyncMetrics, sync_metrics: ContractSyncMetrics,
) -> Instrumented<JoinHandle<Result<()>>> { ) -> Instrumented<JoinHandle<Result<()>>> {
paymaster.sync(self.as_ref().indexer.clone(), sync_metrics) let paymaster = self
.interchain_gas_paymaster(&self.origin_chain_name)
.unwrap();
let sync = paymaster.sync(
self.as_ref().settings.chains[&self.origin_chain_name]
.index
.clone(),
sync_metrics,
);
sync
} }
fn run_checkpoint_fetcher( fn run_checkpoint_fetcher(
@ -151,92 +166,68 @@ impl Relayer {
signed_checkpoint_sender: watch::Sender<Option<MultisigSignedCheckpoint>>, signed_checkpoint_sender: watch::Sender<Option<MultisigSignedCheckpoint>>,
) -> Instrumented<JoinHandle<Result<()>>> { ) -> Instrumented<JoinHandle<Result<()>>> {
let checkpoint_fetcher = CheckpointFetcher::new( let checkpoint_fetcher = CheckpointFetcher::new(
self.outbox().outbox(), self.mailbox(&self.origin_chain_name).unwrap(),
self.signed_checkpoint_polling_interval, self.signed_checkpoint_polling_interval,
self.multisig_checkpoint_syncer.clone(), self.multisig_checkpoint_syncer.clone(),
signed_checkpoint_sender, signed_checkpoint_sender,
self.core.metrics.last_known_message_leaf_index(), self.core.metrics.last_known_message_nonce(),
); );
checkpoint_fetcher.spawn() checkpoint_fetcher.spawn()
} }
fn run_outbox_metrics_loop(&self) -> Instrumented<JoinHandle<Result<()>>> {
let outbox = self.outbox().outbox().clone();
let outbox_name = outbox.chain_name();
let outbox_state_gauge = self
.core
.metrics
.outbox_state()
.with_label_values(&[outbox_name]);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(60 * 10));
interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
loop {
let state = outbox.state().await;
match &state {
Ok(state) => outbox_state_gauge.set(*state as u8 as i64),
Err(e) => tracing::warn!(error = %e, "Failed to get outbox state"),
};
interval.tick().await;
}
})
.instrument(info_span!("outbox_metrics_loop"))
}
/// Helper to construct a new GelatoSubmitter instance for submission to a /// Helper to construct a new GelatoSubmitter instance for submission to a
/// particular inbox. /// particular mailbox.
fn make_gelato_submitter_for_inbox( fn make_gelato_submitter(
&self, &self,
message_receiver: mpsc::UnboundedReceiver<SubmitMessageArgs>, message_receiver: mpsc::UnboundedReceiver<SubmitMessageArgs>,
inbox_contracts: InboxContracts, mailbox: CachingMailbox,
multisig_ism: Arc<dyn MultisigIsm>,
gelato_config: GelatoConf, gelato_config: GelatoConf,
gas_payment_enforcer: Arc<GasPaymentEnforcer>, gas_payment_enforcer: Arc<GasPaymentEnforcer>,
) -> GelatoSubmitter { ) -> GelatoSubmitter {
let inbox_chain_name = inbox_contracts.inbox.chain_name().to_owned(); let chain_name = mailbox.chain_name().to_owned();
GelatoSubmitter::new( GelatoSubmitter::new(
message_receiver, message_receiver,
inbox_contracts, mailbox,
self.outbox().db().clone(), multisig_ism,
self.mailbox(&self.origin_chain_name).unwrap().db().clone(),
gelato_config, gelato_config,
GelatoSubmitterMetrics::new( GelatoSubmitterMetrics::new(&self.core.metrics, &self.origin_chain_name, &chain_name),
&self.core.metrics,
self.outbox().outbox().chain_name(),
&inbox_chain_name,
),
gas_payment_enforcer, gas_payment_enforcer,
) )
} }
#[tracing::instrument(fields(inbox=%inbox_contracts.inbox.chain_name()))] #[allow(clippy::too_many_arguments)]
fn run_inbox( #[tracing::instrument(fields(destination=%destination_mailbox.chain_name()))]
fn run_destination_mailbox(
&self, &self,
inbox_contracts: InboxContracts, destination_mailbox: CachingMailbox,
multisig_ism: Arc<dyn MultisigIsm>,
signed_checkpoint_receiver: watch::Receiver<Option<MultisigSignedCheckpoint>>, signed_checkpoint_receiver: watch::Receiver<Option<MultisigSignedCheckpoint>>,
tx_submission: TransactionSubmissionType, tx_submission: TransactionSubmissionType,
gelato_config: Option<&GelatoConf>, gelato_config: Option<&GelatoConf>,
signer: Signers, signer: Signers,
gas_payment_enforcer: Arc<GasPaymentEnforcer>, gas_payment_enforcer: Arc<GasPaymentEnforcer>,
) -> Instrumented<JoinHandle<Result<()>>> { ) -> Instrumented<JoinHandle<Result<()>>> {
let outbox = self.outbox().outbox(); let origin_mailbox = self.mailbox(&self.origin_chain_name).unwrap();
let outbox_name = outbox.chain_name(); let destination = destination_mailbox.chain_name();
let inbox_name = inbox_contracts.inbox.chain_name(); let metrics =
let metrics = MessageProcessorMetrics::new( MessageProcessorMetrics::new(&self.core.metrics, &self.origin_chain_name, destination);
&self.core.metrics,
outbox_name,
inbox_contracts.inbox.chain_name(),
);
let (msg_send, msg_receive) = mpsc::unbounded_channel(); let (msg_send, msg_receive) = mpsc::unbounded_channel();
let submit_fut = match tx_submission { let submit_fut = match tx_submission {
TransactionSubmissionType::Gelato => { TransactionSubmissionType::Gelato => {
let gelato_config = gelato_config.unwrap_or_else(|| { let gelato_config = gelato_config.unwrap_or_else(|| {
panic!("Expected GelatoConf for inbox {} using Gelato", inbox_name) panic!(
"Expected GelatoConf for mailbox {} using Gelato",
destination
)
}); });
self.make_gelato_submitter_for_inbox( self.make_gelato_submitter(
msg_receive, msg_receive,
inbox_contracts.clone(), destination_mailbox.clone(),
multisig_ism,
gelato_config.clone(), gelato_config.clone(),
gas_payment_enforcer, gas_payment_enforcer,
) )
@ -245,12 +236,13 @@ impl Relayer {
TransactionSubmissionType::Signer => { TransactionSubmissionType::Signer => {
let serial_submitter = SerialSubmitter::new( let serial_submitter = SerialSubmitter::new(
msg_receive, msg_receive,
inbox_contracts.clone(), destination_mailbox.clone(),
self.outbox().db().clone(), multisig_ism,
origin_mailbox.db().clone(),
SerialSubmitterMetrics::new( SerialSubmitterMetrics::new(
&self.core.metrics, &self.core.metrics,
outbox_name, &self.origin_chain_name,
inbox_contracts.inbox.chain_name(), destination,
), ),
gas_payment_enforcer, gas_payment_enforcer,
); );
@ -259,8 +251,8 @@ impl Relayer {
}; };
let message_processor = MessageProcessor::new( let message_processor = MessageProcessor::new(
self.outbox().db().clone(), origin_mailbox.db().clone(),
inbox_contracts, destination_mailbox,
self.whitelist.clone(), self.whitelist.clone(),
self.blacklist.clone(), self.blacklist.clone(),
metrics, metrics,

@ -22,6 +22,8 @@ pub enum GasPaymentEnforcementPolicy {
} }
decl_settings!(Relayer { decl_settings!(Relayer {
// The name of the origin chain
originchainname: String,
/// The polling interval to check for new signed checkpoints in seconds /// The polling interval to check for new signed checkpoints in seconds
signedcheckpointpollinginterval: String, signedcheckpointpollinginterval: String,
/// The multisig checkpoint syncer configuration /// The multisig checkpoint syncer configuration

@ -3,9 +3,10 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use abacus_base::chains::IndexSettings;
use async_trait::async_trait; use async_trait::async_trait;
use ethers::types::H256; use ethers::types::H256;
use eyre::{eyre, Context, Result}; use eyre::{eyre, Result};
use sea_orm::prelude::TimeDateTime; use sea_orm::prelude::TimeDateTime;
use sea_orm::{Database, DbConn}; use sea_orm::{Database, DbConn};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@ -14,13 +15,10 @@ use tracing::instrument::Instrumented;
use tracing::{debug, info, info_span, instrument, trace, warn, Instrument}; use tracing::{debug, info, info_span, instrument, trace, warn, Instrument};
use abacus_base::last_message::validate_message_continuity; use abacus_base::last_message::validate_message_continuity;
use abacus_base::{ use abacus_base::{run_all, BaseAgent, ChainSetup, ContractSyncMetrics, CoreMetrics, Settings};
run_all, BaseAgent, ChainSetup, ContractSyncMetrics, CoreMetrics, IndexSettings,
OutboxAddresses, Settings,
};
use abacus_core::{ use abacus_core::{
name_from_domain_id, AbacusCommon, AbacusContract, CommittedMessage, ListValidity, LogMeta, name_from_domain_id, AbacusContract, AbacusMessage, ListValidity, LogMeta, Mailbox,
Outbox, OutboxIndexer, RawCommittedMessage, MailboxIndexer,
}; };
use crate::scraper::block_cursor::BlockCursor; use crate::scraper::block_cursor::BlockCursor;
@ -36,7 +34,6 @@ pub struct Scraper {
metrics: Arc<CoreMetrics>, metrics: Arc<CoreMetrics>,
/// A map of outbox contracts by name. /// A map of outbox contracts by name.
outboxes: HashMap<String, SqlOutboxScraper>, outboxes: HashMap<String, SqlOutboxScraper>,
inboxes: HashMap<String, ()>,
gas_paymasters: HashMap<String, ()>, gas_paymasters: HashMap<String, ()>,
} }
@ -60,12 +57,10 @@ impl BaseAgent for Scraper {
&metrics, &metrics,
) )
.await?; .await?;
let inboxes = Self::load_inboxes(&db, &core_settings, &metrics).await?;
let gas_paymasters = Self::load_gas_paymasters(&db, &core_settings, &metrics).await?; let gas_paymasters = Self::load_gas_paymasters(&db, &core_settings, &metrics).await?;
Ok(Self { Ok(Self {
metrics, metrics,
outboxes, outboxes,
inboxes,
gas_paymasters, gas_paymasters,
}) })
} }
@ -90,26 +85,17 @@ impl Scraper {
async fn load_outboxes( async fn load_outboxes(
db: &DbConn, db: &DbConn,
core_settings: &Settings, core_settings: &Settings,
config: HashMap<String, ChainSetup<OutboxAddresses>>, config: HashMap<String, ChainSetup>,
index_settings: &HashMap<String, IndexSettings>, index_settings: &HashMap<String, IndexSettings>,
metrics: &Arc<CoreMetrics>, metrics: &Arc<CoreMetrics>,
) -> Result<HashMap<String, SqlOutboxScraper>> { ) -> Result<HashMap<String, SqlOutboxScraper>> {
let contract_sync_metrics = ContractSyncMetrics::new(metrics.clone()); let contract_sync_metrics = ContractSyncMetrics::new(metrics.clone());
let mut outboxes = HashMap::new(); let mut outboxes = HashMap::new();
for (name, outbox_setup) in config { for (name, outbox_setup) in config {
if outbox_setup
.disabled
.as_ref()
.and_then(|d| d.parse::<bool>().ok())
.unwrap_or(false)
{
continue;
}
let signer = core_settings.get_signer(&name).await; let signer = core_settings.get_signer(&name).await;
let outbox = outbox_setup.try_into_outbox(signer, metrics).await?; let outbox = outbox_setup.try_into_mailbox(signer, metrics).await?;
let indexer = core_settings let indexer = core_settings
.try_outbox_indexer_from_config(metrics, &outbox_setup) .try_mailbox_indexer(metrics, &outbox_setup)
.await?; .await?;
let index_settings_for_chain = index_settings let index_settings_for_chain = index_settings
.get(outbox.chain_name()) .get(outbox.chain_name())
@ -129,15 +115,6 @@ impl Scraper {
Ok(outboxes) Ok(outboxes)
} }
async fn load_inboxes(
_db: &DbConn,
_core_settings: &Settings,
_metrics: &Arc<CoreMetrics>,
) -> Result<HashMap<String, ()>> {
// TODO
Ok(HashMap::new())
}
async fn load_gas_paymasters( async fn load_gas_paymasters(
_db: &DbConn, _db: &DbConn,
_core_settings: &Settings, _core_settings: &Settings,
@ -153,8 +130,8 @@ const MESSAGES_LABEL: &str = "messages";
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct SqlOutboxScraper { struct SqlOutboxScraper {
db: DbConn, db: DbConn,
outbox: Arc<dyn Outbox>, outbox: Arc<dyn Mailbox>,
indexer: Arc<dyn OutboxIndexer>, indexer: Arc<dyn MailboxIndexer>,
chunk_size: u32, chunk_size: u32,
metrics: ContractSyncMetrics, metrics: ContractSyncMetrics,
cursor: Arc<BlockCursor>, cursor: Arc<BlockCursor>,
@ -163,8 +140,8 @@ struct SqlOutboxScraper {
impl SqlOutboxScraper { impl SqlOutboxScraper {
pub async fn new( pub async fn new(
db: DbConn, db: DbConn,
outbox: Arc<dyn Outbox>, outbox: Arc<dyn Mailbox>,
indexer: Arc<dyn OutboxIndexer>, indexer: Arc<dyn MailboxIndexer>,
index_settings: &IndexSettings, index_settings: &IndexSettings,
metrics: ContractSyncMetrics, metrics: ContractSyncMetrics,
) -> Result<Self> { ) -> Result<Self> {
@ -201,13 +178,13 @@ impl SqlOutboxScraper {
let indexed_height = self.metrics.indexed_height.with_label_values(&labels); let indexed_height = self.metrics.indexed_height.with_label_values(&labels);
let stored_messages = self.metrics.stored_events.with_label_values(&labels); let stored_messages = self.metrics.stored_events.with_label_values(&labels);
let missed_messages = self.metrics.missed_events.with_label_values(&labels); let missed_messages = self.metrics.missed_events.with_label_values(&labels);
let message_leaf_index = self.metrics.message_leaf_index.clone(); let message_nonce = self.metrics.message_nonce.clone();
let chunk_size = self.chunk_size; let chunk_size = self.chunk_size;
// difference 1 // difference 1
let mut from = self.cursor.height().await as u32; let mut from = self.cursor.height().await as u32;
let mut last_valid_range_start_block = from; let mut last_valid_range_start_block = from;
let mut last_leaf_index = self.last_message_leaf_index().await?.unwrap_or(0); let mut last_nonce = self.last_message_nonce().await?.unwrap_or(0);
info!(from, chunk_size, chain_name, "Resuming outbox sync"); info!(from, chunk_size, chain_name, "Resuming outbox sync");
@ -242,7 +219,7 @@ impl SqlOutboxScraper {
// Difference 2 // Difference 2
sorted_messages = sorted_messages sorted_messages = sorted_messages
.into_iter() .into_iter()
.filter(|m| m.0.leaf_index > last_leaf_index) .filter(|m| m.0.nonce > last_nonce)
.collect(); .collect();
debug!( debug!(
@ -254,7 +231,7 @@ impl SqlOutboxScraper {
); );
match validate_message_continuity( match validate_message_continuity(
Some(last_leaf_index), Some(last_nonce),
&sorted_messages &sorted_messages
.iter() .iter()
.map(|(msg, _)| msg) .map(|(msg, _)| msg)
@ -262,29 +239,27 @@ impl SqlOutboxScraper {
) { ) {
ListValidity::Valid => { ListValidity::Valid => {
// Difference 3 // Difference 3
let max_leaf_index_of_batch = self.store_messages(&sorted_messages).await?; let max_nonce_of_batch = self.store_messages(&sorted_messages).await?;
stored_messages.inc_by(sorted_messages.len() as u64); stored_messages.inc_by(sorted_messages.len() as u64);
for (raw_msg, _) in sorted_messages.iter() { for (message, _) in sorted_messages.iter() {
let dst = CommittedMessage::try_from(raw_msg) let dst = name_from_domain_id(message.destination)
.ok()
.and_then(|msg| name_from_domain_id(msg.message.destination))
.unwrap_or_else(|| "unknown".into()); .unwrap_or_else(|| "unknown".into());
message_leaf_index message_nonce
.with_label_values(&["dispatch", chain_name, &dst]) .with_label_values(&["dispatch", chain_name, &dst])
.set(max_leaf_index_of_batch as i64); .set(max_nonce_of_batch as i64);
} }
// Difference 4 // Difference 4
self.cursor.update(full_chunk_from as u64).await; self.cursor.update(full_chunk_from as u64).await;
last_leaf_index = max_leaf_index_of_batch; last_nonce = max_nonce_of_batch;
last_valid_range_start_block = full_chunk_from; last_valid_range_start_block = full_chunk_from;
from = to + 1; from = to + 1;
} }
ListValidity::InvalidContinuation => { ListValidity::InvalidContinuation => {
missed_messages.inc(); missed_messages.inc();
warn!( warn!(
?last_leaf_index, ?last_nonce,
start_block = from, start_block = from,
end_block = to, end_block = to,
last_valid_range_start_block, last_valid_range_start_block,
@ -296,7 +271,7 @@ impl SqlOutboxScraper {
ListValidity::ContainsGaps => { ListValidity::ContainsGaps => {
missed_messages.inc(); missed_messages.inc();
warn!( warn!(
?last_leaf_index, ?last_nonce,
start_block = from, start_block = from,
end_block = to, end_block = to,
last_valid_range_start_block, last_valid_range_start_block,
@ -313,7 +288,7 @@ impl SqlOutboxScraper {
/// Get the highest message leaf index that is stored in the database. /// Get the highest message leaf index that is stored in the database.
#[instrument(skip(self))] #[instrument(skip(self))]
async fn last_message_leaf_index(&self) -> Result<Option<u32>> { async fn last_message_nonce(&self) -> Result<Option<u32>> {
use crate::db::message; use crate::db::message;
use sea_orm::{prelude::*, QueryOrder, QuerySelect}; use sea_orm::{prelude::*, QueryOrder, QuerySelect};
@ -345,48 +320,42 @@ impl SqlOutboxScraper {
skip_all, skip_all,
fields(messages = ?messages.iter().map(|(_, meta)| meta).collect::<Vec<_>>()) fields(messages = ?messages.iter().map(|(_, meta)| meta).collect::<Vec<_>>())
)] )]
async fn store_messages(&self, messages: &[(RawCommittedMessage, LogMeta)]) -> Result<u32> { async fn store_messages(&self, messages: &[(AbacusMessage, LogMeta)]) -> Result<u32> {
use crate::db::message; use crate::db::message;
use sea_orm::{prelude::*, sea_query::OnConflict, ActiveValue::*, Insert}; use sea_orm::{prelude::*, sea_query::OnConflict, ActiveValue::*, Insert};
debug_assert!(!messages.is_empty()); debug_assert!(!messages.is_empty());
let messages = messages
.iter()
.map(|(raw, meta)| CommittedMessage::try_from(raw).map(|parsed| (parsed, meta)))
.collect::<Result<Vec<(CommittedMessage, &LogMeta)>, _>>()
.context("Failed to parse a message")?;
// TODO: Look up txn info // TODO: Look up txn info
// TODO: Look up block info // TODO: Look up block info
let txns: HashMap<H256, (i64, TimeDateTime)> = self let txns: HashMap<H256, (i64, TimeDateTime)> = self
.ensure_blocks_and_txns(messages.iter().map(|(_, meta)| *meta)) .ensure_blocks_and_txns(messages.iter().map(|(_, meta)| meta))
.await? .await?
.collect(); .collect();
let max_leaf_id = messages let max_leaf_id = messages
.iter() .iter()
.map(|m| m.0.leaf_index) .map(|m| m.0.nonce)
.max() .max()
.ok_or_else(|| eyre!("Received empty list")); .ok_or_else(|| eyre!("Received empty list"));
let models: Vec<_> = messages let models: Vec<_> = messages
.into_iter() .iter()
.map(|(msg, meta)| { .map(|(msg, meta)| {
debug_assert_eq!(self.outbox.local_domain(), msg.message.origin); debug_assert_eq!(self.outbox.local_domain(), msg.origin);
let (txn_id, txn_timestamp) = txns.get(&meta.transaction_hash).unwrap(); let (txn_id, txn_timestamp) = txns.get(&meta.transaction_hash).unwrap();
message::ActiveModel { message::ActiveModel {
id: NotSet, id: NotSet,
time_created: Set(crate::date_time::now()), time_created: Set(crate::date_time::now()),
origin: Unchanged(msg.message.origin as i32), origin: Unchanged(msg.origin as i32),
destination: Set(msg.message.destination as i32), destination: Set(msg.destination as i32),
leaf_index: Unchanged(msg.leaf_index as i32), leaf_index: Unchanged(msg.nonce as i32),
sender: Set(format_h256(&msg.message.sender)), sender: Set(format_h256(&msg.sender)),
recipient: Set(format_h256(&msg.message.recipient)), recipient: Set(format_h256(&msg.recipient)),
msg_body: Set(if msg.message.body.is_empty() { msg_body: Set(if msg.body.is_empty() {
None None
} else { } else {
Some(msg.message.body) Some(msg.body.clone())
}), }),
outbox_address: Unchanged(format_h256(&self.outbox.address())), outbox_address: Unchanged(format_h256(&self.outbox.address())),
timestamp: Set(*txn_timestamp), timestamp: Set(*txn_timestamp),

@ -1,13 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use abacus_base::chains::IndexSettings;
use abacus_base::decl_settings; use abacus_base::decl_settings;
use abacus_base::{ChainSetup, IndexSettings, OutboxAddresses}; use abacus_base::ChainSetup;
// TODO: Make it so the inherited settings better communicate that the `outbox` // TODO: Make it so the inherited settings better communicate that the `outbox`
// config is not needed for the scraper. // config is not needed for the scraper.
decl_settings!(Scraper { decl_settings!(Scraper {
/// Configurations for contracts on the outbox chains /// Configurations for contracts on the outbox chains
outboxes: HashMap<String, ChainSetup<OutboxAddresses>>, outboxes: HashMap<String, ChainSetup>,
/// Index settings by chain /// Index settings by chain
indexes: HashMap<String, IndexSettings>, indexes: HashMap<String, IndexSettings>,
}); });

@ -3,6 +3,8 @@
use abacus_base::decl_settings; use abacus_base::decl_settings;
decl_settings!(Validator { decl_settings!(Validator {
// The name of the origin chain
originchainname: String,
/// The validator attestation signer /// The validator attestation signer
validator: abacus_base::SignerConf, validator: abacus_base::SignerConf,
/// The checkpoint syncer configuration /// The checkpoint syncer configuration

@ -3,18 +3,17 @@ use std::time::{Duration, Instant};
use eyre::Result; use eyre::Result;
use prometheus::IntGauge; use prometheus::IntGauge;
use tokio::time::MissedTickBehavior;
use tokio::{task::JoinHandle, time::sleep}; use tokio::{task::JoinHandle, time::sleep};
use tracing::{debug, info, info_span, instrument::Instrumented, warn, Instrument}; use tracing::{debug, info, info_span, instrument::Instrumented, Instrument};
use abacus_base::{CachingOutbox, CheckpointSyncer, CheckpointSyncers, CoreMetrics}; use abacus_base::{CachingMailbox, CheckpointSyncer, CheckpointSyncers, CoreMetrics};
use abacus_core::{Outbox, Signers}; use abacus_core::{Mailbox, Signers};
pub(crate) struct ValidatorSubmitter { pub(crate) struct ValidatorSubmitter {
interval: u64, interval: u64,
reorg_period: u64, reorg_period: u64,
signer: Arc<Signers>, signer: Arc<Signers>,
outbox: CachingOutbox, mailbox: CachingMailbox,
checkpoint_syncer: Arc<CheckpointSyncers>, checkpoint_syncer: Arc<CheckpointSyncers>,
metrics: ValidatorSubmitterMetrics, metrics: ValidatorSubmitterMetrics,
} }
@ -23,7 +22,7 @@ impl ValidatorSubmitter {
pub(crate) fn new( pub(crate) fn new(
interval: u64, interval: u64,
reorg_period: u64, reorg_period: u64,
outbox: CachingOutbox, mailbox: CachingMailbox,
signer: Arc<Signers>, signer: Arc<Signers>,
checkpoint_syncer: Arc<CheckpointSyncers>, checkpoint_syncer: Arc<CheckpointSyncers>,
metrics: ValidatorSubmitterMetrics, metrics: ValidatorSubmitterMetrics,
@ -31,7 +30,7 @@ impl ValidatorSubmitter {
Self { Self {
reorg_period, reorg_period,
interval, interval,
outbox, mailbox,
signer, signer,
checkpoint_syncer, checkpoint_syncer,
metrics, metrics,
@ -40,31 +39,7 @@ impl ValidatorSubmitter {
pub(crate) fn spawn(self) -> Instrumented<JoinHandle<Result<()>>> { pub(crate) fn spawn(self) -> Instrumented<JoinHandle<Result<()>>> {
let span = info_span!("ValidatorSubmitter"); let span = info_span!("ValidatorSubmitter");
let metrics_loop = tokio::spawn(Self::metrics_loop( tokio::spawn(async move { self.main_task().await }).instrument(span)
self.metrics.outbox_state.clone(),
self.outbox.clone(),
));
tokio::spawn(async move {
let res = self.main_task().await;
metrics_loop.abort();
res
})
.instrument(span)
}
/// Spawn a task to update the outbox state gauge.
async fn metrics_loop(outbox_state_gauge: IntGauge, outbox: CachingOutbox) {
let mut interval = tokio::time::interval(Duration::from_secs(60 * 10));
interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
loop {
let state = outbox.state().await;
match &state {
Ok(state) => outbox_state_gauge.set(*state as u8 as i64),
Err(e) => warn!(error = %e, "Failed to get outbox state"),
};
interval.tick().await;
}
} }
async fn main_task(self) -> Result<()> { async fn main_task(self) -> Result<()> {
@ -73,15 +48,15 @@ impl ValidatorSubmitter {
} else { } else {
Some(self.reorg_period) Some(self.reorg_period)
}; };
// Ensure that the outbox has > 0 messages before we enter the main // Ensure that the mailbox has > 0 messages before we enter the main
// validator submit loop. This is to avoid an underflow / reverted // validator submit loop. This is to avoid an underflow / reverted
// call when we invoke the `outbox.latest_checkpoint()` method, // call when we invoke the `mailbox.latest_checkpoint()` method,
// which returns the **index** of the last element in the tree // which returns the **index** of the last element in the tree
// rather than just the size. See // rather than just the size. See
// https://github.com/abacus-network/abacus-monorepo/issues/575 for // https://github.com/abacus-network/abacus-monorepo/issues/575 for
// more details. // more details.
while self.outbox.count().await? == 0 { while self.mailbox.count().await? == 0 {
info!("Waiting for non-zero outbox size"); info!("Waiting for non-zero mailbox size");
sleep(Duration::from_secs(self.interval)).await; sleep(Duration::from_secs(self.interval)).await;
} }
@ -115,7 +90,7 @@ impl ValidatorSubmitter {
info!(current_index = current_index, "Starting Validator"); info!(current_index = current_index, "Starting Validator");
loop { loop {
// Check the latest checkpoint // Check the latest checkpoint
let latest_checkpoint = self.outbox.latest_checkpoint(reorg_period).await?; let latest_checkpoint = self.mailbox.latest_checkpoint(reorg_period).await?;
self.metrics self.metrics
.latest_checkpoint_observed .latest_checkpoint_observed
@ -157,21 +132,19 @@ impl ValidatorSubmitter {
} }
pub(crate) struct ValidatorSubmitterMetrics { pub(crate) struct ValidatorSubmitterMetrics {
outbox_state: IntGauge,
latest_checkpoint_observed: IntGauge, latest_checkpoint_observed: IntGauge,
latest_checkpoint_processed: IntGauge, latest_checkpoint_processed: IntGauge,
} }
impl ValidatorSubmitterMetrics { impl ValidatorSubmitterMetrics {
pub fn new(metrics: &CoreMetrics, outbox_chain: &str) -> Self { pub fn new(metrics: &CoreMetrics, mailbox_chain: &str) -> Self {
Self { Self {
outbox_state: metrics.outbox_state().with_label_values(&[outbox_chain]),
latest_checkpoint_observed: metrics latest_checkpoint_observed: metrics
.latest_checkpoint() .latest_checkpoint()
.with_label_values(&["validator_observed", outbox_chain]), .with_label_values(&["validator_observed", mailbox_chain]),
latest_checkpoint_processed: metrics latest_checkpoint_processed: metrics
.latest_checkpoint() .latest_checkpoint()
.with_label_values(&["validator_processed", outbox_chain]), .with_label_values(&["validator_processed", mailbox_chain]),
} }
} }
} }

@ -6,7 +6,7 @@ use tokio::task::JoinHandle;
use tracing::instrument::Instrumented; use tracing::instrument::Instrumented;
use abacus_base::{run_all, AbacusAgentCore, Agent, BaseAgent, CheckpointSyncers, CoreMetrics}; use abacus_base::{run_all, AbacusAgentCore, Agent, BaseAgent, CheckpointSyncers, CoreMetrics};
use abacus_core::{AbacusContract, Signers}; use abacus_core::Signers;
use crate::submit::ValidatorSubmitterMetrics; use crate::submit::ValidatorSubmitterMetrics;
use crate::{settings::ValidatorSettings, submit::ValidatorSubmitter}; use crate::{settings::ValidatorSettings, submit::ValidatorSubmitter};
@ -14,6 +14,7 @@ use crate::{settings::ValidatorSettings, submit::ValidatorSubmitter};
/// A validator agent /// A validator agent
#[derive(Debug)] #[derive(Debug)]
pub struct Validator { pub struct Validator {
origin_chain_name: String,
signer: Arc<Signers>, signer: Arc<Signers>,
reorg_period: u64, reorg_period: u64,
interval: u64, interval: u64,
@ -24,6 +25,7 @@ pub struct Validator {
impl Validator { impl Validator {
/// Instantiate a new validator /// Instantiate a new validator
pub fn new( pub fn new(
origin_chain_name: String,
signer: Signers, signer: Signers,
reorg_period: u64, reorg_period: u64,
interval: u64, interval: u64,
@ -31,6 +33,7 @@ impl Validator {
core: AbacusAgentCore, core: AbacusAgentCore,
) -> Self { ) -> Self {
Self { Self {
origin_chain_name,
signer: Arc::new(signer), signer: Arc::new(signer),
reorg_period, reorg_period,
interval, interval,
@ -58,14 +61,16 @@ impl BaseAgent for Validator {
{ {
let signer = settings.validator.try_into_signer().await?; let signer = settings.validator.try_into_signer().await?;
let reorg_period = settings.reorgperiod.parse().expect("invalid uint"); let reorg_period = settings.reorgperiod.parse().expect("invalid uint");
let origin_chain_name = &settings.originchainname;
let interval = settings.interval.parse().expect("invalid uint"); let interval = settings.interval.parse().expect("invalid uint");
let core = settings let core = settings
.as_ref() .as_ref()
.try_into_abacus_core(metrics, false) .try_into_abacus_core(metrics, Some(vec![origin_chain_name]))
.await?; .await?;
let checkpoint_syncer = settings.checkpointsyncer.try_into_checkpoint_syncer(None)?; let checkpoint_syncer = settings.checkpointsyncer.try_into_checkpoint_syncer(None)?;
Ok(Self::new( Ok(Self::new(
origin_chain_name.clone(),
signer, signer,
reorg_period, reorg_period,
interval, interval,
@ -79,10 +84,10 @@ impl BaseAgent for Validator {
let submit = ValidatorSubmitter::new( let submit = ValidatorSubmitter::new(
self.interval, self.interval,
self.reorg_period, self.reorg_period,
self.outbox().clone(), self.mailbox(&self.origin_chain_name).unwrap().clone(),
self.signer.clone(), self.signer.clone(),
self.checkpoint_syncer.clone(), self.checkpoint_syncer.clone(),
ValidatorSubmitterMetrics::new(&self.core.metrics, self.outbox().chain_name()), ValidatorSubmitterMetrics::new(&self.core.metrics, &self.origin_chain_name),
); );
run_all(vec![submit.spawn()]) run_all(vec![submit.spawn()])

@ -1,239 +0,0 @@
[
{
"inputs": [
{
"internalType": "uint32",
"name": "_localDomain",
"type": "uint32"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "version",
"type": "uint8"
}
],
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "messageHash",
"type": "bytes32"
}
],
"name": "Process",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "validatorManager",
"type": "address"
}
],
"name": "ValidatorManagerSet",
"type": "event"
},
{
"inputs": [],
"name": "VERSION",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_remoteDomain",
"type": "uint32"
},
{
"internalType": "address",
"name": "_validatorManager",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "localDomain",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "messages",
"outputs": [
{
"internalType": "enum Inbox.MessageStatus",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_root",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_index",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
},
{
"internalType": "bytes32[32]",
"name": "_proof",
"type": "bytes32[32]"
},
{
"internalType": "uint256",
"name": "_leafIndex",
"type": "uint256"
}
],
"name": "process",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "remoteDomain",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_validatorManager",
"type": "address"
}
],
"name": "setValidatorManager",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "validatorManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]

@ -1,321 +0,0 @@
[
{
"inputs": [
{
"internalType": "uint32",
"name": "_remoteDomain",
"type": "uint32"
},
{
"internalType": "address[]",
"name": "_validators",
"type": "address[]"
},
{
"internalType": "uint256",
"name": "_threshold",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "threshold",
"type": "uint256"
}
],
"name": "ThresholdSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "validator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "validatorCount",
"type": "uint256"
}
],
"name": "ValidatorEnrolled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "validator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "validatorCount",
"type": "uint256"
}
],
"name": "ValidatorUnenrolled",
"type": "event"
},
{
"inputs": [],
"name": "domain",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "domainHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_validator",
"type": "address"
}
],
"name": "enrollValidator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_root",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_index",
"type": "uint256"
},
{
"internalType": "bytes[]",
"name": "_signatures",
"type": "bytes[]"
}
],
"name": "isQuorum",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_validator",
"type": "address"
}
],
"name": "isValidator",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IInbox",
"name": "_inbox",
"type": "address"
},
{
"internalType": "bytes32",
"name": "_root",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_index",
"type": "uint256"
},
{
"internalType": "bytes[]",
"name": "_signatures",
"type": "bytes[]"
},
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
},
{
"internalType": "bytes32[32]",
"name": "_proof",
"type": "bytes32[32]"
},
{
"internalType": "uint256",
"name": "_leafIndex",
"type": "uint256"
}
],
"name": "process",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_threshold",
"type": "uint256"
}
],
"name": "setThreshold",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "threshold",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_validator",
"type": "address"
}
],
"name": "unenrollValidator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "validatorCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "validators",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
}
]

@ -9,15 +9,9 @@
"inputs": [ "inputs": [
{ {
"indexed": true, "indexed": true,
"internalType": "address", "internalType": "bytes32",
"name": "outbox", "name": "messageId",
"type": "address" "type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "leafIndex",
"type": "uint256"
}, },
{ {
"indexed": false, "indexed": false,
@ -91,14 +85,9 @@
{ {
"inputs": [ "inputs": [
{ {
"internalType": "address", "internalType": "bytes32",
"name": "_outbox", "name": "_messageId",
"type": "address" "type": "bytes32"
},
{
"internalType": "uint256",
"name": "_leafIndex",
"type": "uint256"
}, },
{ {
"internalType": "uint32", "internalType": "uint32",

@ -15,18 +15,12 @@
"inputs": [ "inputs": [
{ {
"indexed": true, "indexed": true,
"internalType": "bytes32", "internalType": "address",
"name": "root", "name": "module",
"type": "bytes32" "type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "index",
"type": "uint256"
} }
], ],
"name": "CheckpointCached", "name": "DefaultIsmSet",
"type": "event" "type": "event"
}, },
{ {
@ -34,9 +28,9 @@
"inputs": [ "inputs": [
{ {
"indexed": true, "indexed": true,
"internalType": "uint256", "internalType": "bytes32",
"name": "leafIndex", "name": "messageId",
"type": "uint256" "type": "bytes32"
}, },
{ {
"indexed": false, "indexed": false,
@ -48,12 +42,6 @@
"name": "Dispatch", "name": "Dispatch",
"type": "event" "type": "event"
}, },
{
"anonymous": false,
"inputs": [],
"name": "Fail",
"type": "event"
},
{ {
"anonymous": false, "anonymous": false,
"inputs": [ "inputs": [
@ -90,13 +78,13 @@
"anonymous": false, "anonymous": false,
"inputs": [ "inputs": [
{ {
"indexed": false, "indexed": true,
"internalType": "address", "internalType": "bytes32",
"name": "validatorManager", "name": "messageId",
"type": "address" "type": "bytes32"
} }
], ],
"name": "ValidatorManagerSet", "name": "Process",
"type": "event" "type": "event"
}, },
{ {
@ -117,9 +105,9 @@
"name": "VERSION", "name": "VERSION",
"outputs": [ "outputs": [
{ {
"internalType": "uint8", "internalType": "uint32",
"name": "", "name": "",
"type": "uint8" "type": "uint32"
} }
], ],
"stateMutability": "view", "stateMutability": "view",
@ -127,38 +115,44 @@
}, },
{ {
"inputs": [], "inputs": [],
"name": "cacheCheckpoint", "name": "count",
"outputs": [], "outputs": [
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ {
"internalType": "bytes32", "internalType": "uint32",
"name": "", "name": "",
"type": "bytes32" "type": "uint32"
} }
], ],
"name": "cachedCheckpoints", "stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "defaultIsm",
"outputs": [ "outputs": [
{ {
"internalType": "uint256", "internalType": "contract IInterchainSecurityModule",
"name": "", "name": "",
"type": "uint256" "type": "address"
} }
], ],
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{ {
"inputs": [], "inputs": [
"name": "count", {
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "delivered",
"outputs": [ "outputs": [
{ {
"internalType": "uint256", "internalType": "bool",
"name": "", "name": "",
"type": "uint256" "type": "bool"
} }
], ],
"stateMutability": "view", "stateMutability": "view",
@ -185,26 +179,19 @@
"name": "dispatch", "name": "dispatch",
"outputs": [ "outputs": [
{ {
"internalType": "uint256", "internalType": "bytes32",
"name": "", "name": "",
"type": "uint256" "type": "bytes32"
} }
], ],
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "fail",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
"internalType": "address", "internalType": "address",
"name": "_validatorManager", "name": "_defaultIsm",
"type": "address" "type": "address"
} }
], ],
@ -213,37 +200,6 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "latestCachedCheckpoint",
"outputs": [
{
"internalType": "bytes32",
"name": "root",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "latestCachedRoot",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "latestCheckpoint", "name": "latestCheckpoint",
@ -254,9 +210,9 @@
"type": "bytes32" "type": "bytes32"
}, },
{ {
"internalType": "uint256", "internalType": "uint32",
"name": "", "name": "",
"type": "uint256" "type": "uint32"
} }
], ],
"stateMutability": "view", "stateMutability": "view",
@ -288,6 +244,24 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "bytes",
"name": "_metadata",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "process",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "renounceOwnership", "name": "renounceOwnership",
@ -312,28 +286,15 @@
"inputs": [ "inputs": [
{ {
"internalType": "address", "internalType": "address",
"name": "_validatorManager", "name": "_module",
"type": "address" "type": "address"
} }
], ],
"name": "setValidatorManager", "name": "setDefaultIsm",
"outputs": [], "outputs": [],
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "state",
"outputs": [
{
"internalType": "enum Outbox.States",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
@ -359,18 +320,5 @@
], ],
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
},
{
"inputs": [],
"name": "validatorManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
} }
] ]

@ -0,0 +1,324 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint32",
"name": "domain",
"type": "uint32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "threshold",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "commitment",
"type": "bytes32"
}
],
"name": "ThresholdSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint32",
"name": "domain",
"type": "uint32"
},
{
"indexed": true,
"internalType": "address",
"name": "validator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "validatorCount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "commitment",
"type": "bytes32"
}
],
"name": "ValidatorEnrolled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint32",
"name": "domain",
"type": "uint32"
},
{
"indexed": true,
"internalType": "address",
"name": "validator",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "validatorCount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "commitment",
"type": "bytes32"
}
],
"name": "ValidatorUnenrolled",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"name": "commitment",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_domain",
"type": "uint32"
},
{
"internalType": "address",
"name": "_validator",
"type": "address"
}
],
"name": "enrollValidator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_domain",
"type": "uint32"
},
{
"internalType": "address",
"name": "_address",
"type": "address"
}
],
"name": "isEnrolled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_domain",
"type": "uint32"
},
{
"internalType": "uint256",
"name": "_threshold",
"type": "uint256"
}
],
"name": "setThreshold",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"name": "threshold",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_domain",
"type": "uint32"
},
{
"internalType": "address",
"name": "_validator",
"type": "address"
}
],
"name": "unenrollValidator",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_domain",
"type": "uint32"
}
],
"name": "validatorCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_domain",
"type": "uint32"
}
],
"name": "validators",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_metadata",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "verify",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]

@ -1,142 +0,0 @@
#![allow(clippy::enum_variant_names)]
#![allow(missing_docs)]
use std::collections::HashMap;
use std::fmt::Display;
use std::{error::Error as StdError, sync::Arc};
use async_trait::async_trait;
use ethers::prelude::*;
use eyre::Result;
use abacus_core::{
AbacusAbi, AbacusCommon, AbacusContract, Address, ChainCommunicationError, ContractLocator,
Inbox, MessageStatus, TxOutcome,
};
use crate::contracts::inbox::{Inbox as EthereumInboxInternal, INBOX_ABI};
use crate::trait_builder::MakeableWithProvider;
impl<M> Display for EthereumInboxInternal<M>
where
M: Middleware,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub struct InboxBuilder {}
impl MakeableWithProvider for InboxBuilder {
type Output = Box<dyn Inbox>;
fn make_with_provider<M: Middleware + 'static>(
&self,
provider: M,
locator: &ContractLocator,
) -> Self::Output {
Box::new(EthereumInbox::new(Arc::new(provider), locator))
}
}
/// A struct that provides access to an Ethereum inbox contract
#[derive(Debug)]
pub struct EthereumInbox<M>
where
M: Middleware,
{
contract: Arc<EthereumInboxInternal<M>>,
domain: u32,
chain_name: String,
}
impl<M> EthereumInbox<M>
where
M: Middleware,
{
/// Create a reference to a inbox at a specific Ethereum address on some
/// chain
pub fn new(
provider: Arc<M>,
ContractLocator {
chain_name,
domain,
address,
}: &ContractLocator,
) -> Self {
Self {
contract: Arc::new(EthereumInboxInternal::new(address, provider)),
domain: *domain,
chain_name: chain_name.to_owned(),
}
}
}
impl<M> AbacusContract for EthereumInbox<M>
where
M: Middleware + 'static,
{
fn chain_name(&self) -> &str {
&self.chain_name
}
fn address(&self) -> H256 {
self.contract.address().into()
}
}
#[async_trait]
impl<M> AbacusCommon for EthereumInbox<M>
where
M: Middleware + 'static,
{
fn local_domain(&self) -> u32 {
self.domain
}
#[tracing::instrument(err)]
async fn status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError> {
let receipt_opt = self
.contract
.client()
.get_transaction_receipt(txid)
.await
.map_err(|e| Box::new(e) as Box<dyn StdError + Send + Sync>)?;
Ok(receipt_opt.map(Into::into))
}
#[tracing::instrument(err)]
async fn validator_manager(&self) -> Result<H256, ChainCommunicationError> {
Ok(self.contract.validator_manager().call().await?.into())
}
}
#[async_trait]
impl<M> Inbox for EthereumInbox<M>
where
M: Middleware + 'static,
{
async fn remote_domain(&self) -> Result<u32, ChainCommunicationError> {
Ok(self.contract.remote_domain().call().await?)
}
#[tracing::instrument(err)]
async fn message_status(&self, leaf: H256) -> Result<MessageStatus, ChainCommunicationError> {
let status = self.contract.messages(leaf.into()).call().await?;
Ok(MessageStatus::try_from(status).expect("Bad status from solidity"))
}
fn contract_address(&self) -> Address {
self.contract.address().into()
}
}
pub struct EthereumInboxAbi;
impl AbacusAbi for EthereumInboxAbi {
fn fn_map() -> HashMap<Selector, &'static str> {
super::extract_fn_map(&INBOX_ABI)
}
}

@ -30,9 +30,7 @@ where
} }
pub struct InterchainGasPaymasterIndexerBuilder { pub struct InterchainGasPaymasterIndexerBuilder {
pub outbox_address: H160, pub mailbox_address: H160,
pub from_height: u32,
pub chunk_size: u32,
pub finality_blocks: u32, pub finality_blocks: u32,
} }
@ -47,9 +45,6 @@ impl MakeableWithProvider for InterchainGasPaymasterIndexerBuilder {
Box::new(EthereumInterchainGasPaymasterIndexer::new( Box::new(EthereumInterchainGasPaymasterIndexer::new(
Arc::new(provider), Arc::new(provider),
locator, locator,
self.outbox_address,
self.from_height,
self.chunk_size,
self.finality_blocks, self.finality_blocks,
)) ))
} }
@ -63,11 +58,6 @@ where
{ {
contract: Arc<EthereumInterchainGasPaymasterInternal<M>>, contract: Arc<EthereumInterchainGasPaymasterInternal<M>>,
provider: Arc<M>, provider: Arc<M>,
outbox_address: H160,
#[allow(unused)]
from_height: u32,
#[allow(unused)]
chunk_size: u32,
finality_blocks: u32, finality_blocks: u32,
} }
@ -76,23 +66,13 @@ where
M: Middleware + 'static, M: Middleware + 'static,
{ {
/// Create new EthereumInterchainGasPaymasterIndexer /// Create new EthereumInterchainGasPaymasterIndexer
pub fn new( pub fn new(provider: Arc<M>, locator: &ContractLocator, finality_blocks: u32) -> Self {
provider: Arc<M>,
locator: &ContractLocator,
outbox_address: H160,
from_height: u32,
chunk_size: u32,
finality_blocks: u32,
) -> Self {
Self { Self {
contract: Arc::new(EthereumInterchainGasPaymasterInternal::new( contract: Arc::new(EthereumInterchainGasPaymasterInternal::new(
&locator.address, &locator.address,
provider.clone(), provider.clone(),
)), )),
provider, provider,
outbox_address,
from_height,
chunk_size,
finality_blocks, finality_blocks,
} }
} }
@ -128,7 +108,6 @@ where
let events = self let events = self
.contract .contract
.gas_payment_filter() .gas_payment_filter()
.topic1(self.outbox_address)
.from_block(from_block) .from_block(from_block)
.to_block(to_block) .to_block(to_block)
.query_with_meta() .query_with_meta()
@ -138,7 +117,7 @@ where
.into_iter() .into_iter()
.map(|(log, log_meta)| InterchainGasPaymentWithMeta { .map(|(log, log_meta)| InterchainGasPaymentWithMeta {
payment: InterchainGasPayment { payment: InterchainGasPayment {
leaf_index: log.leaf_index.as_u32(), message_id: H256::from(log.message_id),
amount: log.amount, amount: log.amount,
}, },
meta: InterchainGasPaymentMeta { meta: InterchainGasPaymentMeta {

@ -14,30 +14,26 @@ pub use retrying::{RetryingProvider, RetryingProviderError};
use crate::abi::FunctionExt; use crate::abi::FunctionExt;
#[cfg(not(doctest))] #[cfg(not(doctest))]
pub use crate::{inbox::*, interchain_gas::*, outbox::*, trait_builder::*, validator_manager::*}; pub use crate::{interchain_gas::*, mailbox::*, multisig_ism::*, trait_builder::*};
#[cfg(not(doctest))] #[cfg(not(doctest))]
mod tx; mod tx;
/// Outbox abi /// Outbox abi
#[cfg(not(doctest))] #[cfg(not(doctest))]
mod outbox; mod mailbox;
#[cfg(not(doctest))] #[cfg(not(doctest))]
mod trait_builder; mod trait_builder;
/// Inbox abi
#[cfg(not(doctest))]
mod inbox;
/// InboxValidatorManager abi
#[cfg(not(doctest))]
mod validator_manager;
/// InterchainGasPaymaster abi /// InterchainGasPaymaster abi
#[cfg(not(doctest))] #[cfg(not(doctest))]
mod interchain_gas; mod interchain_gas;
/// MultisigIsm abi
#[cfg(not(doctest))]
mod multisig_ism;
/// Generated contract bindings. /// Generated contract bindings.
#[cfg(not(doctest))] #[cfg(not(doctest))]
mod contracts; mod contracts;

@ -0,0 +1,302 @@
#![allow(clippy::enum_variant_names)]
#![allow(missing_docs)]
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use ethers::abi::AbiEncode;
use ethers::prelude::*;
use ethers_contract::builders::ContractCall;
use eyre::{eyre, Result};
use tracing::instrument;
use abacus_core::{
AbacusAbi, AbacusContract, AbacusMessage, ChainCommunicationError, Checkpoint, ContractLocator,
Indexer, LogMeta, Mailbox, MailboxIndexer, RawAbacusMessage, TxCostEstimate, TxOutcome,
};
use crate::contracts::mailbox::{Mailbox as EthereumMailboxInternal, ProcessCall, MAILBOX_ABI};
use crate::trait_builder::MakeableWithProvider;
use crate::tx::report_tx;
impl<M> std::fmt::Display for EthereumMailboxInternal<M>
where
M: Middleware,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub struct MailboxIndexerBuilder {
pub finality_blocks: u32,
}
impl MakeableWithProvider for MailboxIndexerBuilder {
type Output = Box<dyn MailboxIndexer>;
fn make_with_provider<M: Middleware + 'static>(
&self,
provider: M,
locator: &ContractLocator,
) -> Self::Output {
Box::new(EthereumMailboxIndexer::new(
Arc::new(provider),
locator,
self.finality_blocks,
))
}
}
#[derive(Debug)]
/// Struct that retrieves event data for an Ethereum mailbox
pub struct EthereumMailboxIndexer<M>
where
M: Middleware,
{
contract: Arc<EthereumMailboxInternal<M>>,
provider: Arc<M>,
finality_blocks: u32,
}
impl<M> EthereumMailboxIndexer<M>
where
M: Middleware + 'static,
{
/// Create new EthereumMailboxIndexer
pub fn new(provider: Arc<M>, locator: &ContractLocator, finality_blocks: u32) -> Self {
let contract = Arc::new(EthereumMailboxInternal::new(
&locator.address,
provider.clone(),
));
Self {
contract,
provider,
finality_blocks,
}
}
}
#[async_trait]
impl<M> Indexer for EthereumMailboxIndexer<M>
where
M: Middleware + 'static,
{
#[instrument(err, skip(self))]
async fn get_finalized_block_number(&self) -> Result<u32> {
Ok(self
.provider
.get_block_number()
.await?
.as_u32()
.saturating_sub(self.finality_blocks))
}
}
#[async_trait]
impl<M> MailboxIndexer for EthereumMailboxIndexer<M>
where
M: Middleware + 'static,
{
#[instrument(err, skip(self))]
async fn fetch_sorted_messages(
&self,
from: u32,
to: u32,
) -> Result<Vec<(AbacusMessage, LogMeta)>> {
let mut events: Vec<(AbacusMessage, LogMeta)> = self
.contract
.dispatch_filter()
.from_block(from)
.to_block(to)
.query_with_meta()
.await?
.into_iter()
.map(|(event, meta)| (AbacusMessage::from(event.message.to_vec()), meta.into()))
.collect();
events.sort_by(|a, b| a.0.nonce.cmp(&b.0.nonce));
Ok(events)
}
}
pub struct MailboxBuilder {}
impl MakeableWithProvider for MailboxBuilder {
type Output = Box<dyn Mailbox>;
fn make_with_provider<M: Middleware + 'static>(
&self,
provider: M,
locator: &ContractLocator,
) -> Self::Output {
Box::new(EthereumMailbox::new(Arc::new(provider), locator))
}
}
/// A reference to an Mailbox contract on some Ethereum chain
#[derive(Debug)]
pub struct EthereumMailbox<M>
where
M: Middleware,
{
contract: Arc<EthereumMailboxInternal<M>>,
domain: u32,
chain_name: String,
provider: Arc<M>,
}
impl<M> EthereumMailbox<M>
where
M: Middleware + 'static,
{
/// Create a reference to a mailbox at a specific Ethereum address on some
/// chain
pub fn new(provider: Arc<M>, locator: &ContractLocator) -> Self {
Self {
contract: Arc::new(EthereumMailboxInternal::new(
&locator.address,
provider.clone(),
)),
domain: locator.domain,
chain_name: locator.chain_name.to_owned(),
provider,
}
}
/// Returns a ContractCall that processes the provided message.
/// If the provided tx_gas_limit is None, gas estimation occurs.
async fn process_contract_call(
&self,
message: &AbacusMessage,
metadata: &[u8],
tx_gas_limit: Option<U256>,
) -> Result<ContractCall<M, ()>, ChainCommunicationError> {
let tx = self.contract.process(
metadata.to_vec().into(),
RawAbacusMessage::from(message).to_vec().into(),
);
let gas_limit = if let Some(gas_limit) = tx_gas_limit {
gas_limit
} else {
tx.estimate_gas().await?.saturating_add(U256::from(100000))
};
Ok(tx.gas(gas_limit))
}
}
impl<M> AbacusContract for EthereumMailbox<M>
where
M: Middleware + 'static,
{
fn chain_name(&self) -> &str {
&self.chain_name
}
fn address(&self) -> H256 {
self.contract.address().into()
}
}
#[async_trait]
impl<M> Mailbox for EthereumMailbox<M>
where
M: Middleware + 'static,
{
#[tracing::instrument(err, skip(self))]
async fn count(&self) -> Result<u32, ChainCommunicationError> {
Ok(self.contract.count().call().await?)
}
#[tracing::instrument(err, skip(self))]
async fn latest_checkpoint(
&self,
maybe_lag: Option<u64>,
) -> Result<Checkpoint, ChainCommunicationError> {
let base_call = self.contract.latest_checkpoint();
let call_with_lag = match maybe_lag {
Some(lag) => {
let tip = self
.provider
.get_block_number()
.await
.map_err(|x| ChainCommunicationError::CustomError(Box::new(x)))?
.as_u64();
base_call.block(if lag > tip { 0 } else { tip - lag })
}
None => base_call,
};
let (root, index) = call_with_lag.call().await?;
Ok(Checkpoint {
mailbox_address: self.address(),
mailbox_domain: self.domain,
root: root.into(),
index,
})
}
fn local_domain(&self) -> u32 {
self.domain
}
#[tracing::instrument(err, skip(self))]
async fn default_ism(&self) -> Result<H256, ChainCommunicationError> {
Ok(self.contract.default_ism().call().await?.into())
}
#[tracing::instrument(err)]
async fn delivered(&self, id: H256) -> Result<bool, ChainCommunicationError> {
Ok(self.contract.delivered(id.into()).call().await?)
}
#[tracing::instrument(skip(self))]
async fn process(
&self,
message: &AbacusMessage,
metadata: &[u8],
tx_gas_limit: Option<U256>,
) -> Result<TxOutcome, ChainCommunicationError> {
let contract_call = self
.process_contract_call(message, metadata, tx_gas_limit)
.await?;
let receipt = report_tx(contract_call).await?;
Ok(receipt.into())
}
async fn process_estimate_costs(
&self,
message: &AbacusMessage,
metadata: &[u8],
) -> Result<TxCostEstimate> {
let contract_call = self.process_contract_call(message, metadata, None).await?;
let gas_limit = contract_call
.tx
.gas()
.ok_or_else(|| eyre!("Expected gas limit for process contract call"))?;
let gas_price = self.provider.get_gas_price().await?;
Ok(TxCostEstimate {
gas_limit: *gas_limit,
gas_price,
})
}
fn process_calldata(&self, message: &AbacusMessage, metadata: &[u8]) -> Vec<u8> {
let process_call = ProcessCall {
message: RawAbacusMessage::from(message).to_vec().into(),
metadata: metadata.to_vec().into(),
};
process_call.encode()
}
}
pub struct EthereumMailboxAbi;
impl AbacusAbi for EthereumMailboxAbi {
fn fn_map() -> HashMap<Selector, &'static str> {
super::extract_fn_map(&MAILBOX_ABI)
}
}

@ -0,0 +1,235 @@
#![allow(clippy::enum_variant_names)]
#![allow(missing_docs)]
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use abacus_core::accumulator::merkle::Proof;
use async_trait::async_trait;
use ethers::abi::Token;
use ethers::providers::Middleware;
use ethers::types::{Selector, H160, H256, U256};
use eyre::Result;
use std::hash::Hash;
use tokio::sync::RwLock;
use abacus_core::{
AbacusAbi, AbacusContract, ChainCommunicationError, ContractLocator, MultisigIsm,
MultisigSignedCheckpoint,
};
use crate::contracts::multisig_ism::{MultisigIsm as EthereumMultisigIsmInternal, MULTISIGISM_ABI};
use crate::trait_builder::MakeableWithProvider;
#[derive(Debug)]
struct Timestamped<Value> {
t: Instant,
value: Value,
}
impl<Value> Timestamped<Value> {
fn new(value: Value) -> Timestamped<Value> {
Timestamped {
t: Instant::now(),
value,
}
}
}
#[derive(Debug)]
pub struct ExpiringCache<Key, Value>
where
Key: Eq + Hash,
{
expiry: Duration, // cache endurance
cache: HashMap<Key, Timestamped<Value>>, // hashmap containing references to cached items
}
impl<Key, Value> ExpiringCache<Key, Value>
where
Key: Copy + Eq + Hash + tracing::Value,
Value: Clone,
{
pub fn new(expiry: Duration) -> ExpiringCache<Key, Value> {
ExpiringCache {
expiry,
cache: HashMap::new(),
}
}
pub fn put(&mut self, key: Key, value: Value) {
self.cache.insert(key, Timestamped::new(value));
}
pub fn get(&self, key: Key) -> Option<&Value> {
if let Some(entry) = self.cache.get(&key) {
if entry.t.elapsed() > self.expiry {
None
} else {
Some(&entry.value)
}
} else {
None
}
}
}
impl<M> std::fmt::Display for EthereumMultisigIsmInternal<M>
where
M: Middleware,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub struct MultisigIsmBuilder {}
impl MakeableWithProvider for MultisigIsmBuilder {
type Output = Box<dyn MultisigIsm>;
fn make_with_provider<M: Middleware + 'static>(
&self,
provider: M,
locator: &ContractLocator,
) -> Self::Output {
Box::new(EthereumMultisigIsm::new(Arc::new(provider), locator))
}
}
/// A reference to an MultisigIsm contract on some Ethereum chain
#[derive(Debug)]
pub struct EthereumMultisigIsm<M>
where
M: Middleware,
{
contract: Arc<EthereumMultisigIsmInternal<M>>,
#[allow(dead_code)]
domain: u32,
chain_name: String,
#[allow(dead_code)]
provider: Arc<M>,
threshold_cache: RwLock<ExpiringCache<u32, U256>>,
validators_cache: RwLock<ExpiringCache<u32, Vec<H160>>>,
}
impl<M> EthereumMultisigIsm<M>
where
M: Middleware + 'static,
{
/// Create a reference to a mailbox at a specific Ethereum address on some
/// chain
pub fn new(provider: Arc<M>, locator: &ContractLocator) -> Self {
Self {
contract: Arc::new(EthereumMultisigIsmInternal::new(
&locator.address,
provider.clone(),
)),
domain: locator.domain,
chain_name: locator.chain_name.to_owned(),
provider,
threshold_cache: RwLock::new(ExpiringCache::new(Duration::from_secs(60))),
validators_cache: RwLock::new(ExpiringCache::new(Duration::from_secs(60))),
}
}
}
impl<M> AbacusContract for EthereumMultisigIsm<M>
where
M: Middleware + 'static,
{
fn chain_name(&self) -> &str {
&self.chain_name
}
fn address(&self) -> H256 {
self.contract.address().into()
}
}
#[async_trait]
impl<M> MultisigIsm for EthereumMultisigIsm<M>
where
M: Middleware + 'static,
{
/// Returns the metadata needed by the contract's verify function
async fn format_metadata(
&self,
checkpoint: &MultisigSignedCheckpoint,
proof: Proof,
) -> Result<Vec<u8>, ChainCommunicationError> {
let threshold = self.threshold(checkpoint.checkpoint.mailbox_domain).await?;
let validators: Vec<H256> = self
.validators(checkpoint.checkpoint.mailbox_domain)
.await?
.iter()
.map(|&x| H256::from(x))
.collect();
let validator_tokens: Vec<Token> = validators
.iter()
.map(|x| Token::FixedBytes(x.to_fixed_bytes().into()))
.collect();
let proof_tokens: Vec<Token> = proof
.path
.iter()
.map(|x| Token::FixedBytes(x.to_fixed_bytes().into()))
.collect();
let prefix = ethers::abi::encode(&[
Token::FixedBytes(checkpoint.checkpoint.root.to_fixed_bytes().into()),
Token::Uint(U256::from(checkpoint.checkpoint.index)),
Token::FixedBytes(
checkpoint
.checkpoint
.mailbox_address
.to_fixed_bytes()
.into(),
),
Token::FixedArray(proof_tokens),
Token::Uint(threshold),
]);
let suffix = ethers::abi::encode(&[Token::FixedArray(validator_tokens)]);
// The ethers encoder likes to zero-pad non word-aligned byte arrays.
// Thus, we pack the signatures, which are not word-aligned, ourselves.
let signature_vecs: Vec<Vec<u8>> =
checkpoint.signatures.iter().map(|x| x.to_vec()).collect();
let signature_bytes = signature_vecs.concat();
let metadata = [prefix, signature_bytes, suffix].concat();
Ok(metadata)
}
#[tracing::instrument(err, skip(self))]
async fn threshold(&self, domain: u32) -> Result<U256, ChainCommunicationError> {
let entry = self.threshold_cache.read().await.get(domain).cloned();
if let Some(threshold) = entry {
Ok(threshold)
} else {
let threshold = self.contract.threshold(domain).call().await?;
self.threshold_cache.write().await.put(domain, threshold);
Ok(threshold)
}
}
#[tracing::instrument(err, skip(self))]
async fn validators(&self, domain: u32) -> Result<Vec<H160>, ChainCommunicationError> {
let entry = self.validators_cache.read().await.get(domain).cloned();
if let Some(validators) = entry {
Ok(validators)
} else {
let validators = self.contract.validators(domain).call().await?;
self.validators_cache
.write()
.await
.put(domain, validators.clone());
Ok(validators)
}
}
}
pub struct EthereumMultisigIsmAbi;
impl AbacusAbi for EthereumMultisigIsmAbi {
fn fn_map() -> HashMap<Selector, &'static str> {
super::extract_fn_map(&MULTISIGISM_ABI)
}
}

@ -1,327 +0,0 @@
#![allow(clippy::enum_variant_names)]
#![allow(missing_docs)]
use std::collections::HashMap;
use std::{error::Error as StdError, sync::Arc};
use async_trait::async_trait;
use ethers::prelude::*;
use eyre::Result;
use tracing::instrument;
use abacus_core::{
AbacusAbi, AbacusCommon, AbacusContract, ChainCommunicationError, Checkpoint, ContractLocator,
Indexer, LogMeta, Message, Outbox, OutboxIndexer, OutboxState, RawCommittedMessage, TxOutcome,
};
use crate::contracts::outbox::{Outbox as EthereumOutboxInternal, OUTBOX_ABI};
use crate::trait_builder::MakeableWithProvider;
use crate::tx::report_tx;
impl<M> std::fmt::Display for EthereumOutboxInternal<M>
where
M: Middleware,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub struct OutboxIndexerBuilder {
pub finality_blocks: u32,
}
impl MakeableWithProvider for OutboxIndexerBuilder {
type Output = Box<dyn OutboxIndexer>;
fn make_with_provider<M: Middleware + 'static>(
&self,
provider: M,
locator: &ContractLocator,
) -> Self::Output {
Box::new(EthereumOutboxIndexer::new(
Arc::new(provider),
locator,
self.finality_blocks,
))
}
}
#[derive(Debug)]
/// Struct that retrieves event data for an Ethereum outbox
pub struct EthereumOutboxIndexer<M>
where
M: Middleware,
{
contract: Arc<EthereumOutboxInternal<M>>,
provider: Arc<M>,
finality_blocks: u32,
outbox_domain: u32,
}
impl<M> EthereumOutboxIndexer<M>
where
M: Middleware + 'static,
{
/// Create new EthereumOutboxIndexer
pub fn new(provider: Arc<M>, locator: &ContractLocator, finality_blocks: u32) -> Self {
let contract = Arc::new(EthereumOutboxInternal::new(
&locator.address,
provider.clone(),
));
Self {
contract,
provider,
finality_blocks,
outbox_domain: locator.domain,
}
}
}
#[async_trait]
impl<M> Indexer for EthereumOutboxIndexer<M>
where
M: Middleware + 'static,
{
#[instrument(err, skip(self))]
async fn get_finalized_block_number(&self) -> Result<u32> {
Ok(self
.provider
.get_block_number()
.await?
.as_u32()
.saturating_sub(self.finality_blocks))
}
}
#[async_trait]
impl<M> OutboxIndexer for EthereumOutboxIndexer<M>
where
M: Middleware + 'static,
{
#[instrument(err, skip(self))]
async fn fetch_sorted_messages(
&self,
from: u32,
to: u32,
) -> Result<Vec<(RawCommittedMessage, LogMeta)>> {
let mut events: Vec<(RawCommittedMessage, LogMeta)> = self
.contract
.dispatch_filter()
.from_block(from)
.to_block(to)
.query_with_meta()
.await?
.into_iter()
.map(|(event, meta)| {
(
RawCommittedMessage {
leaf_index: event.leaf_index.as_u32(),
message: event.message.to_vec(),
},
meta.into(),
)
})
.collect();
events.sort_by(|a, b| a.0.leaf_index.cmp(&b.0.leaf_index));
Ok(events)
}
#[instrument(err, skip(self))]
async fn fetch_sorted_cached_checkpoints(
&self,
from: u32,
to: u32,
) -> Result<Vec<(Checkpoint, LogMeta)>> {
let mut events: Vec<(Checkpoint, LogMeta)> = self
.contract
.checkpoint_cached_filter()
.from_block(from)
.to_block(to)
.query_with_meta()
.await?
.into_iter()
.map(|(event, meta)| {
(
Checkpoint {
outbox_domain: self.outbox_domain,
root: event.root.into(),
index: event.index.as_u32(),
},
meta.into(),
)
})
.collect();
events.sort_by(|a, b| a.1.cmp(&b.1));
Ok(events)
}
}
pub struct OutboxBuilder {}
impl MakeableWithProvider for OutboxBuilder {
type Output = Box<dyn Outbox>;
fn make_with_provider<M: Middleware + 'static>(
&self,
provider: M,
locator: &ContractLocator,
) -> Self::Output {
Box::new(EthereumOutbox::new(Arc::new(provider), locator))
}
}
/// A reference to an Outbox contract on some Ethereum chain
#[derive(Debug)]
pub struct EthereumOutbox<M>
where
M: Middleware,
{
contract: Arc<EthereumOutboxInternal<M>>,
domain: u32,
chain_name: String,
provider: Arc<M>,
}
impl<M> EthereumOutbox<M>
where
M: Middleware + 'static,
{
/// Create a reference to a outbox at a specific Ethereum address on some
/// chain
pub fn new(provider: Arc<M>, locator: &ContractLocator) -> Self {
Self {
contract: Arc::new(EthereumOutboxInternal::new(
&locator.address,
provider.clone(),
)),
domain: locator.domain,
chain_name: locator.chain_name.to_owned(),
provider,
}
}
}
impl<M> AbacusContract for EthereumOutbox<M>
where
M: Middleware + 'static,
{
fn chain_name(&self) -> &str {
&self.chain_name
}
fn address(&self) -> H256 {
self.contract.address().into()
}
}
#[async_trait]
impl<M> AbacusCommon for EthereumOutbox<M>
where
M: Middleware + 'static,
{
fn local_domain(&self) -> u32 {
self.domain
}
#[tracing::instrument(err, skip(self))]
async fn status(&self, txid: H256) -> Result<Option<TxOutcome>, ChainCommunicationError> {
let receipt_opt = self
.contract
.client()
.get_transaction_receipt(txid)
.await
.map_err(|e| Box::new(e) as Box<dyn StdError + Send + Sync>)?;
Ok(receipt_opt.map(Into::into))
}
#[tracing::instrument(err, skip(self))]
async fn validator_manager(&self) -> Result<H256, ChainCommunicationError> {
Ok(self.contract.validator_manager().call().await?.into())
}
}
#[async_trait]
impl<M> Outbox for EthereumOutbox<M>
where
M: Middleware + 'static,
{
#[tracing::instrument(err, skip(self))]
async fn dispatch(&self, message: &Message) -> Result<TxOutcome, ChainCommunicationError> {
let tx = self.contract.dispatch(
message.destination,
message.recipient.to_fixed_bytes(),
message.body.clone().into(),
);
Ok(report_tx(tx).await?.into())
}
#[tracing::instrument(err, skip(self))]
async fn state(&self) -> Result<OutboxState, ChainCommunicationError> {
let state = self.contract.state().call().await?;
Ok(OutboxState::try_from(state).expect("Invalid state received from contract"))
}
#[tracing::instrument(err, skip(self))]
async fn count(&self) -> Result<u32, ChainCommunicationError> {
Ok(self.contract.count().call().await?.as_u32())
}
#[tracing::instrument(err, skip(self))]
async fn cache_checkpoint(&self) -> Result<TxOutcome, ChainCommunicationError> {
let tx = self.contract.cache_checkpoint();
Ok(report_tx(tx).await?.into())
}
#[tracing::instrument(err, skip(self))]
async fn latest_cached_root(&self) -> Result<H256, ChainCommunicationError> {
Ok(self.contract.latest_cached_root().call().await?.into())
}
#[tracing::instrument(err, skip(self))]
async fn latest_cached_checkpoint(&self) -> Result<Checkpoint, ChainCommunicationError> {
let (root, index) = self.contract.latest_cached_checkpoint().call().await?;
Ok(Checkpoint {
outbox_domain: self.domain,
root: root.into(),
index: index.as_u32(),
})
}
#[tracing::instrument(err, skip(self))]
async fn latest_checkpoint(
&self,
maybe_lag: Option<u64>,
) -> Result<Checkpoint, ChainCommunicationError> {
let base_call = self.contract.latest_checkpoint();
let call_with_lag = match maybe_lag {
Some(lag) => {
let tip = self
.provider
.get_block_number()
.await
.map_err(|x| ChainCommunicationError::CustomError(Box::new(x)))?
.as_u64();
base_call.block(if lag > tip { 0 } else { tip - lag })
}
None => base_call,
};
let (root, index) = call_with_lag.call().await?;
Ok(Checkpoint {
outbox_domain: self.domain,
root: root.into(),
index: index.as_u32(),
})
}
}
pub struct EthereumOutboxAbi;
impl AbacusAbi for EthereumOutboxAbi {
fn fn_map() -> HashMap<Selector, &'static str> {
super::extract_fn_map(&OUTBOX_ABI)
}
}

@ -25,8 +25,9 @@ const METRICS_SCRAPE_INTERVAL: Duration = Duration::from_secs(60);
const HTTP_CLIENT_TIMEOUT: Duration = Duration::from_secs(60); const HTTP_CLIENT_TIMEOUT: Duration = Duration::from_secs(60);
/// A trait for dynamic trait creation with provider initialization. /// A trait for dynamic trait creation with provider initialization.
#[async_trait] #[async_trait]
pub trait MakeableWithProvider { pub trait MakeableWithProvider: Sync {
/// The type that will be created. /// The type that will be created.
type Output; type Output;

@ -1,207 +0,0 @@
#![allow(clippy::enum_variant_names)]
#![allow(missing_docs)]
use std::fmt::Display;
use std::sync::Arc;
use abacus_core::TxCostEstimate;
use async_trait::async_trait;
use ethers::abi::AbiEncode;
use ethers::prelude::*;
use ethers_contract::builders::ContractCall;
use eyre::{eyre, Result};
use abacus_core::{
accumulator::merkle::Proof, AbacusMessage, ChainCommunicationError, ContractLocator, Encode,
InboxValidatorManager, MultisigSignedCheckpoint, TxOutcome,
};
use crate::contracts::inbox_validator_manager::{
InboxValidatorManager as EthereumInboxValidatorManagerInternal, ProcessCall,
};
use crate::trait_builder::MakeableWithProvider;
use crate::tx::report_tx;
pub use crate::contracts::inbox_validator_manager::INBOXVALIDATORMANAGER_ABI;
impl<M> Display for EthereumInboxValidatorManagerInternal<M>
where
M: Middleware,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub struct InboxValidatorManagerBuilder {
pub inbox_address: Address,
}
impl MakeableWithProvider for InboxValidatorManagerBuilder {
type Output = Box<dyn InboxValidatorManager>;
fn make_with_provider<M: Middleware + 'static>(
&self,
provider: M,
locator: &ContractLocator,
) -> Self::Output {
Box::new(EthereumInboxValidatorManager::new(
Arc::new(provider),
locator,
self.inbox_address,
))
}
}
/// A struct that provides access to an Ethereum InboxValidatorManager contract
#[derive(Debug)]
pub struct EthereumInboxValidatorManager<M>
where
M: Middleware,
{
contract: Arc<EthereumInboxValidatorManagerInternal<M>>,
#[allow(unused)]
domain: u32,
#[allow(unused)]
chain_name: String,
#[allow(unused)]
provider: Arc<M>,
inbox_address: Address,
}
impl<M> EthereumInboxValidatorManager<M>
where
M: Middleware,
{
/// Create a reference to a inbox at a specific Ethereum address on some
/// chain
pub fn new(provider: Arc<M>, locator: &ContractLocator, inbox_address: Address) -> Self {
Self {
contract: Arc::new(EthereumInboxValidatorManagerInternal::new(
&locator.address,
provider.clone(),
)),
domain: locator.domain,
chain_name: locator.chain_name.to_owned(),
provider,
inbox_address,
}
}
}
#[async_trait]
impl<M> InboxValidatorManager for EthereumInboxValidatorManager<M>
where
M: Middleware + 'static,
{
#[tracing::instrument(skip(self))]
async fn process(
&self,
multisig_signed_checkpoint: &MultisigSignedCheckpoint,
message: &AbacusMessage,
proof: &Proof,
tx_gas_limit: Option<U256>,
) -> Result<TxOutcome, ChainCommunicationError> {
let contract_call = self
.process_contract_call(multisig_signed_checkpoint, message, proof, tx_gas_limit)
.await?;
let receipt = report_tx(contract_call).await?;
Ok(receipt.into())
}
async fn process_estimate_costs(
&self,
multisig_signed_checkpoint: &MultisigSignedCheckpoint,
message: &AbacusMessage,
proof: &Proof,
) -> Result<TxCostEstimate> {
let contract_call = self
.process_contract_call(multisig_signed_checkpoint, message, proof, None)
.await?;
let gas_limit = contract_call
.tx
.gas()
.ok_or_else(|| eyre!("Expected gas limit for process contract call"))?;
let gas_price = self.provider.get_gas_price().await?;
Ok(TxCostEstimate {
gas_limit: *gas_limit,
gas_price,
})
}
fn process_calldata(
&self,
multisig_signed_checkpoint: &MultisigSignedCheckpoint,
message: &AbacusMessage,
proof: &Proof,
) -> Vec<u8> {
let mut sol_proof: [[u8; 32]; 32] = Default::default();
sol_proof
.iter_mut()
.enumerate()
.for_each(|(i, elem)| *elem = proof.path[i].to_fixed_bytes());
let process_call = ProcessCall {
inbox: self.inbox_address,
root: multisig_signed_checkpoint.checkpoint.root.to_fixed_bytes(),
index: multisig_signed_checkpoint.checkpoint.index.into(),
signatures: multisig_signed_checkpoint
.signatures
.iter()
.map(|s| s.to_vec().into())
.collect(),
message: message.to_vec().into(),
proof: sol_proof,
leaf_index: proof.index.into(),
};
process_call.encode()
}
fn contract_address(&self) -> abacus_core::Address {
self.contract.address().into()
}
}
impl<M> EthereumInboxValidatorManager<M>
where
M: Middleware + 'static,
{
/// Returns a ContractCall that processes the provided message.
/// If the provided tx_gas_limit is None, gas estimation occurs.
async fn process_contract_call(
&self,
multisig_signed_checkpoint: &MultisigSignedCheckpoint,
message: &AbacusMessage,
proof: &Proof,
tx_gas_limit: Option<U256>,
) -> Result<ContractCall<M, ()>, ChainCommunicationError> {
let mut sol_proof: [[u8; 32]; 32] = Default::default();
sol_proof
.iter_mut()
.enumerate()
.for_each(|(i, elem)| *elem = proof.path[i].to_fixed_bytes());
let tx = self.contract.process(
self.inbox_address,
multisig_signed_checkpoint.checkpoint.root.to_fixed_bytes(),
multisig_signed_checkpoint.checkpoint.index.into(),
multisig_signed_checkpoint
.signatures
.iter()
.map(|s| s.to_vec().into())
.collect(),
message.to_vec().into(),
sol_proof,
proof.index.into(),
);
let gas_limit = if let Some(gas_limit) = tx_gas_limit {
gas_limit
} else {
tx.estimate_gas().await?.saturating_add(U256::from(100000))
};
Ok(tx.gas(gas_limit))
}
}

@ -1,126 +0,0 @@
{
"environment": "mainnet",
"signers": {},
"inboxes": {
"bsc": {
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x79b3D752cc9494eCB93800712471a7a62954C8AE",
"validatorManager": "0x61DDB465eEA5bc3708Cf8B53156aC91a77A2f029"
}
},
"avalanche": {
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x552D41c0B5c774F529C956E7CC77d0e054D7aFa8",
"validatorManager": "0x23ce76645EC601148fa451e751eeB75785b97A00"
}
},
"polygon": {
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x5060eCD5dFAD300A90592C04e504600A7cdcF70b",
"validatorManager": "0x4E1c88DD261BEe2941e6c1814597e30F53330428"
}
},
"celo": {
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x4E1c88DD261BEe2941e6c1814597e30F53330428",
"validatorManager": "0xC077A0Cc408173349b1c9870C667B40FE3C01dd7"
}
},
"optimism": {
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634",
"validatorManager": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d"
}
},
"ethereum": {
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x7082e975Fde8D85B0C56B4512b437efFb46F0a09",
"validatorManager": "0xCA41932888D323B3d99f5eA48F86D502055C0322"
}
},
"moonbeam": {
"domain": "1836002669",
"name": "moonbeam",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xCA41932888D323B3d99f5eA48F86D502055C0322",
"validatorManager": "0x71b2644183ECA86401c13577f5332fcc5e48352a"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004",
"interchainGasPaymaster": "0x376aD181E8cd45eAd5403F78d5A871D08c3c4D77"
},
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "14751425"
}
}

@ -1,123 +0,0 @@
{
"environment": "mainnet",
"signers": {},
"inboxes": {
"bsc": {
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B",
"validatorManager": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638"
}
},
"polygon": {
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xA805695C20ED9F4ce9905cd1aFaE7877A81ec0d7",
"validatorManager": "0x97423A68BAe94b5De52d767a17aBCc54c157c0E5"
}
},
"celo": {
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x97423A68BAe94b5De52d767a17aBCc54c157c0E5",
"validatorManager": "0xf9DbC8776Bc2812c4DBEc45383A1783Ac758Fb55"
}
},
"arbitrum": {
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9",
"validatorManager": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3"
}
},
"optimism": {
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x79b3D752cc9494eCB93800712471a7a62954C8AE",
"validatorManager": "0x61DDB465eEA5bc3708Cf8B53156aC91a77A2f029"
}
},
"ethereum": {
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x95Ad03405aC24c0bc247bdFDC113B01955A71761",
"validatorManager": "0x4B44e4305B42405382b7BeC717F64D0552a9D9Fe"
}
},
"moonbeam": {
"domain": "1836002669",
"name": "moonbeam",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x4B44e4305B42405382b7BeC717F64D0552a9D9Fe",
"validatorManager": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004",
"interchainGasPaymaster": "0xed9a722c543883FB7e07E78F3879762DE09eA7D5"
},
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path"
}

@ -1,126 +0,0 @@
{
"environment": "mainnet",
"signers": {},
"inboxes": {
"avalanche": {
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x4B44e4305B42405382b7BeC717F64D0552a9D9Fe",
"validatorManager": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA"
}
},
"polygon": {
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x9fA986ACB22953c504Fcf5985DFA476d481C3b1B",
"validatorManager": "0x0BD07E3934D1C4cc8Db0eA2a5cDAc8C8d8eb9824"
}
},
"celo": {
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xdB670e1a1e312BF17425b08cE55Bdf2cD8F8eD54",
"validatorManager": "0x83c2DB237e93Ce52565AB110124f78fdf159E3f4"
}
},
"arbitrum": {
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"validatorManager": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7"
}
},
"optimism": {
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x66DC49405Ae2956f7E87FEAa9fE8f506C8987462",
"validatorManager": "0xaad207a0Fd7a4e3C927Ccc78ac8134baF586B852"
}
},
"ethereum": {
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x81a92A1a272cb09d7b4970b07548463dC7aE0cB7",
"validatorManager": "0xC343A7054838FE9F249D7E3Ec1Fa6f1D108694b8"
}
},
"moonbeam": {
"domain": "1836002669",
"name": "moonbeam",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x552D41c0B5c774F529C956E7CC77d0e054D7aFa8",
"validatorManager": "0x23ce76645EC601148fa451e751eeB75785b97A00"
}
}
},
"outbox": {
"addresses": {
"outbox": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94",
"interchainGasPaymaster": "0x47bf94790241B1764fC41A35a8329A15569E121C"
},
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "18722839"
}
}

@ -1,126 +0,0 @@
{
"environment": "mainnet",
"signers": {},
"inboxes": {
"bsc": {
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x66DC49405Ae2956f7E87FEAa9fE8f506C8987462",
"validatorManager": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9"
}
},
"avalanche": {
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7",
"validatorManager": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7"
}
},
"polygon": {
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147",
"validatorManager": "0x086eF95a2F74582Ee30E7D698518a872fb18301f"
}
},
"arbitrum": {
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x4B44e4305B42405382b7BeC717F64D0552a9D9Fe",
"validatorManager": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA"
}
},
"optimism": {
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0",
"validatorManager": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa"
}
},
"ethereum": {
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xCB5C99F908410add8241b558299fe9aadC06bA99",
"validatorManager": "0x552D41c0B5c774F529C956E7CC77d0e054D7aFa8"
}
},
"moonbeam": {
"domain": "1836002669",
"name": "moonbeam",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9",
"validatorManager": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7"
}
}
},
"outbox": {
"addresses": {
"outbox": "0xe042D1fbDf59828dd16b9649Ede7abFc856F7a6c",
"interchainGasPaymaster": "0xCDeb368Db32ecCefaf7018e152DA9120565cb572"
},
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "13551287"
}
}

@ -1,126 +0,0 @@
{
"environment": "mainnet",
"signers": {},
"inboxes": {
"bsc": {
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0",
"validatorManager": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa"
}
},
"avalanche": {
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x0E3239277501d215e17a4d31c487F86a425E110B",
"validatorManager": "0x28EFBCadA00A7ed6772b3666F3898d276e88CAe3"
}
},
"polygon": {
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x6A55822cf11f9fcBc4c75BC2638AfE8Eb942cAdd",
"validatorManager": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28"
}
},
"celo": {
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28",
"validatorManager": "0x2Fca7f6eC3d4A0408900f2BB30004d4616eE985E"
}
},
"arbitrum": {
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x552D41c0B5c774F529C956E7CC77d0e054D7aFa8",
"validatorManager": "0x23ce76645EC601148fa451e751eeB75785b97A00"
}
},
"optimism": {
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4",
"validatorManager": "0xB3fCcD379ad66CED0c91028520C64226611A48c9"
}
},
"moonbeam": {
"domain": "1836002669",
"name": "moonbeam",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x14c3CEee8F431aE947364f43429a98EA89800238",
"validatorManager": "0x8428a1a7E97Fc75Fb7Ba5c4aec31B55e52bbe9D6"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3",
"interchainGasPaymaster": "0x17E216fBb22dF4ef8A6640ae9Cb147C92710ac84"
},
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "14970190"
}
}

@ -1,126 +0,0 @@
{
"environment": "mainnet",
"signers": {},
"inboxes": {
"bsc": {
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x79e25126E1dAB135734e0261E8aB93674131fD2b",
"validatorManager": "0x319f058FeedA044bD20E949FDCA31AEbb19b0063"
}
},
"avalanche": {
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x32af5Df81fEd5E26119F6640FBB13f3d63a94CDe",
"validatorManager": "0xDd0D36E55078c643cefDc17936b63BACC71c50Da"
}
},
"polygon": {
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x6267Dbfc38f7Af897536563c15f07B89634cb656",
"validatorManager": "0x0c7b67793c56eD93773cEee07A43B3D7aDF533b7"
}
},
"celo": {
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xEb4ca142644172878Bee23E44C8BDae215E92430",
"validatorManager": "0x0D11258092e5BC4a813478ff8837887C2A1a6e89"
}
},
"arbitrum": {
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x88AcaD5222Fbb66C23d0E9532FDd32e57C68a53F",
"validatorManager": "0x76b76307f778CB98Cc71DF9f00cBF99C32544C03"
}
},
"optimism": {
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xCDaebcc592DA5c982B05E95039FF5f3467420223",
"validatorManager": "0xdc47eDc036daaE45D3F019CCfD443Bf72fBD981c"
}
},
"ethereum": {
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xdc47eDc036daaE45D3F019CCfD443Bf72fBD981c",
"validatorManager": "0x1Dcf599693707f41375695488589F4C6Af3845e8"
}
}
},
"outbox": {
"addresses": {
"outbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D",
"interchainGasPaymaster": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6"
},
"domain": "1836002669",
"name": "moonbeam",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "2050158"
}
}

@ -1,126 +0,0 @@
{
"environment": "mainnet",
"signers": {},
"inboxes": {
"bsc": {
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882",
"validatorManager": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA"
}
},
"avalanche": {
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x14c3CEee8F431aE947364f43429a98EA89800238",
"validatorManager": "0x8428a1a7E97Fc75Fb7Ba5c4aec31B55e52bbe9D6"
}
},
"polygon": {
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xc22B646edf6c9A43d83fDBc8D5E1B3c6DAfACb83",
"validatorManager": "0xF5165f115ba4E1Adc09f0EB392232D65F219806a"
}
},
"celo": {
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xF5165f115ba4E1Adc09f0EB392232D65F219806a",
"validatorManager": "0x781bE492F1232E66990d83a9D3AC3Ec26f56DAfB"
}
},
"arbitrum": {
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x0E3239277501d215e17a4d31c487F86a425E110B",
"validatorManager": "0x28EFBCadA00A7ed6772b3666F3898d276e88CAe3"
}
},
"ethereum": {
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xF7af65596A16740b16CF755F3A43206C96285da0",
"validatorManager": "0xF5739A4AF21346Aa937bF7fEB5d3B21c2d230138"
}
},
"moonbeam": {
"domain": "1836002669",
"name": "moonbeam",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xF5739A4AF21346Aa937bF7fEB5d3B21c2d230138",
"validatorManager": "0xBC9cd961BF6c224FAc51fb049aB6788e38e4A9C0"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x0be2Ae2f6D02a3e0e00ECB57D3E1fCbb7f8F38F4",
"interchainGasPaymaster": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563"
},
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "11966568"
}
}

@ -1,126 +0,0 @@
{
"environment": "mainnet",
"signers": {},
"inboxes": {
"bsc": {
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xaad207a0Fd7a4e3C927Ccc78ac8134baF586B852",
"validatorManager": "0x8f4BeB6552b76aA38Cd9994701c0Da7bC829648B"
}
},
"avalanche": {
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9",
"validatorManager": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3"
}
},
"celo": {
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x086eF95a2F74582Ee30E7D698518a872fb18301f",
"validatorManager": "0x95878Fd41bC26f7045C0b98e381c22f010745A75"
}
},
"arbitrum": {
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x14c3CEee8F431aE947364f43429a98EA89800238",
"validatorManager": "0x8428a1a7E97Fc75Fb7Ba5c4aec31B55e52bbe9D6"
}
},
"optimism": {
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882",
"validatorManager": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA"
}
},
"ethereum": {
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xF59557dfacDc5a1cb8A36Af43aA4819a6A891e88",
"validatorManager": "0x0E3239277501d215e17a4d31c487F86a425E110B"
}
},
"moonbeam": {
"domain": "1836002669",
"name": "moonbeam",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x0E3239277501d215e17a4d31c487F86a425E110B",
"validatorManager": "0x28EFBCadA00A7ed6772b3666F3898d276e88CAe3"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x8249cD1275855F2BB20eE71f0B9fA3c9155E5FaB",
"interchainGasPaymaster": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53"
},
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "29616068"
}
}

@ -1,249 +0,0 @@
{
"environment": "mainnet",
"signers": {},
"inboxes": {
"arbitrum": {
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9",
"validatorManager": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3"
}
},
"avalanche": {
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x552D41c0B5c774F529C956E7CC77d0e054D7aFa8",
"validatorManager": "0x23ce76645EC601148fa451e751eeB75785b97A00"
}
},
"bsc": {
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x79b3D752cc9494eCB93800712471a7a62954C8AE",
"validatorManager": "0x61DDB465eEA5bc3708Cf8B53156aC91a77A2f029"
}
},
"celo": {
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x4E1c88DD261BEe2941e6c1814597e30F53330428",
"validatorManager": "0xC077A0Cc408173349b1c9870C667B40FE3C01dd7"
}
},
"ethereum": {
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x7082e975Fde8D85B0C56B4512b437efFb46F0a09",
"validatorManager": "0xCA41932888D323B3d99f5eA48F86D502055C0322"
}
},
"optimism": {
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634",
"validatorManager": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d"
}
},
"polygon": {
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x5060eCD5dFAD300A90592C04e504600A7cdcF70b",
"validatorManager": "0x4E1c88DD261BEe2941e6c1814597e30F53330428"
}
}
},
"outbox": {
"addresses": {
"outbox": "",
"interchainGasPaymaster": ""
},
"domain": "0",
"name": "IGNORED",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"outboxes": {
"arbitrum": {
"addresses": {
"outbox": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004",
"interchainGasPaymaster": "0x376aD181E8cd45eAd5403F78d5A871D08c3c4D77"
},
"domain": "6386274",
"name": "arbitrum",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"avalanche": {
"addresses": {
"outbox": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004",
"interchainGasPaymaster": "0xed9a722c543883FB7e07E78F3879762DE09eA7D5"
},
"domain": "1635148152",
"name": "avalanche",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
}
},
"bsc": {
"addresses": {
"outbox": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94",
"interchainGasPaymaster": "0x47bf94790241B1764fC41A35a8329A15569E121C"
},
"domain": "6452067",
"name": "bsc",
"rpcStyle": "ethereum",
"finalityBlocks": "15",
"connection": {
"type": "http",
"url": ""
}
},
"celo": {
"addresses": {
"outbox": "0xe042D1fbDf59828dd16b9649Ede7abFc856F7a6c",
"interchainGasPaymaster": "0xCDeb368Db32ecCefaf7018e152DA9120565cb572"
},
"domain": "1667591279",
"name": "celo",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"ethereum": {
"addresses": {
"outbox": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3",
"interchainGasPaymaster": "0x17E216fBb22dF4ef8A6640ae9Cb147C92710ac84"
},
"domain": "6648936",
"name": "ethereum",
"rpcStyle": "ethereum",
"finalityBlocks": "20",
"connection": {
"type": "http",
"url": ""
}
},
"optimism": {
"addresses": {
"outbox": "0x0be2Ae2f6D02a3e0e00ECB57D3E1fCbb7f8F38F4",
"interchainGasPaymaster": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563"
},
"domain": "28528",
"name": "optimism",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"polygon": {
"addresses": {
"outbox": "0x8249cD1275855F2BB20eE71f0B9fA3c9155E5FaB",
"interchainGasPaymaster": "0x60B8d195f1b2EcaC26d54b95C69E6399cFD64b53"
},
"domain": "1886350457",
"name": "polygon",
"rpcStyle": "ethereum",
"finalityBlocks": "256",
"connection": {
"type": "http",
"url": ""
}
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": ""
},
"indexes": {
"arbitrum": {
"from": "14751425"
},
"avalanche": {
"from": "16077239"
},
"bsc": {
"from": "18722839"
},
"celo": {
"from": "13551287"
},
"ethereum": {
"from": "14970190"
},
"optimism": {
"from": "11966568"
},
"polygon": {
"from": "29616068"
}
}
}

@ -1,56 +0,0 @@
{
"environment": "test",
"signers": {},
"inboxes": {
"test2": {
"domain": "13372",
"name": "test2",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690",
"validatorManager": "0xc5a5C42992dECbae36851359345FE25997F5C42d"
}
},
"test3": {
"domain": "13373",
"name": "test3",
"rpcStyle": "ethereum",
"finalityBlocks": "2",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x4c5859f0F772848b2D91F1D83E2Fe57935348029",
"validatorManager": "0x5eb3Bc0a489C5A8288765d2336659EbCA68FCd00"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6",
"interchainGasPaymaster": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"
},
"domain": "13371",
"name": "test1",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "1"
}
}

@ -1,56 +0,0 @@
{
"environment": "test",
"signers": {},
"inboxes": {
"test1": {
"domain": "13371",
"name": "test1",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
"validatorManager": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788"
}
},
"test3": {
"domain": "13373",
"name": "test3",
"rpcStyle": "ethereum",
"finalityBlocks": "2",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xb7278A61aa25c888815aFC32Ad3cC52fF24fE575",
"validatorManager": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
"interchainGasPaymaster": "0x59b670e9fA9D0A427751Af201D676719a970857b"
},
"domain": "13372",
"name": "test2",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "19"
}
}

@ -1,56 +0,0 @@
{
"environment": "test",
"signers": {},
"inboxes": {
"test1": {
"domain": "13371",
"name": "test1",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
"validatorManager": "0x0B306BF915C4d645ff596e518fAf3F9669b97016"
}
},
"test2": {
"domain": "13372",
"name": "test2",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
"validatorManager": "0x9E545E3C0baAB3E08CdfD552C960A1050f373042"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf",
"interchainGasPaymaster": "0x998abeb3E57409262aE5b751f60747921B33613E"
},
"domain": "13373",
"name": "test3",
"rpcStyle": "ethereum",
"finalityBlocks": "2",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "38"
}
}

@ -0,0 +1,65 @@
{
"environment": "test",
"chains": {
"test1": {
"name": "test1",
"domain": "13371",
"addresses": {
"mailbox": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
"interchainGasPaymaster": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
"multisigIsm": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9"
},
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"index": {
"from": "4"
}
},
"test2": {
"name": "test2",
"domain": "13372",
"addresses": {
"mailbox": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44",
"interchainGasPaymaster": "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
"multisigIsm": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1"
},
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"index": {
"from": "16"
}
},
"test3": {
"name": "test3",
"domain": "13373",
"addresses": {
"mailbox": "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8",
"interchainGasPaymaster": "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
"multisigIsm": "0xc5a5C42992dECbae36851359345FE25997F5C42d"
},
"rpcStyle": "ethereum",
"finalityBlocks": "2",
"connection": {
"type": "http",
"url": ""
},
"index": {
"from": "28"
}
}
},
"signers": {},
"db": "db_path",
"tracing": {
"level": "debug",
"fmt": "json"
}
}

@ -1,98 +0,0 @@
{
"environment": "testnet2",
"signers": {},
"inboxes": {
"fuji": {
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xaf578f7f9a4D835aaCB5909AD5F39139022173fB",
"validatorManager": "0x4904f38433583f0F72609C0bb8788d3296bd0E3B"
}
},
"mumbai": {
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
"finalityBlocks": "32",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x772926Ffc5FE8B3ae9a85cB085700748606aE283",
"validatorManager": "0x99A42d6Bf191127667f55297Af0259708bd8c59e"
}
},
"bsctestnet": {
"domain": "1651715444",
"name": "bsctestnet",
"rpcStyle": "ethereum",
"finalityBlocks": "9",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xFfA20C4c8e3b2A2C1220134684FEe23EEB8872d0",
"validatorManager": "0x5B30De0c322F7720D144df2AB2e82b160Eba0EBF"
}
},
"goerli": {
"domain": "5",
"name": "goerli",
"rpcStyle": "ethereum",
"finalityBlocks": "7",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xD3d062a5dcBA85ae863618d4c264d2358300c283",
"validatorManager": "0xB08d78F439e55D02C398519eef61606A5926245F"
}
},
"moonbasealpha": {
"domain": "1836002657",
"name": "moonbasealpha",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc",
"validatorManager": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x5C7D9B5f38022dB78416D6C0132bf8c404deDe27",
"interchainGasPaymaster": "0x1Fb165396FB26AC4178ca4240b3724039F75EED7"
},
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "11877043"
}
}

@ -1,98 +0,0 @@
{
"environment": "testnet2",
"signers": {},
"inboxes": {
"alfajores": {
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x3582d1238cBC812165981E4fFaB0E8D9a4518910",
"validatorManager": "0x0AfCCF2ffc1D7A42b3F8616C4270Da27f2729F5F"
}
},
"fuji": {
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x4e49616d6f26C3080277b2fBDA242690AD403420",
"validatorManager": "0x15569bE4B03593A9eA93Bd519bB74928B1eF5fB2"
}
},
"mumbai": {
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
"finalityBlocks": "32",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xFd9387BB1506F4Eb4Ac1a1f8c8128FB89b83e64c",
"validatorManager": "0x3572a9d808738922194921b275B2A55414BcDA57"
}
},
"goerli": {
"domain": "5",
"name": "goerli",
"rpcStyle": "ethereum",
"finalityBlocks": "7",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x7914A3349107A7295Bbf2374db5A973d73D1b324",
"validatorManager": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8"
}
},
"moonbasealpha": {
"domain": "1836002657",
"name": "moonbasealpha",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xef48bd850E5827B96B55C4D28FB32Bbaa73616F2",
"validatorManager": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8"
}
}
},
"outbox": {
"addresses": {
"outbox": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350",
"interchainGasPaymaster": "0x155b1F1801030Ea4dF038107d3cc1b4bA496916e"
},
"domain": "1651715444",
"name": "bsctestnet",
"rpcStyle": "ethereum",
"finalityBlocks": "9",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "20035396"
}
}

@ -1,98 +0,0 @@
{
"environment": "testnet2",
"signers": {},
"inboxes": {
"alfajores": {
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xac5e56b6eF335bbE4413eE48965dB6B538415E49",
"validatorManager": "0x793b4c911362c8900372cE6Da5f9dA96457E8c1B"
}
},
"mumbai": {
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
"finalityBlocks": "32",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x56c09458cC7863fff1Cc6Bcb6652Dcc3412FcA86",
"validatorManager": "0xd09D08a19C6609a1B51e1ca6a055861E7e7A4400"
}
},
"bsctestnet": {
"domain": "1651715444",
"name": "bsctestnet",
"rpcStyle": "ethereum",
"finalityBlocks": "9",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xE3D93F9296FA3dF262E1a54f0de02F71E845af6b",
"validatorManager": "0x6d6a9bDDea1456673062633b7a4823dB13bDB9fb"
}
},
"goerli": {
"domain": "5",
"name": "goerli",
"rpcStyle": "ethereum",
"finalityBlocks": "7",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xfc8d0D2E15A36f1A3F3aE3Cb127B706c1f23Aadc",
"validatorManager": "0xF7F0DaB0BECE4498dAc7eb616e288809D4499371"
}
},
"moonbasealpha": {
"domain": "1836002657",
"name": "moonbasealpha",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x1D5EbC3e15e9ECDe0e3530C85899556797eeaea5",
"validatorManager": "0x7FE7EA170cf08A25C2ff315814D96D93C311E692"
}
}
},
"outbox": {
"addresses": {
"outbox": "0xc507A7c848b59469cC44A3653F8a582aa8BeC71E",
"interchainGasPaymaster": "0x4834a491f78BBF48e983F9Ce0E20D1E4DbE013D8"
},
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "10542676"
}
}

@ -1,98 +0,0 @@
{
"environment": "testnet2",
"signers": {},
"inboxes": {
"alfajores": {
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x5C96BfCBD87E4E8A5208fD080A28c74F8Ca12285",
"validatorManager": "0x80B24aFeC7dD9B67CaEF0f06592753ed5e52783F"
}
},
"fuji": {
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xa5D5EdF366F0D8FF135EBb31555E10b07f096427",
"validatorManager": "0x63C619FF7caE1d565149EB6381E24CA53F957704"
}
},
"mumbai": {
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x934809a3a89CAdaB30F0A8C703619C3E02c37616",
"validatorManager": "0x412219094B1E49e7b53fDE9F5Cd793fE9dD07615"
}
},
"bsctestnet": {
"domain": "1651715444",
"name": "bsctestnet",
"rpcStyle": "ethereum",
"finalityBlocks": "9",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xb51D33b294aF850E47CcEdD7C4580A547507f675",
"validatorManager": "0xEd23982947054CfeDD759163cbbC5CDA911A43d5"
}
},
"moonbasealpha": {
"domain": "1836002657",
"name": "moonbasealpha",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x679Dc08cC3A4acFeea2f7CAFAa37561aE0b41Ce7",
"validatorManager": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A"
}
}
},
"outbox": {
"addresses": {
"outbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"interchainGasPaymaster": "0x44b764045BfDC68517e10e783E69B376cef196B2"
},
"domain": "5",
"name": "goerli",
"rpcStyle": "ethereum",
"finalityBlocks": "7",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"index": {
"from": "7061415"
},
"db": "db_path"
}

@ -1,98 +0,0 @@
{
"environment": "testnet2",
"signers": {},
"inboxes": {
"alfajores": {
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xBE3541E3D391751Ae73cC0A52F48CCe45120f74B",
"validatorManager": "0xdE1a1f41871ebD2D1B8ddac6BAC3a2F4898a1747"
}
},
"fuji": {
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xb31b0a575a151E0E72D438999f5a65e08802466f",
"validatorManager": "0x8a14566c8649C2b72600c920F40aF161FB435846"
}
},
"mumbai": {
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
"finalityBlocks": "3",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xEf9bae5E38c552bEc367b6B4f7a4D0a5e663B898",
"validatorManager": "0xeAD058dc774892e71403C4EB4600850A89524EaD"
}
},
"bsctestnet": {
"domain": "1651715444",
"name": "bsctestnet",
"rpcStyle": "ethereum",
"finalityBlocks": "9",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x963f552583E56ddBc58d12Aa5f8f85187A72E142",
"validatorManager": "0x5F3aA4De5132688c2c1750D3780AdD49d72FAaBC"
}
},
"goerli": {
"domain": "5",
"name": "goerli",
"rpcStyle": "ethereum",
"finalityBlocks": "7",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE",
"validatorManager": "0x4926a10788306D84202A2aDbd290b7743146Cc17"
}
}
},
"outbox": {
"addresses": {
"outbox": "0x54148470292C24345fb828B003461a9444414517",
"interchainGasPaymaster": "0xeb6f11189197223c656807a83B0DD374f9A6dF44"
},
"domain": "1836002657",
"name": "moonbasealpha",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"index": {
"from": "2309252"
},
"db": "db_path"
}

@ -1,98 +0,0 @@
{
"environment": "testnet2",
"signers": {},
"inboxes": {
"alfajores": {
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x06a919Ec005Be1c7319c18ab7a51A4C62a69Fe2A",
"validatorManager": "0x50d45EEe9C2903Ad204d393F5411Aa75A6CB02c4"
}
},
"fuji": {
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x04268B83eE9684F8767eB4e83cf7fBb7B86Ed597",
"validatorManager": "0x5e976f063FbE35d29d6E575f8ee504e59D19fcc6"
}
},
"bsctestnet": {
"domain": "1651715444",
"name": "bsctestnet",
"rpcStyle": "ethereum",
"finalityBlocks": "9",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x14EE2f01907707Ce8d13C4F5DBC40778b5b664e0",
"validatorManager": "0xDa5177080f7fC5d9255eB32cC64B9b4e5136A716"
}
},
"goerli": {
"domain": "5",
"name": "goerli",
"rpcStyle": "ethereum",
"finalityBlocks": "7",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8",
"validatorManager": "0xd785272D240B07719e417622cbd2cfA0E584d1bd"
}
},
"moonbasealpha": {
"domain": "1836002657",
"name": "moonbasealpha",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x0526E47C49742C15F8817ef8cf0d8FFc72139D4F",
"validatorManager": "0xfc8d0D2E15A36f1A3F3aE3Cb127B706c1f23Aadc"
}
}
},
"outbox": {
"addresses": {
"outbox": "0xe17c37212d785760E8331D4A4395B17b34Ba8cDF",
"interchainGasPaymaster": "0x9A27744C249A11f68B3B56f09D280599585DFBb8"
},
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
"finalityBlocks": "32",
"connection": {
"type": "http",
"url": ""
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": "26666833"
}
}

@ -1,218 +0,0 @@
{
"environment": "testnet2",
"signers": {},
"inboxes": {
"alfajores": {
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x5939e17FE19669Ba22f06603CA2eBE18C836c6Ab",
"validatorManager": "0xAf4778deef817E02c63076d803D687596067179d"
}
},
"fuji": {
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xaf578f7f9a4D835aaCB5909AD5F39139022173fB",
"validatorManager": "0x4904f38433583f0F72609C0bb8788d3296bd0E3B"
}
},
"mumbai": {
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
"finalityBlocks": "32",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x772926Ffc5FE8B3ae9a85cB085700748606aE283",
"validatorManager": "0x99A42d6Bf191127667f55297Af0259708bd8c59e"
}
},
"bsctestnet": {
"domain": "1651715444",
"name": "bsctestnet",
"rpcStyle": "ethereum",
"finalityBlocks": "9",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xFfA20C4c8e3b2A2C1220134684FEe23EEB8872d0",
"validatorManager": "0x5B30De0c322F7720D144df2AB2e82b160Eba0EBF"
}
},
"goerli": {
"domain": "5",
"name": "goerli",
"rpcStyle": "ethereum",
"finalityBlocks": "7",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0xD3d062a5dcBA85ae863618d4c264d2358300c283",
"validatorManager": "0xB08d78F439e55D02C398519eef61606A5926245F"
}
},
"moonbasealpha": {
"domain": "1836002657",
"name": "moonbasealpha",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
},
"addresses": {
"inbox": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc",
"validatorManager": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD"
}
}
},
"outbox": {
"addresses": {
"outbox": "",
"interchainGasPaymaster": ""
},
"domain": "0",
"name": "IGNORED",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"outboxes": {
"alfajores": {
"addresses": {
"outbox": "0x5C7D9B5f38022dB78416D6C0132bf8c404deDe27",
"interchainGasPaymaster": "0x1Fb165396FB26AC4178ca4240b3724039F75EED7"
},
"domain": "1000",
"name": "alfajores",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"bsctestnet": {
"addresses": {
"outbox": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350",
"interchainGasPaymaster": "0x155b1F1801030Ea4dF038107d3cc1b4bA496916e"
},
"domain": "1651715444",
"name": "bsctestnet",
"rpcStyle": "ethereum",
"finalityBlocks": "9",
"connection": {
"type": "http",
"url": ""
}
},
"fuji": {
"addresses": {
"outbox": "0xc507A7c848b59469cC44A3653F8a582aa8BeC71E",
"interchainGasPaymaster": "0x4834a491f78BBF48e983F9Ce0E20D1E4DbE013D8"
},
"domain": "43113",
"name": "fuji",
"rpcStyle": "ethereum",
"finalityBlocks": "0",
"connection": {
"type": "http",
"url": ""
}
},
"goerli": {
"addresses": {
"outbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"interchainGasPaymaster": "0x44b764045BfDC68517e10e783E69B376cef196B2"
},
"domain": "5",
"name": "goerli",
"rpcStyle": "ethereum",
"finalityBlocks": "7",
"connection": {
"type": "http",
"url": ""
}
},
"moonbasealpha": {
"addresses": {
"outbox": "0x54148470292C24345fb828B003461a9444414517",
"interchainGasPaymaster": "0xeb6f11189197223c656807a83B0DD374f9A6dF44"
},
"domain": "1836002657",
"name": "moonbasealpha",
"rpcStyle": "ethereum",
"finalityBlocks": "1",
"connection": {
"type": "http",
"url": ""
}
},
"mumbai": {
"addresses": {
"outbox": "0xe17c37212d785760E8331D4A4395B17b34Ba8cDF",
"interchainGasPaymaster": "0x9A27744C249A11f68B3B56f09D280599585DFBb8"
},
"domain": "80001",
"name": "mumbai",
"rpcStyle": "ethereum",
"finalityBlocks": "32",
"connection": {
"type": "http",
"url": ""
}
}
},
"tracing": {
"level": "debug",
"fmt": "json"
},
"db": "db_path",
"index": {
"from": ""
},
"indexes": {
"alfajores": {
"from": "11877043"
},
"bsctestnet": {
"from": "20035396"
},
"fuji": {
"from": "10542676"
},
"goerli": {
"from": "7061415"
},
"moonbasealpha": {
"from": "2309252"
},
"mumbai": {
"from": "26666833"
}
}
}

@ -140,13 +140,13 @@ fn main() -> ExitCode {
}; };
let relayer_env = hashmap! { let relayer_env = hashmap! {
"HYP_BASE_OUTBOX_CONNECTION_URLS" => "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545", "HYP_BASE_CHAINS_TEST1_CONNECTION_URLS" => "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545",
"HYP_BASE_OUTBOX_CONNECTION_TYPE" => "httpQuorum", "HYP_BASE_CHAINS_TEST1_CONNECTION_TYPE" => "httpQuorum",
"HYP_BASE_INBOXES_TEST2_CONNECTION_URLS" => "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545", "HYP_BASE_CHAINS_TEST2_CONNECTION_URLS" => "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545",
"HYP_BASE_INBOXES_TEST2_CONNECTION_TYPE" => "httpQuorum", "HYP_BASE_CHAINS_TEST2_CONNECTION_TYPE" => "httpQuorum",
"HYP_BASE_INBOXES_TEST3_CONNECTION_URL" => "http://127.0.0.1:8545", "HYP_BASE_CHAINS_TEST3_CONNECTION_URL" => "http://127.0.0.1:8545",
"HYP_BASE_INBOXES_TEST3_CONNECTION_TYPE" => "http", "HYP_BASE_CHAINS_TEST3_CONNECTION_TYPE" => "http",
"BASE_CONFIG" => "test1_config.json", "BASE_CONFIG" => "test_config.json",
"RUN_ENV" => "test", "RUN_ENV" => "test",
"HYP_BASE_METRICS" => "9092", "HYP_BASE_METRICS" => "9092",
"HYP_BASE_TRACING_FMT" => "pretty", "HYP_BASE_TRACING_FMT" => "pretty",
@ -159,6 +159,7 @@ fn main() -> ExitCode {
"HYP_BASE_SIGNERS_TEST3_KEY" => "701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82", "HYP_BASE_SIGNERS_TEST3_KEY" => "701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82",
"HYP_BASE_SIGNERS_TEST3_TYPE" => "hexKey", "HYP_BASE_SIGNERS_TEST3_TYPE" => "hexKey",
"HYP_RELAYER_GASPAYMENTENFORCEMENTPOLICY_TYPE" => "none", "HYP_RELAYER_GASPAYMENTENFORCEMENTPOLICY_TYPE" => "none",
"HYP_RELAYER_ORIGINCHAINNAME" => "test1",
"HYP_RELAYER_WHITELIST" => r#"[{"sourceAddress": "*", "destinationDomain": ["13372", "13373"], "destinationAddress": "*"}]"#, "HYP_RELAYER_WHITELIST" => r#"[{"sourceAddress": "*", "destinationDomain": ["13372", "13373"], "destinationAddress": "*"}]"#,
"HYP_RELAYER_SIGNEDCHECKPOINTPOLLINGINTERVAL" => "5", "HYP_RELAYER_SIGNEDCHECKPOINTPOLLINGINTERVAL" => "5",
"HYP_RELAYER_MULTISIGCHECKPOINTSYNCER_THRESHOLD" => "1", "HYP_RELAYER_MULTISIGCHECKPOINTSYNCER_THRESHOLD" => "1",
@ -167,18 +168,19 @@ fn main() -> ExitCode {
}; };
let validator_env = hashmap! { let validator_env = hashmap! {
"HYP_BASE_OUTBOX_CONNECTION_URLS" => "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545", "HYP_BASE_CHAINS_TEST1_CONNECTION_URLS" => "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545",
"HYP_BASE_OUTBOX_CONNECTION_TYPE" => "httpQuorum", "HYP_BASE_CHAINS_TEST1_CONNECTION_TYPE" => "httpQuorum",
"HYP_BASE_INBOXES_TEST2_CONNECTION_URLS" => "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545", "HYP_BASE_CHAINS_TEST2_CONNECTION_URLS" => "http://127.0.0.1:8545,http://127.0.0.1:8545,http://127.0.0.1:8545",
"HYP_BASE_INBOXES_TEST2_CONNECTION_TYPE" => "httpQuorum", "HYP_BASE_CHAINS_TEST2_CONNECTION_TYPE" => "httpQuorum",
"HYP_BASE_INBOXES_TEST3_CONNECTION_URLS" => "http://127.0.0.1:8545", "HYP_BASE_CHAINS_TEST3_CONNECTION_URLS" => "http://127.0.0.1:8545",
"HYP_BASE_INBOXES_TEST3_CONNECTION_TYPE" => "http", "HYP_BASE_CHAINS_TEST3_CONNECTION_TYPE" => "http",
"BASE_CONFIG" => "test1_config.json", "BASE_CONFIG" => "test_config.json",
"RUN_ENV" => "test", "RUN_ENV" => "test",
"HYP_BASE_METRICS" => "9091", "HYP_BASE_METRICS" => "9091",
"HYP_BASE_TRACING_FMT" => "pretty", "HYP_BASE_TRACING_FMT" => "pretty",
"HYP_BASE_TRACING_LEVEL" => "info", "HYP_BASE_TRACING_LEVEL" => "info",
"HYP_BASE_DB" => validator_db.to_str().unwrap(), "HYP_BASE_DB" => validator_db.to_str().unwrap(),
"HYP_VALIDATOR_ORIGINCHAINNAME" => "test1",
"HYP_VALIDATOR_VALIDATOR_KEY" => "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", "HYP_VALIDATOR_VALIDATOR_KEY" => "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
"HYP_VALIDATOR_VALIDATOR_TYPE" => "hexKey", "HYP_VALIDATOR_VALIDATOR_TYPE" => "hexKey",
"HYP_VALIDATOR_REORGPERIOD" => "0", "HYP_VALIDATOR_REORGPERIOD" => "0",
@ -197,6 +199,7 @@ fn main() -> ExitCode {
println!("Building typescript..."); println!("Building typescript...");
build_cmd(&["yarn", "install"], &build_log, log_all, Some("../")); build_cmd(&["yarn", "install"], &build_log, log_all, Some("../"));
build_cmd(&["yarn", "clean"], &build_log, log_all, Some("../"));
build_cmd(&["yarn", "build"], &build_log, log_all, Some("../")); build_cmd(&["yarn", "build"], &build_log, log_all, Some("../"));
println!("Building relayer..."); println!("Building relayer...");
@ -325,7 +328,7 @@ fn main() -> ExitCode {
.current_dir("../typescript/infra") .current_dir("../typescript/infra")
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.expect("Failed tp start kathy"); .expect("Failed to start kathy");
let kathy_stdout = kathy.stdout.take().unwrap(); let kathy_stdout = kathy.stdout.take().unwrap();
state.watchers.push(spawn(move || { state.watchers.push(spawn(move || {
if log_all { if log_all {
@ -428,7 +431,7 @@ fn assert_termination_invariants(num_expected_messages_processed: u32) {
.unwrap() .unwrap()
.lines() .lines()
.filter(|l| l.contains(r#"phase="message_processed""#)) .filter(|l| l.contains(r#"phase="message_processed""#))
.filter(|l| l.starts_with("abacus_last_known_message_leaf_index")) .filter(|l| l.starts_with("abacus_last_known_message_nonce"))
.map(|l| l.rsplit_once(' ').unwrap().1.parse::<u32>().unwrap()) .map(|l| l.rsplit_once(' ').unwrap().1.parse::<u32>().unwrap())
.collect(); .collect();
assert!( assert!(
@ -558,5 +561,9 @@ fn build_cmd(cmd: &[&str], log: impl AsRef<Path>, log_all: bool, wd: Option<&str
c.current_dir(wd); c.current_dir(wd);
} }
let status = c.status().expect("Failed to run command"); let status = c.status().expect("Failed to run command");
assert!(status.success(), "Command returned non-zero exit code"); assert!(
status.success(),
"Command returned non-zero exit code: {}",
cmd.join(" ")
);
} }

@ -174,8 +174,9 @@ contract Mailbox is
/** /**
* @notice Returns the number of inserted leaves in the tree * @notice Returns the number of inserted leaves in the tree
*/ */
function count() public view returns (uint256) { function count() public view returns (uint32) {
return tree.count; // count cannot exceed 2**TREE_DEPTH, see MerkleLib.sol
return uint32(tree.count);
} }
/** /**
@ -183,7 +184,7 @@ contract Mailbox is
* @return root The root of the Outbox's merkle tree. * @return root The root of the Outbox's merkle tree.
* @return index The index of the last element in the tree. * @return index The index of the last element in the tree.
*/ */
function latestCheckpoint() public view returns (bytes32, uint256) { function latestCheckpoint() public view returns (bytes32, uint32) {
return (root(), count() - 1); return (root(), count() - 1);
} }

@ -12,11 +12,11 @@ library Message {
uint256 private constant VERSION_OFFSET = 0; uint256 private constant VERSION_OFFSET = 0;
uint256 private constant NONCE_OFFSET = 1; uint256 private constant NONCE_OFFSET = 1;
uint256 private constant ORIGIN_OFFSET = 33; uint256 private constant ORIGIN_OFFSET = 5;
uint256 private constant SENDER_OFFSET = 37; uint256 private constant SENDER_OFFSET = 9;
uint256 private constant DESTINATION_OFFSET = 69; uint256 private constant DESTINATION_OFFSET = 41;
uint256 private constant RECIPIENT_OFFSET = 73; uint256 private constant RECIPIENT_OFFSET = 45;
uint256 private constant BODY_OFFSET = 105; uint256 private constant BODY_OFFSET = 77;
/** /**
* @notice Returns formatted (packed) Hyperlane message with provided fields * @notice Returns formatted (packed) Hyperlane message with provided fields
@ -32,7 +32,7 @@ library Message {
*/ */
function formatMessage( function formatMessage(
uint8 _version, uint8 _version,
uint256 _nonce, uint32 _nonce,
uint32 _originDomain, uint32 _originDomain,
bytes32 _sender, bytes32 _sender,
uint32 _destinationDomain, uint32 _destinationDomain,
@ -74,8 +74,8 @@ library Message {
* @param _message ABI encoded Hyperlane message. * @param _message ABI encoded Hyperlane message.
* @return Nonce of `_message` * @return Nonce of `_message`
*/ */
function nonce(bytes calldata _message) internal pure returns (uint256) { function nonce(bytes calldata _message) internal pure returns (uint32) {
return uint256(bytes32(_message[NONCE_OFFSET:ORIGIN_OFFSET])); return uint32(bytes4(_message[NONCE_OFFSET:ORIGIN_OFFSET]));
} }
/** /**

@ -13,9 +13,9 @@ interface IMailbox {
function process(bytes calldata _metadata, bytes calldata _message) function process(bytes calldata _metadata, bytes calldata _message)
external; external;
function count() external view returns (uint256); function count() external view returns (uint32);
function root() external view returns (bytes32); function root() external view returns (bytes32);
function latestCheckpoint() external view returns (bytes32, uint256); function latestCheckpoint() external view returns (bytes32, uint32);
} }

@ -51,7 +51,7 @@ export const dispatchMessageAndReturnProof = async (
proof: { proof: {
branch: proof, branch: proof,
leaf: messageId, leaf: messageId,
index: nonce.toNumber(), index: nonce,
}, },
message, message,
}; };
@ -93,14 +93,14 @@ export async function dispatchMessageAndReturnMetadata(
const root = await mailbox.root(); const root = await mailbox.root();
const signatures = await signCheckpoint( const signatures = await signCheckpoint(
root, root,
index.toNumber(), index,
mailbox.address, mailbox.address,
orderedValidators, orderedValidators,
); );
const origin = utils.parseMessage(proofAndMessage.message).origin; const origin = utils.parseMessage(proofAndMessage.message).origin;
const metadata = utils.formatMultisigIsmMetadata({ const metadata = utils.formatMultisigIsmMetadata({
checkpointRoot: root, checkpointRoot: root,
checkpointIndex: index.toNumber(), checkpointIndex: index,
originMailbox: mailbox.address, originMailbox: mailbox.address,
proof: proofAndMessage.proof.branch, proof: proofAndMessage.proof.branch,
signatures, signatures,

@ -20,7 +20,7 @@ const chainSummary = async <Chain extends ChainName>(
) => { ) => {
const coreContracts = core.getContracts(chain); const coreContracts = core.getContracts(chain);
const mailbox = coreContracts.mailbox.contract; const mailbox = coreContracts.mailbox.contract;
const dispatched = (await mailbox.count()).toNumber(); const dispatched = await mailbox.count();
// TODO: Allow processed messages to be filtered by // TODO: Allow processed messages to be filtered by
// origin, possibly sender and recipient. // origin, possibly sender and recipient.
const processFilter = mailbox.filters.Process(); const processFilter = mailbox.filters.Process();
@ -95,7 +95,7 @@ task('kathy', 'Dispatches random hyperlane messages')
console.log( console.log(
`send to ${recipient.address} on ${remote} via mailbox ${ `send to ${recipient.address} on ${remote} via mailbox ${
mailbox.address mailbox.address
} with nonce ${(await mailbox.count()).toNumber() - 1}`, } on ${local} with nonce ${(await mailbox.count()) - 1}`,
); );
console.log(await chainSummary(core, local)); console.log(await chainSummary(core, local));
await sleep(timeout); await sleep(timeout);

@ -48,16 +48,18 @@ async function main() {
`${environment}.json`, `${environment}.json`,
serializeContracts(deployer.deployedContracts), serializeContracts(deployer.deployedContracts),
); );
const existingVerificationInputs = readJSON( const verificationDir = getCoreVerificationDirectory(environment);
getCoreVerificationDirectory(environment), const verificationFile = 'verification.json';
'verification.json', let existingVerificationInputs = [];
); try {
writeJSON( existingVerificationInputs = readJSON(verificationDir, verificationFile);
getCoreVerificationDirectory(environment), } finally {
'verification.json', writeJSON(
deployer.mergeWithExistingVerificationInputs(existingVerificationInputs), getCoreVerificationDirectory(environment),
); 'verification.json',
deployer.mergeWithExistingVerificationInputs(existingVerificationInputs),
);
}
deployer.writeRustConfigs(environment, getCoreRustDirectory(environment)); deployer.writeRustConfigs(environment, getCoreRustDirectory(environment));
} }

@ -255,25 +255,27 @@ export type RustCoreAddresses = {
multisigIsm: types.Address; multisigIsm: types.Address;
}; };
export type RustChainConfig = { export type RustChainSetup = {
name: ChainName; name: ChainName;
domain: string; domain: string;
finalityBlocks: string;
addresses: RustCoreAddresses; addresses: RustCoreAddresses;
rpcStyle: 'ethereum'; rpcStyle: 'ethereum';
finalityBlocks: string;
connection: RustConnection; connection: RustConnection;
tracing: {
level: string;
fmt: 'json';
};
db: string;
index?: { from: string }; index?: { from: string };
signer?: RustSigner;
}; };
export type RustConfig<Chain extends ChainName> = { export type RustConfig<Chain extends ChainName> = {
environment: DeployEnvironment; environment: DeployEnvironment;
chains: Partial<ChainMap<Chain, RustChainConfig>>; chains: Partial<ChainMap<Chain, RustChainSetup>>;
// TODO: Separate DBs for each chain (fold into RustChainSetup)
db: string;
// TODO: Fold this into RustChainSetup
signers?: Partial<ChainMap<Chain, RustSigner>>;
tracing: {
level: string;
fmt: 'json';
};
}; };
// Helper to get chain-specific agent configurations // Helper to get chain-specific agent configurations

@ -1,4 +1,4 @@
export { AgentConfig, RustConfig, RustChainConfig } from './agent'; export { AgentConfig, RustConfig, RustChainSetup } from './agent';
export { CoreEnvironmentConfig, DeployEnvironment } from './environment'; export { CoreEnvironmentConfig, DeployEnvironment } from './environment';
export { HelloWorldConfig } from './helloworld'; export { HelloWorldConfig } from './helloworld';
export { InfrastructureConfig } from './infrastructure'; export { InfrastructureConfig } from './infrastructure';

@ -5,7 +5,7 @@ import {
objMap, objMap,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { DeployEnvironment, RustChainConfig, RustConfig } from '../config'; import { DeployEnvironment, RustChainSetup, RustConfig } from '../config';
import { ConnectionType } from '../config/agent'; import { ConnectionType } from '../config/agent';
import { writeJSON } from '../utils/utils'; import { writeJSON } from '../utils/utils';
@ -16,6 +16,12 @@ export class HyperlaneCoreInfraDeployer<
const rustConfig: RustConfig<Chain> = { const rustConfig: RustConfig<Chain> = {
environment, environment,
chains: {}, chains: {},
signers: {},
db: 'db_path',
tracing: {
level: 'debug',
fmt: 'json',
},
}; };
objMap(this.configMap, (chain) => { objMap(this.configMap, (chain) => {
const contracts = this.deployedContracts[chain]; const contracts = this.deployedContracts[chain];
@ -30,7 +36,7 @@ export class HyperlaneCoreInfraDeployer<
return; return;
} }
const chainConfig: RustChainConfig = { const chainConfig: RustChainSetup = {
name: chain, name: chain,
domain: metadata.id.toString(), domain: metadata.id.toString(),
addresses: { addresses: {
@ -44,11 +50,6 @@ export class HyperlaneCoreInfraDeployer<
type: ConnectionType.Http, type: ConnectionType.Http,
url: '', url: '',
}, },
tracing: {
level: 'debug',
fmt: 'json',
},
db: 'db_path',
}; };
const startingBlockNumber = this.startingBlockNumbers[chain]; const startingBlockNumber = this.startingBlockNumbers[chain];
@ -58,6 +59,6 @@ export class HyperlaneCoreInfraDeployer<
} }
rustConfig.chains[chain] = chainConfig; rustConfig.chains[chain] = chainConfig;
}); });
writeJSON(directory, 'rust_config.json', rustConfig); writeJSON(directory, `${environment}_config.json`, rustConfig);
} }
} }

@ -164,14 +164,14 @@ export function writeJSON(directory: string, filename: string, obj: any) {
export function readJSON(directory: string, filename: string) { export function readJSON(directory: string, filename: string) {
if (!fs.existsSync(directory)) { if (!fs.existsSync(directory)) {
throw Error("directory doesn't exist"); throw Error(`directory doesn't exist: ${directory}`);
} }
return readJSONAtPath(path.join(directory, filename)); return readJSONAtPath(path.join(directory, filename));
} }
export function readJSONAtPath(filepath: string) { export function readJSONAtPath(filepath: string) {
if (!fs.existsSync(filepath)) { if (!fs.existsSync(filepath)) {
throw Error("file doesn't exist"); throw Error(`file doesn't exist: ${filepath}`);
} }
return JSON.parse(fs.readFileSync(filepath, 'utf8')); return JSON.parse(fs.readFileSync(filepath, 'utf8'));
} }

@ -9,42 +9,42 @@
}, },
"mailbox": { "mailbox": {
"kind": "UpgradeBeacon", "kind": "UpgradeBeacon",
"proxy": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", "proxy": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
"implementation": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", "implementation": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
"beacon": "0x0165878A594ca255338adfa4d48449f69242Eb8F" "beacon": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788"
}, },
"multisigIsm": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" "multisigIsm": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9"
}, },
"test2": { "test2": {
"upgradeBeaconController": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", "upgradeBeaconController": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
"interchainGasPaymaster": { "interchainGasPaymaster": {
"kind": "UpgradeBeacon",
"proxy": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
"implementation": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
"beacon": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788"
},
"mailbox": {
"kind": "UpgradeBeacon", "kind": "UpgradeBeacon",
"proxy": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", "proxy": "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
"implementation": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", "implementation": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
"beacon": "0x9A676e781A523b5d0C0e43731313A708CB607508" "beacon": "0x9A676e781A523b5d0C0e43731313A708CB607508"
}, },
"multisigIsm": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" "mailbox": {
"kind": "UpgradeBeacon",
"proxy": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44",
"implementation": "0x59b670e9fA9D0A427751Af201D676719a970857b",
"beacon": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1"
},
"multisigIsm": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1"
}, },
"test3": { "test3": {
"upgradeBeaconController": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1", "upgradeBeaconController": "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f",
"interchainGasPaymaster": { "interchainGasPaymaster": {
"kind": "UpgradeBeacon", "kind": "UpgradeBeacon",
"proxy": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", "proxy": "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
"implementation": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", "implementation": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319",
"beacon": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" "beacon": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F"
}, },
"mailbox": { "mailbox": {
"kind": "UpgradeBeacon", "kind": "UpgradeBeacon",
"proxy": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", "proxy": "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8",
"implementation": "0x59b670e9fA9D0A427751Af201D676719a970857b", "implementation": "0x9E545E3C0baAB3E08CdfD552C960A1050f373042",
"beacon": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1" "beacon": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9"
}, },
"multisigIsm": "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d" "multisigIsm": "0xc5a5C42992dECbae36851359345FE25997F5C42d"
} }
} }

@ -131,7 +131,7 @@ export const formatMessage = (
recipientAddr = addressToBytes32(recipientAddr); recipientAddr = addressToBytes32(recipientAddr);
return ethers.utils.solidityPack( return ethers.utils.solidityPack(
['uint8', 'uint256', 'uint32', 'bytes32', 'uint32', 'bytes32', 'bytes'], ['uint8', 'uint32', 'uint32', 'bytes32', 'uint32', 'bytes32', 'bytes'],
[ [
version, version,
nonce, nonce,
@ -157,17 +157,15 @@ export function messageId(message: HexString): string {
export function parseMessage(message: string): ParsedMessage { export function parseMessage(message: string): ParsedMessage {
const VERSION_OFFSET = 0; const VERSION_OFFSET = 0;
const NONCE_OFFSET = 1; const NONCE_OFFSET = 1;
const ORIGIN_OFFSET = 33; const ORIGIN_OFFSET = 5;
const SENDER_OFFSET = 37; const SENDER_OFFSET = 9;
const DESTINATION_OFFSET = 69; const DESTINATION_OFFSET = 41;
const RECIPIENT_OFFSET = 73; const RECIPIENT_OFFSET = 45;
const BODY_OFFSET = 105; const BODY_OFFSET = 77;
const buf = Buffer.from(utils.arrayify(message)); const buf = Buffer.from(utils.arrayify(message));
const version = buf.readUint8(VERSION_OFFSET); const version = buf.readUint8(VERSION_OFFSET);
const nonce = BigNumber.from( const nonce = buf.readUInt32BE(NONCE_OFFSET);
utils.hexlify(buf.slice(NONCE_OFFSET, ORIGIN_OFFSET)),
).toNumber();
const origin = buf.readUInt32BE(ORIGIN_OFFSET); const origin = buf.readUInt32BE(ORIGIN_OFFSET);
const sender = utils.hexlify(buf.slice(SENDER_OFFSET, DESTINATION_OFFSET)); const sender = utils.hexlify(buf.slice(SENDER_OFFSET, DESTINATION_OFFSET));
const destination = buf.readUInt32BE(DESTINATION_OFFSET); const destination = buf.readUInt32BE(DESTINATION_OFFSET);

Loading…
Cancel
Save