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. 125
      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. 215
      rust/abacus-base/src/settings/chains.rs
  12. 311
      rust/abacus-base/src/settings/mod.rs
  13. 55
      rust/abacus-core/src/chain.rs
  14. 220
      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. 18
      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. 57
      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. 12
      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::{collections::HashMap, sync::Arc};
use abacus_core::MultisigIsm;
use async_trait::async_trait;
use eyre::{Report, Result};
use futures_util::future::select_all;
@ -10,39 +11,25 @@ use tracing::instrument::Instrumented;
use tracing::{info_span, Instrument};
use abacus_core::db::DB;
use abacus_core::InboxValidatorManager;
use crate::{
cancel_task,
metrics::CoreMetrics,
settings::{IndexSettings, Settings},
CachingInbox, CachingInterchainGasPaymaster, CachingOutbox,
cancel_task, metrics::CoreMetrics, settings::Settings, CachingInterchainGasPaymaster,
CachingMailbox,
};
/// 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
#[derive(Debug)]
pub struct AbacusAgentCore {
/// A boxed Outbox
pub outbox: CachingOutbox,
/// A boxed InterchainGasPaymaster
pub interchain_gas_paymaster: Option<CachingInterchainGasPaymaster>,
/// A map of Inbox contracts by name
pub inboxes: HashMap<String, InboxContracts>,
/// A map of mailbox contracts by chain name
pub mailboxes: HashMap<String, CachingMailbox>,
/// A map of interchain gas paymaster contracts by chain name
pub interchain_gas_paymasters: HashMap<String, CachingInterchainGasPaymaster>,
/// A map of interchain gas paymaster contracts by chain name
pub multisig_isms: HashMap<String, Arc<dyn MultisigIsm>>,
/// A persistent KV Store (currently implemented as rocksdb)
pub db: DB,
/// Prometheus metrics
pub metrics: Arc<CoreMetrics>,
/// The height at which to start indexing the Outbox
pub indexer: IndexSettings,
/// Settings this agent was created with
pub settings: Settings,
}
@ -86,17 +73,14 @@ pub trait Agent: BaseAgent {
/// Return a handle to the DB
fn db(&self) -> &DB;
/// Return a reference to an Outbox contract
fn outbox(&self) -> &CachingOutbox;
/// Return a reference to a Mailbox contract
fn mailbox(&self, chain_name: &str) -> Option<&CachingMailbox>;
/// Return a reference to an InterchainGasPaymaster contract
fn interchain_gas_paymaster(&self) -> Option<&CachingInterchainGasPaymaster>;
/// Get a reference to the inboxes map
fn inboxes(&self) -> &HashMap<String, InboxContracts>;
fn interchain_gas_paymaster(&self, chain_name: &str) -> Option<&CachingInterchainGasPaymaster>;
/// Get a reference to an inbox's contracts by its name
fn inbox_by_name(&self, name: &str) -> Option<&InboxContracts>;
/// Return a reference to a Multisig Ism contract
fn multisig_ism(&self, chain_name: &str) -> Option<&Arc<dyn MultisigIsm>>;
}
#[async_trait]
@ -108,20 +92,16 @@ where
&self.as_ref().db
}
fn outbox(&self) -> &CachingOutbox {
&self.as_ref().outbox
}
fn interchain_gas_paymaster(&self) -> Option<&CachingInterchainGasPaymaster> {
self.as_ref().interchain_gas_paymaster.as_ref()
fn mailbox(&self, chain_name: &str) -> Option<&CachingMailbox> {
self.as_ref().mailboxes.get(chain_name)
}
fn inboxes(&self) -> &HashMap<String, InboxContracts> {
&self.as_ref().inboxes
fn interchain_gas_paymaster(&self, chain_name: &str) -> Option<&CachingInterchainGasPaymaster> {
self.as_ref().interchain_gas_paymasters.get(chain_name)
}
fn inbox_by_name(&self, name: &str) -> Option<&InboxContracts> {
self.inboxes().get(name)
fn multisig_ism(&self, chain_name: &str) -> Option<&Arc<dyn MultisigIsm>> {
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
/// 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
/// previous messages (None case).
pub fn validate_message_continuity(
latest_message_leaf_index: Option<u32>,
sorted_messages: &[&RawCommittedMessage],
latest_message_nonce: Option<u32>,
sorted_messages: &[&AbacusMessage],
) -> ListValidity {
if sorted_messages.is_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
// 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
.iter()
.any(|message| last_seen == message.leaf_index - 1);
.any(|&message| last_seen == message.nonce - 1);
if !has_desired_message {
return ListValidity::InvalidContinuation;
}
@ -31,7 +31,7 @@ pub fn validate_message_continuity(
// Ensure no gaps in new batch of leaves
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;
}
}

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

@ -28,8 +28,8 @@ pub struct ContractSyncMetrics {
/// - `chain`: Chain the indexer is collecting data from.
pub missed_events: IntCounterVec,
/// See `last_known_message_leaf_index` in CoreMetrics.
pub message_leaf_index: IntGaugeVec,
/// See `last_known_message_nonce` in CoreMetrics.
pub message_nonce: IntGaugeVec,
}
impl ContractSyncMetrics {
@ -59,13 +59,13 @@ impl ContractSyncMetrics {
)
.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 {
indexed_height,
stored_events,
missed_events,
message_leaf_index,
message_nonce,
}
}
}

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

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

@ -21,13 +21,9 @@ pub use agent::*;
#[macro_use]
pub mod macros;
/// outbox type
mod outbox;
pub use outbox::*;
/// inbox type
mod inbox;
pub use inbox::*;
/// mailbox type
mod mailbox;
pub use mailbox::*;
mod 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_events: IntCounterVec,
last_known_message_leaf_index: IntGaugeVec,
last_known_message_nonce: IntGaugeVec,
validator_checkpoint_index: IntGaugeVec,
submitter_queue_length: IntGaugeVec,
submitter_queue_duration_histogram: HistogramVec,
@ -103,9 +103,9 @@ impl CoreMetrics {
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!(
namespaced!("last_known_message_leaf_index"),
namespaced!("last_known_message_nonce"),
"Last known message leaf index",
const_labels_ref
),
@ -186,7 +186,7 @@ impl CoreMetrics {
span_durations,
span_events,
last_known_message_leaf_index,
last_known_message_nonce,
validator_checkpoint_index,
submitter_queue_length,
@ -315,8 +315,8 @@ impl CoreMetrics {
/// has gotten to but not attempted to send it.
/// - `message_processed`: When a leaf index was processed as part of the
/// MessageProcessor loop.
pub fn last_known_message_leaf_index(&self) -> IntGaugeVec {
self.last_known_message_leaf_index.clone()
pub fn last_known_message_nonce(&self) -> IntGaugeVec {
self.last_known_message_nonce.clone()
}
/// Gauge for reporting the most recent validator checkpoint index
@ -373,7 +373,7 @@ impl CoreMetrics {
/// lifetime.
///
/// 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
/// delivered. Since deliveries can happen out-of-index-order, we
/// 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 abacus_core::{
AbacusAbi, ContractLocator, Inbox, InboxValidatorManager, InterchainGasPaymaster, Outbox,
Signers,
AbacusAbi, ContractLocator, InterchainGasPaymaster, Mailbox, MultisigIsm, Signers,
};
use abacus_ethereum::{
Connection, EthereumInboxAbi, EthereumInterchainGasPaymasterAbi, EthereumOutboxAbi,
InboxBuilder, InboxValidatorManagerBuilder, InterchainGasPaymasterBuilder,
MakeableWithProvider, OutboxBuilder,
Connection, EthereumInterchainGasPaymasterAbi, EthereumMailboxAbi, EthereumMultisigIsmAbi,
InterchainGasPaymasterBuilder, MailboxBuilder, MakeableWithProvider, MultisigIsmBuilder,
};
use ethers_prometheus::middleware::{
ChainInfo, ContractInfo, PrometheusMiddlewareConf, WalletInfo,
@ -54,28 +52,49 @@ pub struct GelatoConf {
/// Addresses for outbox chain contracts
#[derive(Clone, Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct OutboxAddresses {
/// Address of the Outbox contract
pub outbox: String,
pub struct CoreContractAddresses {
/// Address of the mailbox contract
pub mailbox: String,
/// Address of the MultisigIsm contract
pub multisig_ism: String,
/// Address of the InterchainGasPaymaster contract
pub interchain_gas_paymaster: Option<String>,
pub interchain_gas_paymaster: String,
}
/// Addresses for inbox chain contracts
#[derive(Clone, Debug, Deserialize, Default)]
/// Outbox indexing settings
#[derive(Debug, Deserialize, Default, Clone)]
#[serde(rename_all = "camelCase")]
pub struct InboxAddresses {
/// Address of the Inbox contract
pub inbox: String,
/// Address of the InboxValidatorManager contract
pub validator_manager: String,
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)
}
}
/// 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.
#[derive(Clone, Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct ChainSetup<T> {
pub struct ChainSetup {
/// Chain name
pub name: String,
/// Chain domain identifier
@ -83,24 +102,24 @@ pub struct ChainSetup<T> {
/// Number of blocks until finality
pub finality_blocks: String,
/// Addresses of contracts on the chain
pub addresses: T,
pub addresses: CoreContractAddresses,
/// The chain connection details
#[serde(flatten)]
pub chain: ChainConf,
/// How transactions to this chain are submitted.
#[serde(default)]
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
/// add all contract addresses but will not override any set explicitly.
/// Use `metrics_conf()` to get the metrics.
#[serde(default)]
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
pub fn finality_blocks(&self) -> u32 {
self.finality_blocks
@ -109,32 +128,20 @@ impl<T> ChainSetup<T> {
}
}
impl ChainSetup<OutboxAddresses> {
/// Try to convert the chain setting into an Outbox contract
pub async fn try_into_outbox(
impl ChainSetup {
/// Try to convert the chain setting into a Mailbox contract
pub async fn try_into_mailbox(
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn Outbox>> {
match &self.chain {
ChainConf::Ethereum(conf) => Ok(OutboxBuilder {}
.make_with_connection(
conf.clone(),
&ContractLocator {
chain_name: self.name.clone(),
domain: self.domain.parse().expect("invalid uint"),
address: self
.addresses
.outbox
.parse::<ethers::types::Address>()?
.into(),
},
) -> eyre::Result<Box<dyn Mailbox>> {
self.try_into_contract(
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), self.metrics_conf())),
metrics,
MailboxBuilder {},
self.addresses.mailbox.clone(),
)
.await?),
}
.await
}
/// Try to convert the chain setting into an InterchainGasPaymaster contract
@ -142,109 +149,48 @@ impl ChainSetup<OutboxAddresses> {
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
) -> eyre::Result<Option<Box<dyn InterchainGasPaymaster>>> {
let paymaster_address = if let Some(address) = &self.addresses.interchain_gas_paymaster {
address
} else {
return Ok(None);
};
match &self.chain {
ChainConf::Ethereum(conf) => Ok(Some(
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(),
},
) -> eyre::Result<Box<dyn InterchainGasPaymaster>> {
self.try_into_contract(
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), self.metrics_conf())),
metrics,
InterchainGasPaymasterBuilder {},
self.addresses.interchain_gas_paymaster.clone(),
)
.await?,
)),
}
}
/// Get a clone of the metrics conf with correctly configured contract
/// information.
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()),
});
.await
}
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(
/// Try to convert the chain setting into a Multisig Ism contract
pub async fn try_into_multisig_ism(
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn Inbox>> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
match &self.chain {
ChainConf::Ethereum(conf) => Ok(InboxBuilder {}
.make_with_connection(
conf.clone(),
&ContractLocator {
chain_name: self.name.clone(),
domain: self.domain.parse().expect("invalid uint"),
address: self
.addresses
.inbox
.parse::<ethers::types::Address>()?
.into(),
},
) -> eyre::Result<Box<dyn MultisigIsm>> {
self.try_into_contract(
signer,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), metrics_conf)),
metrics,
MultisigIsmBuilder {},
self.addresses.multisig_ism.clone(),
)
.await?),
}
.await
}
/// Try to convert the chain setting into an InboxValidatorManager contract
pub async fn try_into_inbox_validator_manager(
/// Try to convert the chain setting into a contract
pub async fn try_into_contract<T: MakeableWithProvider>(
&self,
signer: Option<Signers>,
metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn InboxValidatorManager>> {
let inbox_address = self.addresses.inbox.parse::<ethers::types::Address>()?;
builder: T,
address: String,
) -> eyre::Result<T::Output> {
let metrics_conf = self.metrics_conf(metrics.agent_name(), &signer);
match &self.chain {
ChainConf::Ethereum(conf) => Ok(InboxValidatorManagerBuilder { inbox_address }
ChainConf::Ethereum(conf) => Ok(builder
.make_with_connection(
conf.clone(),
&ContractLocator {
chain_name: self.name.clone(),
domain: self.domain.parse().expect("invalid uint"),
address: self
.addresses
.validator_manager
.parse::<ethers::types::Address>()?
.into(),
address: address.parse::<ethers::types::Address>()?.into(),
},
signer,
Some(|| metrics.json_rpc_client_metrics()),
@ -276,16 +222,23 @@ impl ChainSetup<InboxAddresses> {
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 {
name: Some("inbox".into()),
functions: EthereumInboxAbi::fn_map_owned(),
name: Some("igp".into()),
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 {
name: Some("ivm".into()),
functions: EthereumOutboxAbi::fn_map_owned(),
name: Some("msm".into()),
functions: EthereumMultisigIsmAbi::fn_map_owned(),
});
}
cfg

@ -89,16 +89,13 @@ use tracing::instrument;
use abacus_core::{
db::{AbacusDB, DB},
utils::HexString,
AbacusContract, ContractLocator, InboxValidatorManager, InterchainGasPaymasterIndexer,
OutboxIndexer, Signers,
InterchainGasPaymasterIndexer, MailboxIndexer, MultisigIsm, Signers,
};
use abacus_ethereum::{
InterchainGasPaymasterIndexerBuilder, MakeableWithProvider, OutboxIndexerBuilder,
};
pub use chains::{ChainConf, ChainSetup, InboxAddresses, OutboxAddresses};
use abacus_ethereum::{InterchainGasPaymasterIndexerBuilder, MailboxIndexerBuilder};
pub use chains::{ChainConf, ChainSetup, CoreContractAddresses};
use crate::{settings::trace::TracingConfig, CachingInterchainGasPaymaster};
use crate::{AbacusAgentCore, CachingInbox, CachingOutbox, CoreMetrics, InboxContracts};
use crate::{AbacusAgentCore, CachingMailbox, CoreMetrics};
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
/// follows:
///
@ -223,13 +191,8 @@ pub struct Settings {
pub db: String,
/// Port to listen for prometheus scrape requests
pub metrics: Option<String>,
/// Settings for the outbox indexer
#[serde(default)]
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>>,
/// Configuration for contracts on each chain
pub chains: HashMap<String, ChainSetup>,
/// The tracing configuration
pub tracing: TracingConfig,
/// Transaction signers
@ -245,9 +208,7 @@ impl Settings {
Self {
db: self.db.clone(),
metrics: self.metrics.clone(),
index: self.index.clone(),
outbox: self.outbox.clone(),
inboxes: self.inboxes.clone(),
chains: self.chains.clone(),
tracing: self.tracing.clone(),
signers: self.signers.clone(),
gelato: self.gelato.clone(),
@ -261,180 +222,155 @@ impl Settings {
self.signers.get(name)?.try_into_signer().await.ok()
}
/// Try to get a map of inbox name -> inbox contracts
pub async fn try_inbox_contracts(
/// Try to get a map of chain name -> mailbox contract
pub async fn try_into_mailboxes(
&self,
db: DB,
metrics: &CoreMetrics,
) -> eyre::Result<HashMap<String, InboxContracts>> {
chain_names: &[&str],
) -> eyre::Result<HashMap<String, CachingMailbox>> {
let mut result = HashMap::new();
for (k, v) in self.inboxes.iter().filter(|(_, v)| {
!v.disabled
.as_ref()
.and_then(|d| d.parse::<bool>().ok())
.unwrap_or(false)
}) {
if k != &v.name {
bail!(
"Inbox key does not match inbox name:\n key: {} name: {}",
k,
v.name
);
for &chain_name in chain_names {
if let Some(x) = self.chains.get(chain_name) {
let mailbox = self.try_caching_mailbox(x, db.clone(), metrics).await?;
result.insert(chain_name.into(), mailbox);
} else {
bail!("No chain setup found for {}", chain_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)
}
/// Try to get a CachingInbox
async fn try_caching_inbox(
/// Try to get a map of chain name -> interchain gas paymaster contract
pub async fn try_into_interchain_gas_paymasters(
&self,
chain_setup: &ChainSetup<InboxAddresses>,
db: DB,
metrics: &CoreMetrics,
) -> eyre::Result<CachingInbox> {
let signer = self.get_signer(&chain_setup.name).await;
let inbox = chain_setup.try_into_inbox(signer, metrics).await?;
let abacus_db = AbacusDB::new(inbox.chain_name(), db);
Ok(CachingInbox::new(inbox.into(), abacus_db))
chain_names: &[&str],
) -> eyre::Result<HashMap<String, CachingInterchainGasPaymaster>> {
let mut result = HashMap::new();
for &chain_name in chain_names {
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
async fn try_inbox_validator_manager(
/// Try to get a map of chain name -> multisig ism contract
pub async fn try_into_multisig_isms(
&self,
chain_setup: &ChainSetup<InboxAddresses>,
db: DB,
metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn InboxValidatorManager>> {
let signer = self.get_signer(&chain_setup.name).await;
chain_setup
.try_into_inbox_validator_manager(signer, metrics)
.await
chain_names: &[&str],
) -> eyre::Result<HashMap<String, Arc<dyn MultisigIsm>>> {
let mut result: HashMap<String, Arc<dyn MultisigIsm>> = HashMap::new();
for &chain_name in chain_names {
if let Some(x) = self.chains.get(chain_name) {
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
pub async fn try_caching_outbox(
/// Try to get a CachingMailbox
async fn try_caching_mailbox(
&self,
chain_setup: &ChainSetup,
db: DB,
metrics: &CoreMetrics,
) -> eyre::Result<CachingOutbox> {
let signer = self.get_signer(&self.outbox.name).await;
let outbox = self.outbox.try_into_outbox(signer, metrics).await?;
let indexer = self.try_outbox_indexer(metrics).await?;
let abacus_db = AbacusDB::new(outbox.chain_name(), db);
Ok(CachingOutbox::new(outbox.into(), abacus_db, indexer.into()))
) -> eyre::Result<CachingMailbox> {
let signer = self.get_signer(&chain_setup.name).await;
let mailbox = chain_setup.try_into_mailbox(signer, metrics).await?;
let indexer = self.try_mailbox_indexer(metrics, chain_setup).await?;
let abacus_db = AbacusDB::new(&chain_setup.name, db);
Ok(CachingMailbox::new(
mailbox.into(),
abacus_db,
indexer.into(),
))
}
/// Try to get a CachingInterchainGasPaymaster
pub async fn try_caching_interchain_gas_paymaster(
async fn try_caching_interchain_gas_paymaster(
&self,
chain_setup: &ChainSetup,
db: DB,
metrics: &CoreMetrics,
) -> eyre::Result<Option<CachingInterchainGasPaymaster>> {
let signer = self.get_signer(&self.outbox.name).await;
match self
.outbox
) -> eyre::Result<CachingInterchainGasPaymaster> {
let signer = self.get_signer(&chain_setup.name).await;
let interchain_gas_paymaster = chain_setup
.try_into_interchain_gas_paymaster(signer, metrics)
.await?
{
Some(paymaster) => {
let indexer = self.try_interchain_gas_paymaster_indexer(metrics).await?;
let abacus_db = AbacusDB::new(paymaster.chain_name(), db);
Ok(Some(CachingInterchainGasPaymaster::new(
paymaster.into(),
.await?;
let indexer = self
.try_interchain_gas_paymaster_indexer(metrics, chain_setup)
.await?;
let abacus_db = AbacusDB::new(&chain_setup.name, db);
Ok(CachingInterchainGasPaymaster::new(
interchain_gas_paymaster.into(),
abacus_db,
indexer.into(),
)))
}
None => Ok(None),
}
))
}
/// Try to get an indexer object for a given outbox
pub async fn try_outbox_indexer_from_config(
/// Try to get a MultisigIsm
async fn try_multisig_ism(
&self,
chain_setup: &ChainSetup,
_db: DB,
metrics: &CoreMetrics,
outbox: &ChainSetup<OutboxAddresses>,
) -> eyre::Result<Box<dyn OutboxIndexer>> {
match &outbox.chain {
ChainConf::Ethereum(conn) => Ok(OutboxIndexerBuilder {
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?),
}
) -> eyre::Result<Box<dyn MultisigIsm>> {
let signer = self.get_signer(&chain_setup.name).await;
let multisig_ism = chain_setup.try_into_multisig_ism(signer, metrics).await?;
Ok(multisig_ism)
}
/// Try to get an indexer object for the outbox
pub async fn try_outbox_indexer(
/// Try to get an indexer object for a given mailbox
pub async fn try_mailbox_indexer(
&self,
metrics: &CoreMetrics,
) -> eyre::Result<Box<dyn OutboxIndexer>> {
self.try_outbox_indexer_from_config(metrics, &self.outbox)
chain_setup: &ChainSetup,
) -> 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
}
/// Try to get an indexer object for an interchain gas paymaster.
/// This function is only expected to be called when it's already been
/// confirmed that the interchain gas paymaster address was provided in
/// settings.
pub async fn try_interchain_gas_paymaster_indexer(
/// Try to get an indexer object for a given interchain gas paymaster
async fn try_interchain_gas_paymaster_indexer(
&self,
metrics: &CoreMetrics,
chain_setup: &ChainSetup,
) -> eyre::Result<Box<dyn InterchainGasPaymasterIndexer>> {
match &self.outbox.chain {
ChainConf::Ethereum(conn) => Ok(InterchainGasPaymasterIndexerBuilder {
outbox_address: self
.outbox
chain_setup
.try_into_contract(
self.get_signer(&chain_setup.name).await,
metrics,
InterchainGasPaymasterIndexerBuilder {
mailbox_address: chain_setup
.addresses
.outbox
.mailbox
.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
.interchain_gas_paymaster
.as_ref()
.expect("interchain_gas_paymaster not provided")
.parse::<ethers::types::Address>()?
.into(),
finality_blocks: chain_setup.finality_blocks(),
},
self.get_signer(&self.outbox.name).await,
Some(|| metrics.json_rpc_client_metrics()),
Some((metrics.provider_metrics(), self.outbox.metrics_conf())),
chain_setup.addresses.interchain_gas_paymaster.clone(),
)
.await?),
}
.await
}
/// 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(
&self,
metrics: Arc<CoreMetrics>,
parse_inboxes: bool,
chain_names: Option<Vec<&str>>,
) -> eyre::Result<AbacusAgentCore> {
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 {
self.try_inbox_contracts(db.clone(), &metrics).await?
} else {
HashMap::new()
// If not provided, default to using every chain listed in self.chains.
let chain_names = match chain_names {
Some(x) => x,
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 {
outbox,
inboxes: inbox_contracts,
interchain_gas_paymaster,
mailboxes,
interchain_gas_paymasters,
multisig_isms,
db,
metrics,
indexer: self.index.clone(),
settings: self.clone(),
})
}

@ -191,7 +191,6 @@ pub fn domain_id_from_name(name: &'static str) -> Option<u32> {
mod tests {
use abacus_base::Settings;
use config::{Config, File, FileFormat};
use num_traits::identities::Zero;
use std::collections::BTreeSet;
use std::fs::read_to_string;
use std::path::Path;
@ -216,9 +215,7 @@ mod tests {
const BLACKLISTED_DIRS: &[&str] = &[
// Ignore only-local names of fake chains used by
// e.g. test suites.
"test/test1_config.json",
"test/test2_config.json",
"test/test3_config.json",
"test/test_config.json",
];
fn is_blacklisted(path: &Path) -> bool {
@ -280,35 +277,11 @@ mod tests {
.collect()
}
fn outbox_chain_names() -> BTreeSet<String> {
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> {
fn chain_name_domain_records() -> BTreeSet<ChainCoordinate> {
abacus_settings()
.iter()
.flat_map(|x: &Settings| {
x.inboxes.iter().map(|(_, v)| ChainCoordinate {
x.chains.iter().map(|(_, v)| ChainCoordinate {
name: v.name.clone(),
domain: v.domain.parse().unwrap(),
})
@ -318,24 +291,6 @@ mod tests {
#[test]
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
// we have entries for all of the Gelato contract
// addresses we need hardcoded in the binary for now.
@ -345,8 +300,8 @@ mod tests {
// by the macro `domain_and_chain` is complete
// and in agreement with our on-disk json-based
// configuration data.
for ChainCoordinate { name, domain } in inbox_coords.iter().chain(outbox_coords.iter()) {
let chain_coords = chain_name_domain_records();
for ChainCoordinate { name, domain } in chain_coords.iter() {
assert_eq!(
AbacusDomain::try_from(domain.to_owned())
.unwrap()

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

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

@ -24,8 +24,10 @@ pub mod output_functions {
use super::*;
/// Output proof to /vector/message.json
pub fn output_message_and_leaf() {
pub fn output_message() {
let abacus_message = AbacusMessage {
nonce: 0,
version: 0,
origin: 1000,
sender: H256::from(
H160::from_str("0x1111111111111111111111111111111111111111").unwrap(),
@ -38,12 +40,14 @@ pub mod output_functions {
};
let message_json = json!({
"nonce": abacus_message.nonce,
"version": abacus_message.version,
"origin": abacus_message.origin,
"sender": abacus_message.sender,
"destination": abacus_message.destination,
"recipient": abacus_message.recipient,
"body": abacus_message.body,
"messageHash": abacus_message.to_leaf(0),
"id": abacus_message.id(),
});
let json = json!([message_json]).to_string();
@ -64,7 +68,7 @@ pub mod output_functions {
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(
"0xd89959d277019eee21f1c3c270a125964d63b71876880724d287fbb8b8de55f1"
.parse()
@ -97,11 +101,14 @@ pub mod output_functions {
/// Outputs domain hash test cases in /vector/domainHash.json
pub fn output_domain_hashes() {
let mailbox =
H256::from(H160::from_str("0x2222222222222222222222222222222222222222").unwrap());
let test_cases: Vec<Value> = (1..=3)
.map(|i| {
json!({
"outboxDomain": i,
"expectedDomainHash": domain_hash(i)
"domain": i,
"mailbox": mailbox,
"expectedDomainHash": domain_hash(mailbox, i)
})
})
.collect();
@ -121,6 +128,8 @@ pub mod output_functions {
/// Outputs signed checkpoint test cases in /vector/signedCheckpoint.json
pub fn output_signed_checkpoints() {
let mailbox =
H256::from(H160::from_str("0x2222222222222222222222222222222222222222").unwrap());
let t = async {
let signer: ethers::signers::LocalWallet =
"1111111111111111111111111111111111111111111111111111111111111111"
@ -132,7 +141,8 @@ pub mod output_functions {
// test suite
for i in 1..=3 {
let signed_checkpoint = Checkpoint {
outbox_domain: 1000,
mailbox_address: mailbox,
mailbox_domain: 1000,
root: H256::repeat_byte(i + 1),
index: i as u32,
}
@ -141,7 +151,8 @@ pub mod output_functions {
.expect("!sign_with");
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,
"index": signed_checkpoint.checkpoint.index,
"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
//! checkpoints/messages emitted within a certain block range by calling out to
//! a chain-specific library and provider (e.g. ethers::provider). A
//! chain-specific outbox or inbox should implement one or both of the Indexer
//! traits (CommonIndexer or OutboxIndexer) to provide an common interface which
//! chain-specific mailbox or inbox should implement one or both of the Indexer
//! traits (CommonIndexer or MailboxIndexer) to provide an common interface which
//! other entities can retrieve this chain-specific info.
use std::fmt::Debug;
@ -13,7 +13,7 @@ use async_trait::async_trait;
use auto_impl::auto_impl;
use eyre::Result;
use crate::{Checkpoint, InterchainGasPaymentWithMeta, LogMeta, RawCommittedMessage};
use crate::{AbacusMessage, InterchainGasPaymentWithMeta, LogMeta};
/// Interface for an indexer.
#[async_trait]
@ -23,31 +23,23 @@ pub trait Indexer: Send + Sync + Debug {
async fn get_finalized_block_number(&self) -> Result<u32>;
}
/// Interface for Outbox contract indexer. Interface for allowing other
/// entities to retrieve chain-specific data from an outbox.
/// Interface for Mailbox contract indexer. Interface for allowing other
/// entities to retrieve chain-specific data from an mailbox.
#[async_trait]
#[auto_impl(Box, Arc)]
pub trait OutboxIndexer: Indexer + Send + Sync + Debug {
pub trait MailboxIndexer: Indexer {
/// Fetch list of messages between blocks `from` and `to`.
async fn fetch_sorted_messages(
&self,
from: u32,
to: u32,
) -> Result<Vec<(RawCommittedMessage, 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)>>;
) -> Result<Vec<(AbacusMessage, LogMeta)>>;
}
/// Interface for InterchainGasPaymaster contract indexer.
#[async_trait]
#[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`,
/// inclusive
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::fmt::Debug;
use async_trait::async_trait;
use auto_impl::auto_impl;
use ethers::prelude::Selector;
use ethers::{
@ -10,25 +9,20 @@ use ethers::{
core::types::{TransactionReceipt, H256},
providers::{Middleware, ProviderError},
};
use eyre::Result;
pub use common::*;
pub use encode::*;
pub use inbox::*;
pub use indexer::*;
pub use interchain_gas::*;
pub use outbox::*;
pub use validator_manager::*;
pub use mailbox::*;
pub use multisig_ism::*;
use crate::{db::DbError, utils::domain_hash, AbacusError};
use crate::{db::DbError, AbacusError};
mod common;
mod encode;
mod inbox;
mod indexer;
mod interchain_gas;
mod outbox;
mod validator_manager;
mod mailbox;
mod multisig_ism;
/// The result of a transaction
#[derive(Debug, Clone, Copy)]
@ -98,25 +92,6 @@ pub trait AbacusContract {
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.
#[auto_impl(Box, Arc)]
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
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Checkpoint {
/// The outbox chain
pub outbox_domain: u32,
/// The mailbox address
pub mailbox_address: H256,
/// The mailbox chain
pub mailbox_domain: u32,
/// The checkpointed root
pub root: H256,
/// The index of the checkpoint
@ -24,7 +26,7 @@ impl std::fmt::Display for Checkpoint {
write!(
f,
"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
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.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,
Self: Sized,
{
let mut outbox_domain = [0u8; 4];
reader.read_exact(&mut outbox_domain)?;
let mut mailbox_address = H256::zero();
reader.read_exact(mailbox_address.as_mut())?;
let mut mailbox_domain = [0u8; 4];
reader.read_exact(&mut mailbox_domain)?;
let mut root = H256::zero();
reader.read_exact(root.as_mut())?;
@ -57,7 +63,8 @@ impl Decode for Checkpoint {
reader.read_exact(&mut index)?;
Ok(Self {
outbox_domain: u32::from_be_bytes(outbox_domain),
mailbox_address,
mailbox_domain: u32::from_be_bytes(mailbox_domain),
root,
index: u32::from_be_bytes(index),
})
@ -68,10 +75,10 @@ impl Checkpoint {
fn signing_hash(&self) -> H256 {
let buffer = [0u8; 28];
// sign:
// domain_hash(outbox_domain) || root || index (as u256)
// domain_hash(mailbox_address, mailbox_domain) || root || index (as u256)
H256::from_slice(
Keccak256::new()
.chain(domain_hash(self.outbox_domain))
.chain(domain_hash(self.mailbox_address, self.mailbox_domain))
.chain(self.root)
.chain(buffer)
.chain(self.index.to_be_bytes())

@ -3,114 +3,31 @@ use sha3::{Digest, Keccak256};
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
#[derive(Debug, Default, Clone, PartialEq, Eq)]
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,
})
}
}
pub type RawAbacusMessage = Vec<u8>;
/// 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 {
type Error = AbacusError;
fn try_from(raw: &RawCommittedMessage) -> Result<Self, Self::Error> {
Ok(Self {
leaf_index: raw.leaf_index,
message: AbacusMessage::read_from(&mut raw.message.as_slice())?,
})
impl From<&AbacusMessage> for RawAbacusMessage {
fn from(m: &AbacusMessage) -> Self {
let mut message_vec = vec![];
m.write_to(&mut message_vec).expect("!write_to");
message_vec
}
}
/// A full Abacus message between chains
#[derive(Debug, Default, Clone)]
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,
/// 32 Address in Outbox convention
/// 32 Address in origin convention
pub sender: H256,
/// 4 SLIP-44 ID
/// 4 Destination domain ID
pub destination: u32,
/// 32 Address in destination convention
pub recipient: H256,
@ -118,15 +35,31 @@ pub struct AbacusMessage {
pub body: Vec<u8>,
}
/// A partial Abacus message between chains
#[derive(Debug, Default, Clone)]
pub struct Message {
/// 4 SLIP-44 ID
pub destination: u32,
/// 32 Address in destination convention
pub recipient: H256,
/// 0+ Message contents
pub body: Vec<u8>,
impl From<RawAbacusMessage> for AbacusMessage {
fn from(m: RawAbacusMessage) -> Self {
AbacusMessage::from(&m)
}
}
impl From<&RawAbacusMessage> for AbacusMessage {
fn from(m: &RawAbacusMessage) -> Self {
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 {
@ -134,6 +67,8 @@ impl Encode for AbacusMessage {
where
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.sender.as_ref())?;
writer.write_all(&self.destination.to_be_bytes())?;
@ -148,6 +83,12 @@ impl Decode for AbacusMessage {
where
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];
reader.read_exact(&mut origin)?;
@ -164,6 +105,8 @@ impl Decode for AbacusMessage {
reader.read_to_end(&mut body)?;
Ok(Self {
version: u8::from_be_bytes(version),
nonce: u32::from_be_bytes(nonce),
origin: u32::from_be_bytes(origin),
sender,
destination: u32::from_be_bytes(destination),
@ -174,17 +117,9 @@ impl Decode for AbacusMessage {
}
impl AbacusMessage {
/// Convert the message to a leaf
pub fn to_leaf(&self, leaf_index: u32) -> H256 {
let buffer = [0u8; 28];
H256::from_slice(
Keccak256::new()
.chain(&self.to_vec())
.chain(buffer)
.chain(leaf_index.to_be_bytes())
.finalize()
.as_slice(),
)
/// Convert the message to a message id
pub fn id(&self) -> H256 {
H256::from_slice(Keccak256::new().chain(&self.to_vec()).finalize().as_slice())
}
}

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

@ -17,12 +17,13 @@ pub fn strip_0x_prefix(s: &str) -> &str {
}
}
/// Computes hash of domain concatenated with "ABACUS"
pub fn domain_hash(domain: u32) -> H256 {
/// Computes hash of domain concatenated with "HYPERLANE"
pub fn domain_hash(address: H256, domain: u32) -> H256 {
H256::from_slice(
Keccak256::new()
.chain(domain.to_be_bytes())
.chain("ABACUS".as_bytes())
.chain(address.as_ref())
.chain("HYPERLANE".as_bytes())
.finalize()
.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 mockall::*;
use abacus_core::{Indexer, OutboxIndexer, *};
use abacus_core::{Indexer, MailboxIndexer, *};
mock! {
pub Indexer {
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 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<(RawCommittedMessage, LogMeta)>> {}
pub fn _fetch_sorted_messages(&self, from: u32, to: u32) -> Result<Vec<(AbacusMessage, LogMeta)>> {}
}
}
@ -44,20 +42,12 @@ impl Indexer for MockAbacusIndexer {
}
#[async_trait]
impl OutboxIndexer for MockAbacusIndexer {
impl MailboxIndexer for MockAbacusIndexer {
async fn fetch_sorted_messages(
&self,
from: u32,
to: u32,
) -> Result<Vec<(RawCommittedMessage, LogMeta)>> {
) -> Result<Vec<(AbacusMessage, LogMeta)>> {
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
pub mod outbox;
/// Mock inbox contract
pub mod inbox;
/// Mock mailbox contract
pub mod mailbox;
/// Mock indexer
pub mod indexer;
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 {
use ethers::types::H256;
use abacus_core::{
accumulator::merkle::Proof, db::AbacusDB, AbacusMessage, Encode, RawCommittedMessage,
};
use abacus_core::{accumulator::merkle::Proof, db::AbacusDB, AbacusMessage, RawAbacusMessage};
use super::*;
@ -42,8 +40,9 @@ mod test {
let outbox_name = "outbox_1".to_owned();
let db = AbacusDB::new(outbox_name, db);
let leaf_index = 100;
let m = AbacusMessage {
nonce: 100,
version: 0,
origin: 10,
sender: H256::from_low_u64_be(4),
destination: 12,
@ -51,23 +50,16 @@ mod test {
body: vec![1, 2, 3],
};
let message = RawCommittedMessage {
leaf_index,
message: m.to_vec(),
};
assert_eq!(m.to_leaf(leaf_index), message.leaf());
db.store_raw_committed_message(&message).unwrap();
db.store_message(&m).unwrap();
let by_leaf = db.message_by_leaf(message.leaf()).unwrap().unwrap();
assert_eq!(by_leaf, message);
let by_id = db.message_by_id(m.id()).unwrap().unwrap();
assert_eq!(RawAbacusMessage::from(&by_id), RawAbacusMessage::from(&m));
let by_index = db
.message_by_leaf_index(message.leaf_index)
.unwrap()
.unwrap();
assert_eq!(by_index, message);
let by_nonce = db.message_by_nonce(m.nonce).unwrap().unwrap();
assert_eq!(
RawAbacusMessage::from(&by_nonce),
RawAbacusMessage::from(&m)
);
})
.await;
}
@ -85,7 +77,7 @@ mod test {
};
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);
})
.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 abacus_base::MultisigCheckpointSyncer;
use abacus_core::{MultisigSignedCheckpoint, Outbox};
use abacus_core::{Mailbox, MultisigSignedCheckpoint};
pub(crate) struct CheckpointFetcher {
polling_interval: u64,
@ -18,7 +18,7 @@ pub(crate) struct CheckpointFetcher {
impl CheckpointFetcher {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
outbox: &dyn Outbox,
mailbox: &dyn Mailbox,
polling_interval: u64,
multisig_checkpoint_syncer: MultisigCheckpointSyncer,
signed_checkpoint_sender: Sender<Option<MultisigSignedCheckpoint>>,
@ -26,7 +26,7 @@ impl CheckpointFetcher {
) -> Self {
let signed_checkpoint_gauge = leaf_index_gauge.with_label_values(&[
"signed_offchain_checkpoint",
outbox.chain_name(),
mailbox.chain_name(),
"unknown", // Checkpoints are not remote-specific
]);
Self {
@ -51,7 +51,7 @@ impl CheckpointFetcher {
.fetch_checkpoint(signed_checkpoint_index)
.await?
{
debug!(
info!(
signed_checkpoint_index = signed_checkpoint_index,
"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> {
match self.db.leaf_by_leaf_index(leaf_index) {
match self.db.message_id_by_nonce(leaf_index) {
Ok(Some(leaf)) => {
debug!(leaf_index = leaf_index, "Ingesting leaf");
self.prover.ingest(leaf).expect("!tree full");
@ -117,7 +117,7 @@ impl MerkleTreeBuilder {
}
let starting_index = self.prover.count() as u32;
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)?;
}

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

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

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

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

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

@ -4,8 +4,8 @@ use std::{
time::Duration,
};
use abacus_base::InboxContracts;
use abacus_core::{ChainCommunicationError, Inbox, InboxValidatorManager, MessageStatus};
use abacus_base::CachingMailbox;
use abacus_core::{AbacusContract, ChainCommunicationError, Mailbox, MultisigIsm};
use eyre::Result;
use gelato::{
sponsored_call::{SponsoredCallApiCall, SponsoredCallApiCallResult, SponsoredCallArgs},
@ -29,7 +29,8 @@ pub struct SponsoredCallOpArgs {
pub http: reqwest::Client,
pub message: SubmitMessageArgs,
pub inbox_contracts: InboxContracts,
pub mailbox: CachingMailbox,
pub multisig_ism: Arc<dyn MultisigIsm>,
pub sponsor_api_key: String,
pub destination_chain: Chain,
@ -61,11 +62,11 @@ impl SponsoredCallOp {
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) {
loop {
match self.tick().await {
Ok(MessageStatus::Processed) => {
Ok(true) => {
// If the message was processed, send it over the channel and
// stop running.
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
/// 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.
if let Ok(MessageStatus::Processed) = self.message_status().await {
return Ok(MessageStatus::Processed);
if let Ok(true) = self.message_delivered().await {
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
// 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.
let tx_cost_estimate = match self
.inbox_contracts
.validator_manager
.process_estimate_costs(
&self.message.checkpoint,
&self.message.committed_message.message,
&self.message.proof,
)
.mailbox
.process_estimate_costs(&self.message.message, &metadata)
.await
{
Ok(tx_cost_estimate) => tx_cost_estimate,
Err(err) => {
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.
let (meets_gas_requirement, gas_payment) = self
.gas_payment_enforcer
.message_meets_gas_payment_requirement(
&self.message.committed_message,
&tx_cost_estimate,
)
.message_meets_gas_payment_requirement(&self.message.message, &tx_cost_estimate)
.await?;
if !meets_gas_requirement {
tracing::info!(gas_payment=?gas_payment, "Gas payment requirement not met yet");
return Ok(MessageStatus::None);
return Ok(false);
}
// Send the sponsored call.
@ -155,22 +152,22 @@ impl SponsoredCallOp {
// and set ourselves up for the next tick.
Err(err) => {
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
// 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 {
sleep(self.opts.poll_interval).await;
// 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
// marked as processed.
if let Ok(MessageStatus::Processed) = self.message_status().await {
return Ok(MessageStatus::Processed);
if let Ok(true) = self.message_delivered().await {
return Ok(true);
}
// 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
// after about 30 seconds.
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
// sponsored call call.
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 {
args: &args,
@ -216,30 +213,25 @@ impl SponsoredCallOp {
Ok(sponsored_call_api_call.run().await?)
}
fn create_sponsored_call_args(&self) -> SponsoredCallArgs {
let calldata = self.inbox_contracts.validator_manager.process_calldata(
&self.message.checkpoint,
&self.message.committed_message.message,
&self.message.proof,
);
SponsoredCallArgs {
async fn create_sponsored_call_args(&self) -> Result<SponsoredCallArgs> {
let metadata = self
.multisig_ism
.format_metadata(&self.message.checkpoint, self.message.proof)
.await?;
let calldata = self
.mailbox
.process_calldata(&self.message.message, &metadata);
Ok(SponsoredCallArgs {
chain_id: self.destination_chain,
target: self
.inbox_contracts
.validator_manager
.contract_address()
.into(),
target: self.mailbox.address().into(),
data: calldata.into(),
gas_limit: None, // Gelato will handle gas estimation
retries: None, // Use Gelato's default of 5 retries, each ~5 seconds apart
}
})
}
async fn message_status(&self) -> Result<MessageStatus, ChainCommunicationError> {
self.inbox_contracts
.inbox
.message_status(self.message.committed_message.to_leaf())
.await
async fn message_delivered(&self) -> Result<bool, ChainCommunicationError> {
self.mailbox.delivered(self.message.message.id()).await
}
fn send_message_processed(

@ -1,6 +1,6 @@
use std::cmp::Ordering;
use abacus_core::{accumulator::merkle::Proof, CommittedMessage, MultisigSignedCheckpoint};
use abacus_core::{accumulator::merkle::Proof, AbacusMessage, MultisigSignedCheckpoint};
use tokio::time::Instant;
@ -30,8 +30,7 @@ pub mod serial_submitter;
#[derive(Clone, Debug)]
pub struct SubmitMessageArgs {
pub leaf_index: u32,
pub committed_message: CommittedMessage,
pub message: AbacusMessage,
pub checkpoint: MultisigSignedCheckpoint,
pub proof: Proof,
pub enqueue_time: Instant,
@ -40,15 +39,13 @@ pub struct SubmitMessageArgs {
impl SubmitMessageArgs {
pub fn new(
leaf_index: u32,
committed_message: CommittedMessage,
message: AbacusMessage,
checkpoint: MultisigSignedCheckpoint,
proof: Proof,
enqueue_time: Instant,
) -> Self {
SubmitMessageArgs {
leaf_index,
committed_message,
message,
checkpoint,
proof,
enqueue_time,
@ -67,7 +64,7 @@ impl SubmitMessageArgs {
impl Ord for SubmitMessageArgs {
fn cmp(&self, other: &Self) -> Ordering {
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::Less => Ordering::Greater,
Ordering::Greater => Ordering::Less,
@ -86,7 +83,7 @@ impl PartialOrd for SubmitMessageArgs {
impl PartialEq for SubmitMessageArgs {
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 abacus_base::{CoreMetrics, InboxContracts};
use abacus_core::{
db::AbacusDB, AbacusCommon, AbacusContract, CommittedMessage, MultisigSignedCheckpoint,
};
use abacus_base::{CachingMailbox, CoreMetrics};
use abacus_core::{db::AbacusDB, AbacusContract, AbacusMessage, Mailbox, MultisigSignedCheckpoint};
use crate::{merkle_tree_builder::MerkleTreeBuilder, settings::matching_list::MatchingList};
@ -21,21 +19,21 @@ use super::SubmitMessageArgs;
#[derive(Debug)]
pub(crate) struct MessageProcessor {
db: AbacusDB,
inbox_contracts: InboxContracts,
destination_mailbox: CachingMailbox,
whitelist: Arc<MatchingList>,
blacklist: Arc<MatchingList>,
metrics: MessageProcessorMetrics,
tx_msg: mpsc::UnboundedSender<SubmitMessageArgs>,
ckpt_rx: watch::Receiver<Option<MultisigSignedCheckpoint>>,
prover_sync: MerkleTreeBuilder,
message_leaf_index: u32,
message_nonce: u32,
}
impl MessageProcessor {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
db: AbacusDB,
inbox_contracts: InboxContracts,
destination_mailbox: CachingMailbox,
whitelist: Arc<MatchingList>,
blacklist: Arc<MatchingList>,
metrics: MessageProcessorMetrics,
@ -44,14 +42,14 @@ impl MessageProcessor {
) -> Self {
Self {
db: db.clone(),
inbox_contracts,
destination_mailbox,
whitelist,
blacklist,
metrics,
tx_msg,
ckpt_rx,
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)
}
#[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<()> {
// Ensure that there is at least one valid, known checkpoint before starting
// work loop.
@ -72,8 +70,8 @@ impl MessageProcessor {
}
// Forever, scan AbacusDB looking for new messages to send. When criteria are
// satisfied or the message is disqualified, push the message onto
// self.tx_msg and then continue the scan at the next outbox highest
// leaf index.
// self.tx_msg and then continue the scan at the next highest
// nonce.
loop {
self.tick().await?;
}
@ -84,35 +82,31 @@ impl MessageProcessor {
async fn tick(&mut self) -> Result<()> {
self.metrics
.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
.db
.retrieve_leaf_processing_status(self.message_leaf_index)?
.retrieve_message_processed(self.message_nonce)?
.is_some()
{
debug!(
inbox_name=?self.inbox_contracts.inbox.chain_name(),
local_domain=?self.inbox_contracts.inbox.local_domain(),
idx=?self.message_leaf_index,
"Skipping since message_index already in DB");
self.message_leaf_index += 1;
chain=?self.destination_mailbox.chain_name(),
domain=?self.destination_mailbox.local_domain(),
nonce=?self.message_nonce,
"Skipping since message_nonce already in DB");
self.message_nonce += 1;
return Ok(());
}
let message = if let Some(msg) = self
.db
.message_by_leaf_index(self.message_leaf_index)?
.map(CommittedMessage::try_from)
.transpose()?
.message_by_nonce(self.message_nonce)?
.map(AbacusMessage::from)
{
debug!(msg=?msg, "Working on msg");
msg
} else {
debug!(
"Leaf in db without message idx: {}",
self.message_leaf_index
);
debug!("Leaf in db without message nonce: {}", self.message_nonce);
// 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
// info to rocksdb across a few records, so we might see the leaf
@ -125,51 +119,48 @@ impl MessageProcessor {
};
// Skip if for different inbox.
if message.message.destination != self.inbox_contracts.inbox.local_domain() {
if message.destination != self.destination_mailbox.local_domain() {
debug!(
inbox_name=?self.inbox_contracts.inbox.chain_name(),
local_domain=?self.inbox_contracts.inbox.local_domain(),
dst=?message.message.destination,
msg=?message,
"Message not for local domain, skipping idx {}", self.message_leaf_index);
self.message_leaf_index += 1;
id=?message.id(),
destination=message.destination,
nonce=message.nonce,
"Message destined for other domain, skipping");
self.message_nonce += 1;
return Ok(());
}
// Skip if not whitelisted.
if !self.whitelist.msg_matches(&message.message, true) {
if !self.whitelist.msg_matches(&message, true) {
debug!(
inbox_name=?self.inbox_contracts.inbox.chain_name(),
local_domain=?self.inbox_contracts.inbox.local_domain(),
dst=?message.message.destination,
id=?message.id(),
destination=message.destination,
nonce=message.nonce,
whitelist=?self.whitelist,
msg=?message,
"Message not whitelisted, skipping idx {}", self.message_leaf_index);
self.message_leaf_index += 1;
"Message not whitelisted, skipping");
self.message_nonce += 1;
return Ok(());
}
// skip if the message is blacklisted
if self.blacklist.msg_matches(&message.message, false) {
// Skip if the message is blacklisted
if self.blacklist.msg_matches(&message, false) {
debug!(
inbox_name=?self.inbox_contracts.inbox.chain_name(),
local_domain=?self.inbox_contracts.inbox.local_domain(),
dst=?message.message.destination,
id=?message.id(),
destination=message.destination,
nonce=message.nonce,
blacklist=?self.blacklist,
msg=?message,
"Message blacklisted, skipping idx {}", self.message_leaf_index);
self.message_leaf_index += 1;
"Message blacklisted, skipping");
self.message_nonce += 1;
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
// submitter channel.
let mut ckpt;
loop {
ckpt = self.ckpt_rx.borrow().clone();
match &ckpt {
Some(ckpt) if ckpt.checkpoint.index >= self.message_leaf_index => {
Some(ckpt) if ckpt.checkpoint.index >= self.message_nonce => {
break;
}
_ => {
@ -178,7 +169,7 @@ impl MessageProcessor {
}
}
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
// submitter.
@ -188,32 +179,23 @@ impl MessageProcessor {
.await?;
}
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
.db
.leaf_by_leaf_index(self.message_leaf_index)?
.is_some()
{
if self.db.message_id_by_nonce(self.message_nonce)?.is_some() {
debug!(
"Sending message at idx {} to submitter",
self.message_leaf_index
id=?message.id(),
nonce=message.nonce,
"Sending message to submitter"
);
// Finally, build the submit arg and dispatch it to the submitter.
let submit_args = SubmitMessageArgs::new(
self.message_leaf_index,
message,
checkpoint,
proof,
Instant::now(),
);
let submit_args = SubmitMessageArgs::new(message, checkpoint, proof, Instant::now());
self.tx_msg.send(submit_args)?;
self.message_leaf_index += 1;
self.message_nonce += 1;
} else {
warn!(
idx=self.message_leaf_index,
inbox_name=?self.inbox_contracts.inbox.chain_name(),
"Unexpected missing leaf_by_leaf_index");
nonce=self.message_nonce,
chain=?self.destination_mailbox.chain_name(),
"Unexpected missing message_id_by_nonce");
}
Ok(())
}
@ -225,12 +207,12 @@ pub(crate) struct 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 {
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",
outbox_chain,
inbox_chain,
origin_chain,
destination_chain,
]),
}
}

@ -1,13 +1,10 @@
use std::collections::VecDeque;
use std::sync::Arc;
use abacus_base::CachingMailbox;
use abacus_base::CoreMetrics;
use abacus_base::InboxContracts;
use abacus_core::db::AbacusDB;
use abacus_core::AbacusContract;
use abacus_core::Inbox;
use abacus_core::InboxValidatorManager;
use abacus_core::MessageStatus;
use abacus_core::{AbacusContract, Mailbox, MultisigIsm};
use eyre::{bail, Result};
use prometheus::{Histogram, IntCounter, IntGauge};
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
/// queue could grow.
run_queue: VecDeque<SubmitMessageArgs>,
/// Inbox / InboxValidatorManager on the destination chain.
inbox_contracts: InboxContracts,
/// Mailbox on the destination chain.
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.
db: AbacusDB,
/// Metrics for serial submitter.
@ -134,7 +133,8 @@ pub(crate) struct SerialSubmitter {
impl SerialSubmitter {
pub(crate) fn new(
rx: mpsc::UnboundedReceiver<SubmitMessageArgs>,
inbox_contracts: InboxContracts,
mailbox: CachingMailbox,
multisig_ism: Arc<dyn MultisigIsm>,
db: AbacusDB,
metrics: SerialSubmitterMetrics,
gas_payment_enforcer: Arc<GasPaymentEnforcer>,
@ -143,7 +143,8 @@ impl SerialSubmitter {
rx,
wait_queue: Vec::new(),
run_queue: VecDeque::new(),
inbox_contracts,
mailbox,
multisig_ism,
db,
metrics,
gas_payment_enforcer,
@ -155,7 +156,7 @@ impl SerialSubmitter {
.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<()> {
loop {
self.tick().await?;
@ -212,18 +213,18 @@ impl SerialSubmitter {
};
match self.process_message(&msg).await {
Ok(MessageStatus::Processed) => {
info!(msg=?msg, msg_leaf_index=msg.leaf_index, "Message processed");
Ok(true) => {
info!(id=?msg.message.id(), nonce=msg.message.nonce, "Message processed");
self.record_message_process_success(&msg)?;
return Ok(());
}
Ok(MessageStatus::None) => {
info!(msg=?msg, msg_leaf_index=msg.leaf_index, "Message not processed");
Ok(false) => {
info!(id=?msg.message.id(), nonce=msg.message.nonce, "Message not processed");
}
// We expect this branch to be hit when there is unexpected behavior -
// defined behavior like gas estimation failing will not hit this branch.
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
/// be processed, either due to failed gas estimation or an insufficient gas payment,
/// Ok(MessageStatus::None) is returned.
#[instrument(skip(self, msg), fields(msg_leaf_index=msg.leaf_index))]
async fn process_message(&self, msg: &SubmitMessageArgs) -> Result<MessageStatus> {
#[instrument(skip(self, msg), fields(msg_nonce=msg.message.nonce))]
async fn process_message(&self, msg: &SubmitMessageArgs) -> Result<bool> {
// 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
// already-processed, and move on to the next tick.
// TODO(webbhorn): Make this robust to re-orgs on inbox.
if let MessageStatus::Processed = self
.inbox_contracts
.inbox
.message_status(msg.committed_message.to_leaf())
.await?
{
if self.mailbox.delivered(msg.message.id()).await? {
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
// 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.
let tx_cost_estimate = match self
.inbox_contracts
.validator_manager
.process_estimate_costs(&msg.checkpoint, &msg.committed_message.message, &msg.proof)
.mailbox
.process_estimate_costs(&msg.message, &metadata)
.await
{
Ok(tx_cost_estimate) => tx_cost_estimate,
Err(err) => {
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.
let (meets_gas_requirement, gas_payment) = self
.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?;
if !meets_gas_requirement {
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.
@ -293,14 +292,8 @@ impl SerialSubmitter {
// We use the estimated gas limit from the prior call to `process_estimate_costs` to
// avoid a second gas estimation.
let process_result = self
.inbox_contracts
.validator_manager
.process(
&msg.checkpoint,
&msg.committed_message.message,
&msg.proof,
Some(tx_cost_estimate.gas_limit),
)
.mailbox
.process(&msg.message, &metadata, Some(tx_cost_estimate.gas_limit))
.await;
match process_result {
// TODO(trevor): Instead of immediately marking as processed, move to a verification
@ -312,11 +305,11 @@ impl SerialSubmitter {
info!(hash=?outcome.txid,
wq_sz=?self.wait_queue.len(), rq_sz=?self.run_queue.len(),
"Message successfully processed by transaction");
Ok(MessageStatus::Processed)
Ok(true)
}
Ok(outcome) => {
info!(hash=?outcome.txid, "Transaction attempting to process transaction reverted");
Ok(MessageStatus::None)
Ok(false)
}
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
/// this message again, even after the relayer restarts.
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
.queue_duration_hist
.observe((Instant::now() - msg.enqueue_time).as_secs_f64());
self.metrics.max_submitted_leaf_index =
std::cmp::max(self.metrics.max_submitted_leaf_index, msg.leaf_index);
self.metrics.max_submitted_nonce =
std::cmp::max(self.metrics.max_submitted_nonce, msg.message.nonce);
self.metrics
.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();
Ok(())
}
@ -351,34 +344,34 @@ pub(crate) struct SerialSubmitterMetrics {
messages_processed_count: IntCounter,
/// Private state used to update actual metrics each tick.
max_submitted_leaf_index: u32,
max_submitted_nonce: u32,
}
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 {
run_queue_length_gauge: metrics.submitter_queue_length().with_label_values(&[
outbox_chain,
inbox_chain,
origin_chain,
destination_chain,
"run_queue",
]),
wait_queue_length_gauge: metrics.submitter_queue_length().with_label_values(&[
outbox_chain,
inbox_chain,
origin_chain,
destination_chain,
"wait_queue",
]),
queue_duration_hist: metrics
.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()
.with_label_values(&[outbox_chain, inbox_chain]),
processed_gauge: metrics.last_known_message_leaf_index().with_label_values(&[
.with_label_values(&[origin_chain, destination_chain]),
processed_gauge: metrics.last_known_message_nonce().with_label_values(&[
"message_processed",
outbox_chain,
inbox_chain,
origin_chain,
destination_chain,
]),
max_submitted_leaf_index: 0,
max_submitted_nonce: 0,
}
}
}

@ -1,18 +1,17 @@
use std::sync::Arc;
use std::time::Duration;
use abacus_base::chains::TransactionSubmissionType;
use abacus_base::CachingMailbox;
use async_trait::async_trait;
use eyre::Result;
use tokio::time::MissedTickBehavior;
use tokio::{sync::mpsc, sync::watch, task::JoinHandle};
use tracing::{info, info_span, instrument::Instrumented, Instrument};
use abacus_base::{
chains::GelatoConf, run_all, AbacusAgentCore, Agent, BaseAgent, CachingInterchainGasPaymaster,
ContractSyncMetrics, CoreMetrics, InboxContracts, MultisigCheckpointSyncer,
chains::GelatoConf, run_all, AbacusAgentCore, Agent, BaseAgent, ContractSyncMetrics,
CoreMetrics, MultisigCheckpointSyncer,
};
use abacus_core::{AbacusContract, MultisigSignedCheckpoint, Signers};
use abacus_core::{AbacusContract, MultisigIsm, MultisigSignedCheckpoint, Signers};
use crate::msg::gas_payment::GasPaymentEnforcer;
use crate::msg::gelato_submitter::{GelatoSubmitter, GelatoSubmitterMetrics};
@ -26,6 +25,7 @@ use crate::{checkpoint_fetcher::CheckpointFetcher, msg::serial_submitter::Serial
/// A relayer agent
#[derive(Debug)]
pub struct Relayer {
origin_chain_name: String,
signed_checkpoint_polling_interval: u64,
multisig_checkpoint_syncer: MultisigCheckpointSyncer,
core: AbacusAgentCore,
@ -53,13 +53,13 @@ impl BaseAgent for Relayer {
{
let core = settings
.as_ref()
.try_into_abacus_core(metrics, true)
.try_into_abacus_core(metrics, None)
.await?;
let multisig_checkpoint_syncer: MultisigCheckpointSyncer = settings
.multisigcheckpointsyncer
.try_into_multisig_checkpoint_syncer(
core.outbox.outbox().chain_name(),
&settings.originchainname,
core.metrics.validator_checkpoint_index(),
)?;
@ -68,6 +68,7 @@ impl BaseAgent for Relayer {
info!(whitelist = %whitelist, blacklist = %blacklist, "Whitelist configuration");
Ok(Self {
origin_chain_name: settings.originchainname,
signed_checkpoint_polling_interval: settings
.signedcheckpointpollinginterval
.parse()
@ -85,26 +86,33 @@ impl BaseAgent for Relayer {
let (signed_checkpoint_sender, signed_checkpoint_receiver) =
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(
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
.core
.settings
.get_signer(inbox_name)
.get_signer(chain_name)
.await
.expect("expected signer for inbox");
tasks.push(self.run_inbox(
inbox_contracts.clone(),
.expect("expected signer for mailbox");
let mailbox = self.mailbox(chain_name).unwrap();
let multisig_ism = self.multisig_ism(chain_name).unwrap();
tasks.push(self.run_destination_mailbox(
mailbox.clone(),
multisig_ism.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(),
signer,
gas_payment_enforcer.clone(),
@ -114,36 +122,43 @@ impl BaseAgent for Relayer {
tasks.push(self.run_checkpoint_fetcher(signed_checkpoint_sender));
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)
}
}
impl Relayer {
fn run_outbox_sync(
fn run_origin_mailbox_sync(
&self,
sync_metrics: ContractSyncMetrics,
) -> Instrumented<JoinHandle<Result<()>>> {
let outbox = self.outbox();
let sync = outbox.sync(self.as_ref().indexer.clone(), sync_metrics);
let mailbox = self.mailbox(&self.origin_chain_name).unwrap();
let sync = mailbox.sync(
self.as_ref().settings.chains[&self.origin_chain_name]
.index
.clone(),
sync_metrics,
);
sync
}
fn run_interchain_gas_paymaster_sync(
&self,
paymaster: CachingInterchainGasPaymaster,
sync_metrics: ContractSyncMetrics,
) -> 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(
@ -151,92 +166,68 @@ impl Relayer {
signed_checkpoint_sender: watch::Sender<Option<MultisigSignedCheckpoint>>,
) -> Instrumented<JoinHandle<Result<()>>> {
let checkpoint_fetcher = CheckpointFetcher::new(
self.outbox().outbox(),
self.mailbox(&self.origin_chain_name).unwrap(),
self.signed_checkpoint_polling_interval,
self.multisig_checkpoint_syncer.clone(),
signed_checkpoint_sender,
self.core.metrics.last_known_message_leaf_index(),
self.core.metrics.last_known_message_nonce(),
);
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
/// particular inbox.
fn make_gelato_submitter_for_inbox(
/// particular mailbox.
fn make_gelato_submitter(
&self,
message_receiver: mpsc::UnboundedReceiver<SubmitMessageArgs>,
inbox_contracts: InboxContracts,
mailbox: CachingMailbox,
multisig_ism: Arc<dyn MultisigIsm>,
gelato_config: GelatoConf,
gas_payment_enforcer: Arc<GasPaymentEnforcer>,
) -> GelatoSubmitter {
let inbox_chain_name = inbox_contracts.inbox.chain_name().to_owned();
let chain_name = mailbox.chain_name().to_owned();
GelatoSubmitter::new(
message_receiver,
inbox_contracts,
self.outbox().db().clone(),
mailbox,
multisig_ism,
self.mailbox(&self.origin_chain_name).unwrap().db().clone(),
gelato_config,
GelatoSubmitterMetrics::new(
&self.core.metrics,
self.outbox().outbox().chain_name(),
&inbox_chain_name,
),
GelatoSubmitterMetrics::new(&self.core.metrics, &self.origin_chain_name, &chain_name),
gas_payment_enforcer,
)
}
#[tracing::instrument(fields(inbox=%inbox_contracts.inbox.chain_name()))]
fn run_inbox(
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(fields(destination=%destination_mailbox.chain_name()))]
fn run_destination_mailbox(
&self,
inbox_contracts: InboxContracts,
destination_mailbox: CachingMailbox,
multisig_ism: Arc<dyn MultisigIsm>,
signed_checkpoint_receiver: watch::Receiver<Option<MultisigSignedCheckpoint>>,
tx_submission: TransactionSubmissionType,
gelato_config: Option<&GelatoConf>,
signer: Signers,
gas_payment_enforcer: Arc<GasPaymentEnforcer>,
) -> Instrumented<JoinHandle<Result<()>>> {
let outbox = self.outbox().outbox();
let outbox_name = outbox.chain_name();
let inbox_name = inbox_contracts.inbox.chain_name();
let metrics = MessageProcessorMetrics::new(
&self.core.metrics,
outbox_name,
inbox_contracts.inbox.chain_name(),
);
let origin_mailbox = self.mailbox(&self.origin_chain_name).unwrap();
let destination = destination_mailbox.chain_name();
let metrics =
MessageProcessorMetrics::new(&self.core.metrics, &self.origin_chain_name, destination);
let (msg_send, msg_receive) = mpsc::unbounded_channel();
let submit_fut = match tx_submission {
TransactionSubmissionType::Gelato => {
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,
inbox_contracts.clone(),
destination_mailbox.clone(),
multisig_ism,
gelato_config.clone(),
gas_payment_enforcer,
)
@ -245,12 +236,13 @@ impl Relayer {
TransactionSubmissionType::Signer => {
let serial_submitter = SerialSubmitter::new(
msg_receive,
inbox_contracts.clone(),
self.outbox().db().clone(),
destination_mailbox.clone(),
multisig_ism,
origin_mailbox.db().clone(),
SerialSubmitterMetrics::new(
&self.core.metrics,
outbox_name,
inbox_contracts.inbox.chain_name(),
&self.origin_chain_name,
destination,
),
gas_payment_enforcer,
);
@ -259,8 +251,8 @@ impl Relayer {
};
let message_processor = MessageProcessor::new(
self.outbox().db().clone(),
inbox_contracts,
origin_mailbox.db().clone(),
destination_mailbox,
self.whitelist.clone(),
self.blacklist.clone(),
metrics,

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

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

@ -1,13 +1,14 @@
use std::collections::HashMap;
use abacus_base::chains::IndexSettings;
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`
// config is not needed for the scraper.
decl_settings!(Scraper {
/// Configurations for contracts on the outbox chains
outboxes: HashMap<String, ChainSetup<OutboxAddresses>>,
outboxes: HashMap<String, ChainSetup>,
/// Index settings by chain
indexes: HashMap<String, IndexSettings>,
});

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

@ -3,18 +3,17 @@ use std::time::{Duration, Instant};
use eyre::Result;
use prometheus::IntGauge;
use tokio::time::MissedTickBehavior;
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_core::{Outbox, Signers};
use abacus_base::{CachingMailbox, CheckpointSyncer, CheckpointSyncers, CoreMetrics};
use abacus_core::{Mailbox, Signers};
pub(crate) struct ValidatorSubmitter {
interval: u64,
reorg_period: u64,
signer: Arc<Signers>,
outbox: CachingOutbox,
mailbox: CachingMailbox,
checkpoint_syncer: Arc<CheckpointSyncers>,
metrics: ValidatorSubmitterMetrics,
}
@ -23,7 +22,7 @@ impl ValidatorSubmitter {
pub(crate) fn new(
interval: u64,
reorg_period: u64,
outbox: CachingOutbox,
mailbox: CachingMailbox,
signer: Arc<Signers>,
checkpoint_syncer: Arc<CheckpointSyncers>,
metrics: ValidatorSubmitterMetrics,
@ -31,7 +30,7 @@ impl ValidatorSubmitter {
Self {
reorg_period,
interval,
outbox,
mailbox,
signer,
checkpoint_syncer,
metrics,
@ -40,31 +39,7 @@ impl ValidatorSubmitter {
pub(crate) fn spawn(self) -> Instrumented<JoinHandle<Result<()>>> {
let span = info_span!("ValidatorSubmitter");
let metrics_loop = tokio::spawn(Self::metrics_loop(
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;
}
tokio::spawn(async move { self.main_task().await }).instrument(span)
}
async fn main_task(self) -> Result<()> {
@ -73,15 +48,15 @@ impl ValidatorSubmitter {
} else {
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
// 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
// rather than just the size. See
// https://github.com/abacus-network/abacus-monorepo/issues/575 for
// more details.
while self.outbox.count().await? == 0 {
info!("Waiting for non-zero outbox size");
while self.mailbox.count().await? == 0 {
info!("Waiting for non-zero mailbox size");
sleep(Duration::from_secs(self.interval)).await;
}
@ -115,7 +90,7 @@ impl ValidatorSubmitter {
info!(current_index = current_index, "Starting Validator");
loop {
// 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
.latest_checkpoint_observed
@ -157,21 +132,19 @@ impl ValidatorSubmitter {
}
pub(crate) struct ValidatorSubmitterMetrics {
outbox_state: IntGauge,
latest_checkpoint_observed: IntGauge,
latest_checkpoint_processed: IntGauge,
}
impl ValidatorSubmitterMetrics {
pub fn new(metrics: &CoreMetrics, outbox_chain: &str) -> Self {
pub fn new(metrics: &CoreMetrics, mailbox_chain: &str) -> Self {
Self {
outbox_state: metrics.outbox_state().with_label_values(&[outbox_chain]),
latest_checkpoint_observed: metrics
.latest_checkpoint()
.with_label_values(&["validator_observed", outbox_chain]),
.with_label_values(&["validator_observed", mailbox_chain]),
latest_checkpoint_processed: metrics
.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 abacus_base::{run_all, AbacusAgentCore, Agent, BaseAgent, CheckpointSyncers, CoreMetrics};
use abacus_core::{AbacusContract, Signers};
use abacus_core::Signers;
use crate::submit::ValidatorSubmitterMetrics;
use crate::{settings::ValidatorSettings, submit::ValidatorSubmitter};
@ -14,6 +14,7 @@ use crate::{settings::ValidatorSettings, submit::ValidatorSubmitter};
/// A validator agent
#[derive(Debug)]
pub struct Validator {
origin_chain_name: String,
signer: Arc<Signers>,
reorg_period: u64,
interval: u64,
@ -24,6 +25,7 @@ pub struct Validator {
impl Validator {
/// Instantiate a new validator
pub fn new(
origin_chain_name: String,
signer: Signers,
reorg_period: u64,
interval: u64,
@ -31,6 +33,7 @@ impl Validator {
core: AbacusAgentCore,
) -> Self {
Self {
origin_chain_name,
signer: Arc::new(signer),
reorg_period,
interval,
@ -58,14 +61,16 @@ impl BaseAgent for Validator {
{
let signer = settings.validator.try_into_signer().await?;
let reorg_period = settings.reorgperiod.parse().expect("invalid uint");
let origin_chain_name = &settings.originchainname;
let interval = settings.interval.parse().expect("invalid uint");
let core = settings
.as_ref()
.try_into_abacus_core(metrics, false)
.try_into_abacus_core(metrics, Some(vec![origin_chain_name]))
.await?;
let checkpoint_syncer = settings.checkpointsyncer.try_into_checkpoint_syncer(None)?;
Ok(Self::new(
origin_chain_name.clone(),
signer,
reorg_period,
interval,
@ -79,10 +84,10 @@ impl BaseAgent for Validator {
let submit = ValidatorSubmitter::new(
self.interval,
self.reorg_period,
self.outbox().clone(),
self.mailbox(&self.origin_chain_name).unwrap().clone(),
self.signer.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()])

@ -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": [
{
"indexed": true,
"internalType": "address",
"name": "outbox",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "leafIndex",
"type": "uint256"
"internalType": "bytes32",
"name": "messageId",
"type": "bytes32"
},
{
"indexed": false,
@ -91,14 +85,9 @@
{
"inputs": [
{
"internalType": "address",
"name": "_outbox",
"type": "address"
},
{
"internalType": "uint256",
"name": "_leafIndex",
"type": "uint256"
"internalType": "bytes32",
"name": "_messageId",
"type": "bytes32"
},
{
"internalType": "uint32",

@ -15,18 +15,12 @@
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "root",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "uint256",
"name": "index",
"type": "uint256"
"internalType": "address",
"name": "module",
"type": "address"
}
],
"name": "CheckpointCached",
"name": "DefaultIsmSet",
"type": "event"
},
{
@ -34,9 +28,9 @@
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "leafIndex",
"type": "uint256"
"internalType": "bytes32",
"name": "messageId",
"type": "bytes32"
},
{
"indexed": false,
@ -48,12 +42,6 @@
"name": "Dispatch",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "Fail",
"type": "event"
},
{
"anonymous": false,
"inputs": [
@ -90,13 +78,13 @@
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "validatorManager",
"type": "address"
"indexed": true,
"internalType": "bytes32",
"name": "messageId",
"type": "bytes32"
}
],
"name": "ValidatorManagerSet",
"name": "Process",
"type": "event"
},
{
@ -117,9 +105,9 @@
"name": "VERSION",
"outputs": [
{
"internalType": "uint8",
"internalType": "uint32",
"name": "",
"type": "uint8"
"type": "uint32"
}
],
"stateMutability": "view",
@ -127,38 +115,44 @@
},
{
"inputs": [],
"name": "cacheCheckpoint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
"name": "count",
"outputs": [
{
"internalType": "bytes32",
"internalType": "uint32",
"name": "",
"type": "bytes32"
"type": "uint32"
}
],
"name": "cachedCheckpoints",
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "defaultIsm",
"outputs": [
{
"internalType": "uint256",
"internalType": "contract IInterchainSecurityModule",
"name": "",
"type": "uint256"
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "count",
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "delivered",
"outputs": [
{
"internalType": "uint256",
"internalType": "bool",
"name": "",
"type": "uint256"
"type": "bool"
}
],
"stateMutability": "view",
@ -185,26 +179,19 @@
"name": "dispatch",
"outputs": [
{
"internalType": "uint256",
"internalType": "bytes32",
"name": "",
"type": "uint256"
"type": "bytes32"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "fail",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_validatorManager",
"name": "_defaultIsm",
"type": "address"
}
],
@ -213,37 +200,6 @@
"stateMutability": "nonpayable",
"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": [],
"name": "latestCheckpoint",
@ -254,9 +210,9 @@
"type": "bytes32"
},
{
"internalType": "uint256",
"internalType": "uint32",
"name": "",
"type": "uint256"
"type": "uint32"
}
],
"stateMutability": "view",
@ -288,6 +244,24 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_metadata",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "process",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
@ -312,28 +286,15 @@
"inputs": [
{
"internalType": "address",
"name": "_validatorManager",
"name": "_module",
"type": "address"
}
],
"name": "setValidatorManager",
"name": "setDefaultIsm",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "state",
"outputs": [
{
"internalType": "enum Outbox.States",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
@ -359,18 +320,5 @@
],
"stateMutability": "view",
"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 outbox_address: H160,
pub from_height: u32,
pub chunk_size: u32,
pub mailbox_address: H160,
pub finality_blocks: u32,
}
@ -47,9 +45,6 @@ impl MakeableWithProvider for InterchainGasPaymasterIndexerBuilder {
Box::new(EthereumInterchainGasPaymasterIndexer::new(
Arc::new(provider),
locator,
self.outbox_address,
self.from_height,
self.chunk_size,
self.finality_blocks,
))
}
@ -63,11 +58,6 @@ where
{
contract: Arc<EthereumInterchainGasPaymasterInternal<M>>,
provider: Arc<M>,
outbox_address: H160,
#[allow(unused)]
from_height: u32,
#[allow(unused)]
chunk_size: u32,
finality_blocks: u32,
}
@ -76,23 +66,13 @@ where
M: Middleware + 'static,
{
/// Create new EthereumInterchainGasPaymasterIndexer
pub fn new(
provider: Arc<M>,
locator: &ContractLocator,
outbox_address: H160,
from_height: u32,
chunk_size: u32,
finality_blocks: u32,
) -> Self {
pub fn new(provider: Arc<M>, locator: &ContractLocator, finality_blocks: u32) -> Self {
Self {
contract: Arc::new(EthereumInterchainGasPaymasterInternal::new(
&locator.address,
provider.clone(),
)),
provider,
outbox_address,
from_height,
chunk_size,
finality_blocks,
}
}
@ -128,7 +108,6 @@ where
let events = self
.contract
.gas_payment_filter()
.topic1(self.outbox_address)
.from_block(from_block)
.to_block(to_block)
.query_with_meta()
@ -138,7 +117,7 @@ where
.into_iter()
.map(|(log, log_meta)| InterchainGasPaymentWithMeta {
payment: InterchainGasPayment {
leaf_index: log.leaf_index.as_u32(),
message_id: H256::from(log.message_id),
amount: log.amount,
},
meta: InterchainGasPaymentMeta {

@ -14,30 +14,26 @@ pub use retrying::{RetryingProvider, RetryingProviderError};
use crate::abi::FunctionExt;
#[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))]
mod tx;
/// Outbox abi
#[cfg(not(doctest))]
mod outbox;
mod mailbox;
#[cfg(not(doctest))]
mod trait_builder;
/// Inbox abi
#[cfg(not(doctest))]
mod inbox;
/// InboxValidatorManager abi
#[cfg(not(doctest))]
mod validator_manager;
/// InterchainGasPaymaster abi
#[cfg(not(doctest))]
mod interchain_gas;
/// MultisigIsm abi
#[cfg(not(doctest))]
mod multisig_ism;
/// Generated contract bindings.
#[cfg(not(doctest))]
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);
/// A trait for dynamic trait creation with provider initialization.
#[async_trait]
pub trait MakeableWithProvider {
pub trait MakeableWithProvider: Sync {
/// The type that will be created.
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! {
"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_OUTBOX_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_INBOXES_TEST2_CONNECTION_TYPE" => "httpQuorum",
"HYP_BASE_INBOXES_TEST3_CONNECTION_URL" => "http://127.0.0.1:8545",
"HYP_BASE_INBOXES_TEST3_CONNECTION_TYPE" => "http",
"BASE_CONFIG" => "test1_config.json",
"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_CHAINS_TEST1_CONNECTION_TYPE" => "httpQuorum",
"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_CHAINS_TEST2_CONNECTION_TYPE" => "httpQuorum",
"HYP_BASE_CHAINS_TEST3_CONNECTION_URL" => "http://127.0.0.1:8545",
"HYP_BASE_CHAINS_TEST3_CONNECTION_TYPE" => "http",
"BASE_CONFIG" => "test_config.json",
"RUN_ENV" => "test",
"HYP_BASE_METRICS" => "9092",
"HYP_BASE_TRACING_FMT" => "pretty",
@ -159,6 +159,7 @@ fn main() -> ExitCode {
"HYP_BASE_SIGNERS_TEST3_KEY" => "701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82",
"HYP_BASE_SIGNERS_TEST3_TYPE" => "hexKey",
"HYP_RELAYER_GASPAYMENTENFORCEMENTPOLICY_TYPE" => "none",
"HYP_RELAYER_ORIGINCHAINNAME" => "test1",
"HYP_RELAYER_WHITELIST" => r#"[{"sourceAddress": "*", "destinationDomain": ["13372", "13373"], "destinationAddress": "*"}]"#,
"HYP_RELAYER_SIGNEDCHECKPOINTPOLLINGINTERVAL" => "5",
"HYP_RELAYER_MULTISIGCHECKPOINTSYNCER_THRESHOLD" => "1",
@ -167,18 +168,19 @@ fn main() -> ExitCode {
};
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_OUTBOX_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_INBOXES_TEST2_CONNECTION_TYPE" => "httpQuorum",
"HYP_BASE_INBOXES_TEST3_CONNECTION_URLS" => "http://127.0.0.1:8545",
"HYP_BASE_INBOXES_TEST3_CONNECTION_TYPE" => "http",
"BASE_CONFIG" => "test1_config.json",
"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_CHAINS_TEST1_CONNECTION_TYPE" => "httpQuorum",
"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_CHAINS_TEST2_CONNECTION_TYPE" => "httpQuorum",
"HYP_BASE_CHAINS_TEST3_CONNECTION_URLS" => "http://127.0.0.1:8545",
"HYP_BASE_CHAINS_TEST3_CONNECTION_TYPE" => "http",
"BASE_CONFIG" => "test_config.json",
"RUN_ENV" => "test",
"HYP_BASE_METRICS" => "9091",
"HYP_BASE_TRACING_FMT" => "pretty",
"HYP_BASE_TRACING_LEVEL" => "info",
"HYP_BASE_DB" => validator_db.to_str().unwrap(),
"HYP_VALIDATOR_ORIGINCHAINNAME" => "test1",
"HYP_VALIDATOR_VALIDATOR_KEY" => "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
"HYP_VALIDATOR_VALIDATOR_TYPE" => "hexKey",
"HYP_VALIDATOR_REORGPERIOD" => "0",
@ -197,6 +199,7 @@ fn main() -> ExitCode {
println!("Building typescript...");
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("../"));
println!("Building relayer...");
@ -325,7 +328,7 @@ fn main() -> ExitCode {
.current_dir("../typescript/infra")
.stdout(Stdio::piped())
.spawn()
.expect("Failed tp start kathy");
.expect("Failed to start kathy");
let kathy_stdout = kathy.stdout.take().unwrap();
state.watchers.push(spawn(move || {
if log_all {
@ -428,7 +431,7 @@ fn assert_termination_invariants(num_expected_messages_processed: u32) {
.unwrap()
.lines()
.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())
.collect();
assert!(
@ -558,5 +561,9 @@ fn build_cmd(cmd: &[&str], log: impl AsRef<Path>, log_all: bool, wd: Option<&str
c.current_dir(wd);
}
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
*/
function count() public view returns (uint256) {
return tree.count;
function count() public view returns (uint32) {
// 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 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);
}

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

@ -13,9 +13,9 @@ interface IMailbox {
function process(bytes calldata _metadata, bytes calldata _message)
external;
function count() external view returns (uint256);
function count() external view returns (uint32);
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: {
branch: proof,
leaf: messageId,
index: nonce.toNumber(),
index: nonce,
},
message,
};
@ -93,14 +93,14 @@ export async function dispatchMessageAndReturnMetadata(
const root = await mailbox.root();
const signatures = await signCheckpoint(
root,
index.toNumber(),
index,
mailbox.address,
orderedValidators,
);
const origin = utils.parseMessage(proofAndMessage.message).origin;
const metadata = utils.formatMultisigIsmMetadata({
checkpointRoot: root,
checkpointIndex: index.toNumber(),
checkpointIndex: index,
originMailbox: mailbox.address,
proof: proofAndMessage.proof.branch,
signatures,

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

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

@ -255,25 +255,27 @@ export type RustCoreAddresses = {
multisigIsm: types.Address;
};
export type RustChainConfig = {
export type RustChainSetup = {
name: ChainName;
domain: string;
finalityBlocks: string;
addresses: RustCoreAddresses;
rpcStyle: 'ethereum';
finalityBlocks: string;
connection: RustConnection;
tracing: {
level: string;
fmt: 'json';
};
db: string;
index?: { from: string };
signer?: RustSigner;
};
export type RustConfig<Chain extends ChainName> = {
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

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

@ -5,7 +5,7 @@ import {
objMap,
} from '@hyperlane-xyz/sdk';
import { DeployEnvironment, RustChainConfig, RustConfig } from '../config';
import { DeployEnvironment, RustChainSetup, RustConfig } from '../config';
import { ConnectionType } from '../config/agent';
import { writeJSON } from '../utils/utils';
@ -16,6 +16,12 @@ export class HyperlaneCoreInfraDeployer<
const rustConfig: RustConfig<Chain> = {
environment,
chains: {},
signers: {},
db: 'db_path',
tracing: {
level: 'debug',
fmt: 'json',
},
};
objMap(this.configMap, (chain) => {
const contracts = this.deployedContracts[chain];
@ -30,7 +36,7 @@ export class HyperlaneCoreInfraDeployer<
return;
}
const chainConfig: RustChainConfig = {
const chainConfig: RustChainSetup = {
name: chain,
domain: metadata.id.toString(),
addresses: {
@ -44,11 +50,6 @@ export class HyperlaneCoreInfraDeployer<
type: ConnectionType.Http,
url: '',
},
tracing: {
level: 'debug',
fmt: 'json',
},
db: 'db_path',
};
const startingBlockNumber = this.startingBlockNumbers[chain];
@ -58,6 +59,6 @@ export class HyperlaneCoreInfraDeployer<
}
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) {
if (!fs.existsSync(directory)) {
throw Error("directory doesn't exist");
throw Error(`directory doesn't exist: ${directory}`);
}
return readJSONAtPath(path.join(directory, filename));
}
export function readJSONAtPath(filepath: string) {
if (!fs.existsSync(filepath)) {
throw Error("file doesn't exist");
throw Error(`file doesn't exist: ${filepath}`);
}
return JSON.parse(fs.readFileSync(filepath, 'utf8'));
}

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

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

Loading…
Cancel
Save