commit
7c3bd9d14a
@ -0,0 +1,2 @@ |
||||
typescript/sdk/src/cw-types/*.types.ts linguist-generated=true |
||||
rust/chains/hyperlane-ethereum/abis/*.abi.json linguist-generated=true |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,11 +1,13 @@ |
||||
compressionLevel: mixed |
||||
|
||||
enableGlobalCache: false |
||||
|
||||
enableScripts: false |
||||
|
||||
nodeLinker: node-modules |
||||
|
||||
plugins: |
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs |
||||
spec: "@yarnpkg/plugin-workspace-tools" |
||||
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs |
||||
spec: "https://mskelton.dev/yarn-outdated/v3" |
||||
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs |
||||
spec: "@yarnpkg/plugin-version" |
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.0.cjs |
||||
yarnPath: .yarn/releases/yarn-4.0.1.cjs |
||||
|
@ -0,0 +1,31 @@ |
||||
comment: |
||||
layout: "header, diff, flags, components" # show component info in the PR comment |
||||
|
||||
component_management: |
||||
default_rules: # default rules that will be inherited by all components |
||||
statuses: |
||||
- type: project # in this case every component that doens't have a status defined will have a project type one |
||||
target: auto |
||||
branches: |
||||
- "!main" |
||||
individual_components: |
||||
- component_id: module_core |
||||
name: core |
||||
paths: |
||||
- solidity/contracts/Mailbox.sol |
||||
- component_id: module_hooks |
||||
name: hooks |
||||
paths: |
||||
- solidity/contracts/hooks/** |
||||
- component_id: module_isms |
||||
name: isms |
||||
paths: |
||||
- solidity/contracts/isms/** |
||||
- component_id: module_token |
||||
name: token |
||||
paths: |
||||
- solidity/contracts/token/** |
||||
- component_id: module_middlewares |
||||
name: middlewares |
||||
paths: |
||||
- solidity/contracts/middleware/** |
@ -0,0 +1,2 @@ |
||||
pub(crate) mod builder; |
||||
pub(crate) mod processor; |
@ -0,0 +1,101 @@ |
||||
use std::{ |
||||
fmt::{Debug, Formatter}, |
||||
sync::Arc, |
||||
time::Duration, |
||||
}; |
||||
|
||||
use async_trait::async_trait; |
||||
use derive_new::new; |
||||
use eyre::Result; |
||||
use hyperlane_base::db::HyperlaneRocksDB; |
||||
use hyperlane_core::{HyperlaneDomain, MerkleTreeInsertion}; |
||||
use prometheus::IntGauge; |
||||
use tokio::sync::RwLock; |
||||
use tracing::debug; |
||||
|
||||
use crate::processor::ProcessorExt; |
||||
|
||||
use super::builder::MerkleTreeBuilder; |
||||
|
||||
/// Finds unprocessed merkle tree insertions and adds them to the prover sync
|
||||
#[derive(new)] |
||||
pub struct MerkleTreeProcessor { |
||||
db: HyperlaneRocksDB, |
||||
metrics: MerkleTreeProcessorMetrics, |
||||
prover_sync: Arc<RwLock<MerkleTreeBuilder>>, |
||||
#[new(default)] |
||||
leaf_index: u32, |
||||
} |
||||
|
||||
impl Debug for MerkleTreeProcessor { |
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
||||
write!( |
||||
f, |
||||
"MerkleTreeProcessor {{ leaf_index: {:?} }}", |
||||
self.leaf_index |
||||
) |
||||
} |
||||
} |
||||
|
||||
#[async_trait] |
||||
impl ProcessorExt for MerkleTreeProcessor { |
||||
/// The domain this processor is getting merkle tree hook insertions from.
|
||||
fn domain(&self) -> &HyperlaneDomain { |
||||
self.db.domain() |
||||
} |
||||
|
||||
/// One round of processing, extracted from infinite work loop for
|
||||
/// testing purposes.
|
||||
async fn tick(&mut self) -> Result<()> { |
||||
if let Some(insertion) = self.next_unprocessed_leaf()? { |
||||
// Feed the message to the prover sync
|
||||
self.prover_sync |
||||
.write() |
||||
.await |
||||
.ingest_message_id(insertion.message_id()) |
||||
.await?; |
||||
|
||||
// Increase the leaf index to move on to the next leaf
|
||||
self.leaf_index += 1; |
||||
} else { |
||||
tokio::time::sleep(Duration::from_secs(1)).await; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl MerkleTreeProcessor { |
||||
fn next_unprocessed_leaf(&mut self) -> Result<Option<MerkleTreeInsertion>> { |
||||
let leaf = if let Some(insertion) = self |
||||
.db |
||||
.retrieve_merkle_tree_insertion_by_leaf_index(&self.leaf_index)? |
||||
{ |
||||
// Update the metrics
|
||||
self.metrics |
||||
.max_leaf_index_gauge |
||||
.set(insertion.index() as i64); |
||||
Some(insertion) |
||||
} else { |
||||
debug!(leaf_index=?self.leaf_index, "No message found in DB for leaf index"); |
||||
None |
||||
}; |
||||
Ok(leaf) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug)] |
||||
pub struct MerkleTreeProcessorMetrics { |
||||
max_leaf_index_gauge: IntGauge, |
||||
} |
||||
|
||||
impl MerkleTreeProcessorMetrics { |
||||
pub fn new() -> Self { |
||||
Self { |
||||
max_leaf_index_gauge: IntGauge::new( |
||||
"max_leaf_index_gauge", |
||||
"The max merkle tree leaf index", |
||||
) |
||||
.unwrap(), |
||||
} |
||||
} |
||||
} |
@ -1,69 +0,0 @@ |
||||
use std::fmt::Debug; |
||||
|
||||
use async_trait::async_trait; |
||||
use derive_more::{AsRef, Deref}; |
||||
use derive_new::new; |
||||
|
||||
use eyre::{Context, Result}; |
||||
use hyperlane_base::MultisigCheckpointSyncer; |
||||
use hyperlane_core::{HyperlaneMessage, H256}; |
||||
|
||||
use crate::msg::metadata::BaseMetadataBuilder; |
||||
|
||||
use super::base::{MetadataToken, MultisigIsmMetadataBuilder, MultisigMetadata}; |
||||
|
||||
#[derive(Debug, Clone, Deref, new, AsRef)] |
||||
pub struct LegacyMultisigMetadataBuilder(BaseMetadataBuilder); |
||||
|
||||
#[async_trait] |
||||
impl MultisigIsmMetadataBuilder for LegacyMultisigMetadataBuilder { |
||||
fn token_layout(&self) -> Vec<MetadataToken> { |
||||
vec![ |
||||
MetadataToken::CheckpointRoot, |
||||
MetadataToken::CheckpointIndex, |
||||
MetadataToken::CheckpointMailbox, |
||||
MetadataToken::MerkleProof, |
||||
MetadataToken::Threshold, |
||||
MetadataToken::Signatures, |
||||
MetadataToken::Validators, |
||||
] |
||||
} |
||||
|
||||
async fn fetch_metadata( |
||||
&self, |
||||
validators: &[H256], |
||||
threshold: u8, |
||||
message: &HyperlaneMessage, |
||||
checkpoint_syncer: &MultisigCheckpointSyncer, |
||||
) -> Result<Option<MultisigMetadata>> { |
||||
const CTX: &str = "When fetching LegacyMultisig metadata"; |
||||
let highest_nonce = self.highest_known_nonce().await; |
||||
let Some(quorum_checkpoint) = checkpoint_syncer |
||||
.legacy_fetch_checkpoint_in_range( |
||||
validators, |
||||
threshold as usize, |
||||
message.nonce, |
||||
highest_nonce, |
||||
) |
||||
.await |
||||
.context(CTX)? |
||||
else { |
||||
return Ok(None); |
||||
}; |
||||
|
||||
let Some(proof) = self |
||||
.get_proof(message.nonce, quorum_checkpoint.checkpoint) |
||||
.await |
||||
.context(CTX)? |
||||
else { |
||||
return Ok(None); |
||||
}; |
||||
|
||||
Ok(Some(MultisigMetadata::new( |
||||
quorum_checkpoint.checkpoint, |
||||
quorum_checkpoint.signatures, |
||||
None, |
||||
Some(proof), |
||||
))) |
||||
} |
||||
} |
@ -1,10 +1,8 @@ |
||||
mod base; |
||||
mod legacy_multisig; |
||||
mod merkle_root_multisig; |
||||
mod message_id_multisig; |
||||
|
||||
pub use base::{MetadataToken, MultisigIsmMetadataBuilder, MultisigMetadata}; |
||||
|
||||
pub use legacy_multisig::LegacyMultisigMetadataBuilder; |
||||
pub use merkle_root_multisig::MerkleRootMultisigMetadataBuilder; |
||||
pub use message_id_multisig::MessageIdMultisigMetadataBuilder; |
||||
|
@ -0,0 +1,37 @@ |
||||
use std::fmt::Debug; |
||||
|
||||
use async_trait::async_trait; |
||||
use derive_new::new; |
||||
use eyre::Result; |
||||
use hyperlane_core::HyperlaneDomain; |
||||
use tokio::task::JoinHandle; |
||||
use tracing::{info_span, instrument, instrument::Instrumented, Instrument}; |
||||
|
||||
#[async_trait] |
||||
pub trait ProcessorExt: Send + Debug { |
||||
/// The domain this processor is getting messages from.
|
||||
fn domain(&self) -> &HyperlaneDomain; |
||||
|
||||
/// One round of processing, extracted from infinite work loop for
|
||||
/// testing purposes.
|
||||
async fn tick(&mut self) -> Result<()>; |
||||
} |
||||
|
||||
#[derive(new)] |
||||
pub struct Processor { |
||||
ticker: Box<dyn ProcessorExt>, |
||||
} |
||||
|
||||
impl Processor { |
||||
pub fn spawn(self) -> Instrumented<JoinHandle<Result<()>>> { |
||||
let span = info_span!("MessageProcessor"); |
||||
tokio::spawn(async move { self.main_loop().await }).instrument(span) |
||||
} |
||||
|
||||
#[instrument(ret, err, skip(self), level = "info", fields(domain=%self.ticker.domain()))] |
||||
async fn main_loop(mut self) -> Result<()> { |
||||
loop { |
||||
self.ticker.tick().await?; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,156 @@ |
||||
[ |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "address", |
||||
"name": "_mailbox", |
||||
"type": "address" |
||||
} |
||||
], |
||||
"stateMutability": "nonpayable", |
||||
"type": "constructor" |
||||
}, |
||||
{ |
||||
"anonymous": false, |
||||
"inputs": [ |
||||
{ |
||||
"indexed": false, |
||||
"internalType": "bytes32", |
||||
"name": "messageId", |
||||
"type": "bytes32" |
||||
}, |
||||
{ |
||||
"indexed": false, |
||||
"internalType": "uint32", |
||||
"name": "index", |
||||
"type": "uint32" |
||||
} |
||||
], |
||||
"name": "InsertedIntoTree", |
||||
"type": "event" |
||||
}, |
||||
{ |
||||
"inputs": [], |
||||
"name": "count", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "uint32", |
||||
"name": "", |
||||
"type": "uint32" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [], |
||||
"name": "deployedBlock", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [], |
||||
"name": "latestCheckpoint", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "bytes32", |
||||
"name": "", |
||||
"type": "bytes32" |
||||
}, |
||||
{ |
||||
"internalType": "uint32", |
||||
"name": "", |
||||
"type": "uint32" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "bytes", |
||||
"name": "", |
||||
"type": "bytes" |
||||
}, |
||||
{ |
||||
"internalType": "bytes", |
||||
"name": "message", |
||||
"type": "bytes" |
||||
} |
||||
], |
||||
"name": "postDispatch", |
||||
"outputs": [], |
||||
"stateMutability": "payable", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [ |
||||
{ |
||||
"internalType": "bytes", |
||||
"name": "", |
||||
"type": "bytes" |
||||
}, |
||||
{ |
||||
"internalType": "bytes", |
||||
"name": "", |
||||
"type": "bytes" |
||||
} |
||||
], |
||||
"name": "quoteDispatch", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"stateMutability": "pure", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [], |
||||
"name": "root", |
||||
"outputs": [ |
||||
{ |
||||
"internalType": "bytes32", |
||||
"name": "", |
||||
"type": "bytes32" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
}, |
||||
{ |
||||
"inputs": [], |
||||
"name": "tree", |
||||
"outputs": [ |
||||
{ |
||||
"components": [ |
||||
{ |
||||
"internalType": "bytes32[32]", |
||||
"name": "branch", |
||||
"type": "bytes32[32]" |
||||
}, |
||||
{ |
||||
"internalType": "uint256", |
||||
"name": "count", |
||||
"type": "uint256" |
||||
} |
||||
], |
||||
"internalType": "struct MerkleLib.Tree", |
||||
"name": "", |
||||
"type": "tuple" |
||||
} |
||||
], |
||||
"stateMutability": "view", |
||||
"type": "function" |
||||
} |
||||
] |
@ -0,0 +1,249 @@ |
||||
#![allow(missing_docs)] |
||||
use std::num::NonZeroU64; |
||||
use std::ops::RangeInclusive; |
||||
use std::sync::Arc; |
||||
|
||||
use async_trait::async_trait; |
||||
use ethers::prelude::Middleware; |
||||
use hyperlane_core::accumulator::incremental::IncrementalMerkle; |
||||
use tracing::instrument; |
||||
|
||||
use hyperlane_core::{ |
||||
ChainCommunicationError, ChainResult, Checkpoint, ContractLocator, HyperlaneChain, |
||||
HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexer, LogMeta, MerkleTreeHook, |
||||
MerkleTreeInsertion, SequenceIndexer, H256, |
||||
}; |
||||
|
||||
use crate::contracts::merkle_tree_hook::{MerkleTreeHook as MerkleTreeHookContract, Tree}; |
||||
use crate::trait_builder::BuildableWithProvider; |
||||
use crate::tx::call_with_lag; |
||||
use crate::EthereumProvider; |
||||
|
||||
// We don't need the reverse of this impl, so it's ok to disable the clippy lint
|
||||
#[allow(clippy::from_over_into)] |
||||
impl Into<IncrementalMerkle> for Tree { |
||||
fn into(self) -> IncrementalMerkle { |
||||
let branch = self |
||||
.branch |
||||
.iter() |
||||
.map(|v| v.into()) |
||||
.collect::<Vec<_>>() |
||||
// we're iterating over a fixed-size array and want to collect into a
|
||||
// fixed-size array of the same size (32), so this is safe
|
||||
.try_into() |
||||
.unwrap(); |
||||
IncrementalMerkle::new(branch, self.count.as_usize()) |
||||
} |
||||
} |
||||
|
||||
pub struct MerkleTreeHookBuilder {} |
||||
|
||||
#[async_trait] |
||||
impl BuildableWithProvider for MerkleTreeHookBuilder { |
||||
type Output = Box<dyn MerkleTreeHook>; |
||||
|
||||
async fn build_with_provider<M: Middleware + 'static>( |
||||
&self, |
||||
provider: M, |
||||
locator: &ContractLocator, |
||||
) -> Self::Output { |
||||
Box::new(EthereumMerkleTreeHook::new(Arc::new(provider), locator)) |
||||
} |
||||
} |
||||
|
||||
pub struct MerkleTreeHookIndexerBuilder { |
||||
pub reorg_period: u32, |
||||
} |
||||
|
||||
#[async_trait] |
||||
impl BuildableWithProvider for MerkleTreeHookIndexerBuilder { |
||||
type Output = Box<dyn SequenceIndexer<MerkleTreeInsertion>>; |
||||
|
||||
async fn build_with_provider<M: Middleware + 'static>( |
||||
&self, |
||||
provider: M, |
||||
locator: &ContractLocator, |
||||
) -> Self::Output { |
||||
Box::new(EthereumMerkleTreeHookIndexer::new( |
||||
Arc::new(provider), |
||||
locator, |
||||
self.reorg_period, |
||||
)) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug)] |
||||
/// Struct that retrieves event data for an Ethereum MerkleTreeHook
|
||||
pub struct EthereumMerkleTreeHookIndexer<M> |
||||
where |
||||
M: Middleware, |
||||
{ |
||||
contract: Arc<MerkleTreeHookContract<M>>, |
||||
provider: Arc<M>, |
||||
reorg_period: u32, |
||||
} |
||||
|
||||
impl<M> EthereumMerkleTreeHookIndexer<M> |
||||
where |
||||
M: Middleware + 'static, |
||||
{ |
||||
/// Create new EthereumMerkleTreeHookIndexer
|
||||
pub fn new(provider: Arc<M>, locator: &ContractLocator, reorg_period: u32) -> Self { |
||||
Self { |
||||
contract: Arc::new(MerkleTreeHookContract::new( |
||||
locator.address, |
||||
provider.clone(), |
||||
)), |
||||
provider, |
||||
reorg_period, |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[async_trait] |
||||
impl<M> Indexer<MerkleTreeInsertion> for EthereumMerkleTreeHookIndexer<M> |
||||
where |
||||
M: Middleware + 'static, |
||||
{ |
||||
#[instrument(err, skip(self))] |
||||
async fn fetch_logs( |
||||
&self, |
||||
range: RangeInclusive<u32>, |
||||
) -> ChainResult<Vec<(MerkleTreeInsertion, LogMeta)>> { |
||||
let events = self |
||||
.contract |
||||
.inserted_into_tree_filter() |
||||
.from_block(*range.start()) |
||||
.to_block(*range.end()) |
||||
.query_with_meta() |
||||
.await?; |
||||
|
||||
let logs = events |
||||
.into_iter() |
||||
.map(|(log, log_meta)| { |
||||
( |
||||
MerkleTreeInsertion::new(log.index, H256::from(log.message_id)), |
||||
log_meta.into(), |
||||
) |
||||
}) |
||||
.collect(); |
||||
Ok(logs) |
||||
} |
||||
|
||||
#[instrument(level = "debug", err, ret, skip(self))] |
||||
async fn get_finalized_block_number(&self) -> ChainResult<u32> { |
||||
Ok(self |
||||
.provider |
||||
.get_block_number() |
||||
.await |
||||
.map_err(ChainCommunicationError::from_other)? |
||||
.as_u32() |
||||
.saturating_sub(self.reorg_period)) |
||||
} |
||||
} |
||||
|
||||
#[async_trait] |
||||
impl<M> SequenceIndexer<MerkleTreeInsertion> for EthereumMerkleTreeHookIndexer<M> |
||||
where |
||||
M: Middleware + 'static, |
||||
{ |
||||
async fn sequence_and_tip(&self) -> ChainResult<(Option<u32>, u32)> { |
||||
// The InterchainGasPaymasterIndexerBuilder must return a `SequenceIndexer` type.
|
||||
// It's fine if only a blanket implementation is provided for EVM chains, since their
|
||||
// indexing only uses the `Index` trait, which is a supertrait of `SequenceIndexer`.
|
||||
// TODO: if `SequenceIndexer` turns out to not depend on `Indexer` at all, then the supertrait
|
||||
// dependency could be removed, even if the builder would still need to return a type that is both
|
||||
// ``SequenceIndexer` and `Indexer`.
|
||||
let tip = self.get_finalized_block_number().await?; |
||||
Ok((None, tip)) |
||||
} |
||||
} |
||||
|
||||
/// A reference to a Mailbox contract on some Ethereum chain
|
||||
#[derive(Debug)] |
||||
pub struct EthereumMerkleTreeHook<M> |
||||
where |
||||
M: Middleware, |
||||
{ |
||||
contract: Arc<MerkleTreeHookContract<M>>, |
||||
domain: HyperlaneDomain, |
||||
provider: Arc<M>, |
||||
} |
||||
|
||||
impl<M> EthereumMerkleTreeHook<M> |
||||
where |
||||
M: Middleware, |
||||
{ |
||||
/// 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(MerkleTreeHookContract::new( |
||||
locator.address, |
||||
provider.clone(), |
||||
)), |
||||
domain: locator.domain.clone(), |
||||
provider, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<M> HyperlaneChain for EthereumMerkleTreeHook<M> |
||||
where |
||||
M: Middleware + 'static, |
||||
{ |
||||
fn domain(&self) -> &HyperlaneDomain { |
||||
&self.domain |
||||
} |
||||
|
||||
fn provider(&self) -> Box<dyn HyperlaneProvider> { |
||||
Box::new(EthereumProvider::new( |
||||
self.provider.clone(), |
||||
self.domain.clone(), |
||||
)) |
||||
} |
||||
} |
||||
|
||||
impl<M> HyperlaneContract for EthereumMerkleTreeHook<M> |
||||
where |
||||
M: Middleware + 'static, |
||||
{ |
||||
fn address(&self) -> H256 { |
||||
self.contract.address().into() |
||||
} |
||||
} |
||||
|
||||
#[async_trait] |
||||
impl<M> MerkleTreeHook for EthereumMerkleTreeHook<M> |
||||
where |
||||
M: Middleware + 'static, |
||||
{ |
||||
#[instrument(skip(self))] |
||||
async fn latest_checkpoint(&self, maybe_lag: Option<NonZeroU64>) -> ChainResult<Checkpoint> { |
||||
let call = |
||||
call_with_lag(self.contract.latest_checkpoint(), &self.provider, maybe_lag).await?; |
||||
|
||||
let (root, index) = call.call().await?; |
||||
Ok(Checkpoint { |
||||
merkle_tree_hook_address: self.address(), |
||||
mailbox_domain: self.domain.id(), |
||||
root: root.into(), |
||||
index, |
||||
}) |
||||
} |
||||
|
||||
#[instrument(skip(self))] |
||||
#[allow(clippy::needless_range_loop)] |
||||
async fn tree(&self, maybe_lag: Option<NonZeroU64>) -> ChainResult<IncrementalMerkle> { |
||||
let call = call_with_lag(self.contract.tree(), &self.provider, maybe_lag).await?; |
||||
|
||||
Ok(call.call().await?.into()) |
||||
} |
||||
|
||||
#[instrument(skip(self))] |
||||
async fn count(&self, maybe_lag: Option<NonZeroU64>) -> ChainResult<u32> { |
||||
let call = call_with_lag(self.contract.count(), &self.provider, maybe_lag).await?; |
||||
let count = call.call().await?; |
||||
Ok(count) |
||||
} |
||||
} |
@ -0,0 +1,101 @@ |
||||
use std::{num::NonZeroU64, ops::RangeInclusive}; |
||||
|
||||
use async_trait::async_trait; |
||||
use derive_new::new; |
||||
use hyperlane_core::{ |
||||
accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint, |
||||
Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, SequenceIndexer, |
||||
}; |
||||
use hyperlane_sealevel_mailbox::accounts::OutboxAccount; |
||||
use solana_sdk::commitment_config::CommitmentConfig; |
||||
use tracing::instrument; |
||||
|
||||
use crate::SealevelMailbox; |
||||
|
||||
#[async_trait] |
||||
impl MerkleTreeHook for SealevelMailbox { |
||||
#[instrument(err, ret, skip(self))] |
||||
async fn tree(&self, lag: Option<NonZeroU64>) -> ChainResult<IncrementalMerkle> { |
||||
assert!( |
||||
lag.is_none(), |
||||
"Sealevel does not support querying point-in-time" |
||||
); |
||||
|
||||
let outbox_account = self |
||||
.rpc_client |
||||
.get_account_with_commitment(&self.outbox.0, CommitmentConfig::finalized()) |
||||
.await |
||||
.map_err(ChainCommunicationError::from_other)? |
||||
.value |
||||
.ok_or_else(|| { |
||||
ChainCommunicationError::from_other_str("Could not find account data") |
||||
})?; |
||||
let outbox = OutboxAccount::fetch(&mut outbox_account.data.as_ref()) |
||||
.map_err(ChainCommunicationError::from_other)? |
||||
.into_inner(); |
||||
|
||||
Ok(outbox.tree) |
||||
} |
||||
|
||||
#[instrument(err, ret, skip(self))] |
||||
async fn latest_checkpoint(&self, lag: Option<NonZeroU64>) -> ChainResult<Checkpoint> { |
||||
assert!( |
||||
lag.is_none(), |
||||
"Sealevel does not support querying point-in-time" |
||||
); |
||||
|
||||
let tree = self.tree(lag).await?; |
||||
|
||||
let root = tree.root(); |
||||
let count: u32 = tree |
||||
.count() |
||||
.try_into() |
||||
.map_err(ChainCommunicationError::from_other)?; |
||||
let index = count.checked_sub(1).ok_or_else(|| { |
||||
ChainCommunicationError::from_contract_error_str( |
||||
"Outbox is empty, cannot compute checkpoint", |
||||
) |
||||
})?; |
||||
let checkpoint = Checkpoint { |
||||
merkle_tree_hook_address: self.program_id.to_bytes().into(), |
||||
mailbox_domain: self.domain.id(), |
||||
root, |
||||
index, |
||||
}; |
||||
Ok(checkpoint) |
||||
} |
||||
|
||||
#[instrument(err, ret, skip(self))] |
||||
async fn count(&self, _maybe_lag: Option<NonZeroU64>) -> ChainResult<u32> { |
||||
let tree = self.tree(_maybe_lag).await?; |
||||
|
||||
tree.count() |
||||
.try_into() |
||||
.map_err(ChainCommunicationError::from_other) |
||||
} |
||||
} |
||||
|
||||
/// Struct that retrieves event data for a Sealevel merkle tree hook contract
|
||||
#[derive(Debug, new)] |
||||
pub struct SealevelMerkleTreeHookIndexer {} |
||||
|
||||
#[async_trait] |
||||
impl Indexer<MerkleTreeInsertion> for SealevelMerkleTreeHookIndexer { |
||||
async fn fetch_logs( |
||||
&self, |
||||
_range: RangeInclusive<u32>, |
||||
) -> ChainResult<Vec<(MerkleTreeInsertion, LogMeta)>> { |
||||
Ok(vec![]) |
||||
} |
||||
|
||||
async fn get_finalized_block_number(&self) -> ChainResult<u32> { |
||||
Ok(0) |
||||
} |
||||
} |
||||
|
||||
#[async_trait] |
||||
impl SequenceIndexer<MerkleTreeInsertion> for SealevelMerkleTreeHookIndexer { |
||||
async fn sequence_and_tip(&self) -> ChainResult<(Option<u32>, u32)> { |
||||
Ok((None, 0)) |
||||
} |
||||
} |
@ -0,0 +1,633 @@ |
||||
{ |
||||
"chains": { |
||||
"arbitrum": { |
||||
"chainId": 42161, |
||||
"domainId": 42161, |
||||
"name": "arbitrum", |
||||
"protocol": "ethereum", |
||||
"displayName": "Arbitrum", |
||||
"nativeToken": { |
||||
"name": "Ether", |
||||
"symbol": "ETH", |
||||
"decimals": 18 |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://arb1.arbitrum.io/rpc" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "Arbiscan", |
||||
"url": "https://arbiscan.io", |
||||
"apiUrl": "https://api.arbiscan.io/api", |
||||
"family": "etherscan" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 1, |
||||
"reorgPeriod": 0, |
||||
"estimateBlockTime": 3 |
||||
}, |
||||
"gasCurrencyCoinGeckoId": "ethereum", |
||||
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-arbitrum.safe.global/", |
||||
"storageGasOracle": "0xD3805207b65d99C075ceA938Fa7c0587026a5DF5", |
||||
"proxyAdmin": "0x80Cebd56A65e46c474a1A101e89E76C4c51D179c", |
||||
"merkleRootMultisigIsmFactory": "0x3C330D4A2e2b8443AFaB8E326E64ab4251B7Eae0", |
||||
"messageIdMultisigIsmFactory": "0x12Df53079d399a47e9E730df095b712B0FDFA791", |
||||
"aggregationIsmFactory": "0xD4883084389fC1Eeb4dAfB2ADcFc36B711c310EB", |
||||
"aggregationHookFactory": "0x9B5f440bBb64Fee337F37e03362b628711Ea09C7", |
||||
"routingIsmFactory": "0xC020F8A7b00178dFA0fcC75C159e14b79F8e5c63", |
||||
"merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930", |
||||
"interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", |
||||
"aggregationHook": "0xe0cb37cFc47296f1c4eD77EFf92Aed478644d10c", |
||||
"protocolFee": "0xD0199067DACb8526e7dc524a9a7DCBb57Cd25421", |
||||
"mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", |
||||
"validatorAnnounce": "0x1df063280C4166AF9a725e3828b4dAC6c7113B08", |
||||
"index": { |
||||
"from": 145551152 |
||||
} |
||||
}, |
||||
"avalanche": { |
||||
"chainId": 43114, |
||||
"domainId": 43114, |
||||
"name": "avalanche", |
||||
"protocol": "ethereum", |
||||
"displayName": "Avalanche", |
||||
"nativeToken": { |
||||
"decimals": 18, |
||||
"name": "Avalanche", |
||||
"symbol": "AVAX" |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://api.avax.network/ext/bc/C/rpc", |
||||
"pagination": { |
||||
"maxBlockRange": 100000, |
||||
"minBlockNumber": 6765067 |
||||
} |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "SnowTrace", |
||||
"url": "https://snowtrace.io", |
||||
"apiUrl": "https://api.snowtrace.io/api", |
||||
"family": "other" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 3, |
||||
"reorgPeriod": 3, |
||||
"estimateBlockTime": 2 |
||||
}, |
||||
"gasCurrencyCoinGeckoId": "avalanche-2", |
||||
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-avalanche.safe.global/", |
||||
"storageGasOracle": "0x175821F30AdCAA4bbB72Ce98eF76C2E0De2C3f21", |
||||
"proxyAdmin": "0xd7CF8c05fd81b8cA7CfF8E6C49B08a9D63265c9B", |
||||
"merkleRootMultisigIsmFactory": "0x896cF1D1B66cD211633eDd589fF158E8Cfaf9B54", |
||||
"messageIdMultisigIsmFactory": "0x8819D653DF5b1FC0DdB32189a2704E471AF8483c", |
||||
"aggregationIsmFactory": "0xa5E13796eB7d2EDCc88012c8cfF90D69B51FcF9f", |
||||
"aggregationHookFactory": "0x3bF6Ac986C7Af9A9Ac356C0e99C0041EFd8D96e7", |
||||
"routingIsmFactory": "0xA9Ddc70f50009aF8bDB312aA757B4304b0F7BbB3", |
||||
"merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A", |
||||
"interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", |
||||
"aggregationHook": "0x0165a22BA489F7DA37DAf6397781777D9FCB5708", |
||||
"protocolFee": "0xEc4AdA26E51f2685279F37C8aE62BeAd8212D597", |
||||
"mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", |
||||
"validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f", |
||||
"index": { |
||||
"from": 37133307 |
||||
} |
||||
}, |
||||
"base": { |
||||
"chainId": 8453, |
||||
"domainId": 8453, |
||||
"name": "base", |
||||
"protocol": "ethereum", |
||||
"displayName": "Base", |
||||
"nativeToken": { |
||||
"name": "Ether", |
||||
"symbol": "ETH", |
||||
"decimals": 18 |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://base.publicnode.com/" |
||||
}, |
||||
{ |
||||
"http": "https://mainnet.base.org" |
||||
}, |
||||
{ |
||||
"http": "https://base.blockpi.network/v1/rpc/public" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "BaseScan", |
||||
"url": "https://basescan.org", |
||||
"apiUrl": "https://api.basescan.org/api", |
||||
"family": "etherscan" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 1, |
||||
"reorgPeriod": 1, |
||||
"estimateBlockTime": 2 |
||||
}, |
||||
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-base.safe.global/", |
||||
"merkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", |
||||
"messageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", |
||||
"aggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", |
||||
"aggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", |
||||
"routingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", |
||||
"proxyAdmin": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", |
||||
"mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", |
||||
"merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117", |
||||
"storageGasOracle": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", |
||||
"interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", |
||||
"aggregationHook": "0x13f3d4B0Ee0a713430fded9E18f7fb6c91A6E41F", |
||||
"protocolFee": "0x99ca8c74cE7Cfa9d72A51fbb05F9821f5f826b3a", |
||||
"validatorAnnounce": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B", |
||||
"index": { |
||||
"from": 5959667 |
||||
} |
||||
}, |
||||
"bsc": { |
||||
"chainId": 56, |
||||
"domainId": 56, |
||||
"name": "bsc", |
||||
"protocol": "ethereum", |
||||
"displayName": "Binance Smart Chain", |
||||
"displayNameShort": "Binance", |
||||
"nativeToken": { |
||||
"decimals": 18, |
||||
"name": "BNB", |
||||
"symbol": "BNB" |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://bsc-dataseed.binance.org" |
||||
}, |
||||
{ |
||||
"http": "https://rpc.ankr.com/bsc" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "BscScan", |
||||
"url": "https://bscscan.com", |
||||
"apiUrl": "https://api.bscscan.com/api", |
||||
"family": "etherscan" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 1, |
||||
"reorgPeriod": 15, |
||||
"estimateBlockTime": 3 |
||||
}, |
||||
"gasCurrencyCoinGeckoId": "binancecoin", |
||||
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-bsc.safe.global/", |
||||
"transactionOverrides": { |
||||
"gasPrice": 7000000000 |
||||
}, |
||||
"storageGasOracle": "0x91d23D603d60445411C06e6443d81395593B7940", |
||||
"proxyAdmin": "0x65993Af9D0D3a64ec77590db7ba362D6eB78eF70", |
||||
"merkleRootMultisigIsmFactory": "0xfADBc81Ca8A957F1Bf7c78bCc575b28DBDE042b6", |
||||
"messageIdMultisigIsmFactory": "0x4B1d8352E35e3BDE36dF5ED2e73C24E35c4a96b7", |
||||
"aggregationIsmFactory": "0x38B3878c4fb44d201DA924c4a04bae3EE728c065", |
||||
"aggregationHookFactory": "0xe70E86a7D1e001D419D71F960Cb6CaD59b6A3dB6", |
||||
"routingIsmFactory": "0xc40481D13419BC8090e6AD07074Ef39E538c09CE", |
||||
"mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", |
||||
"merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26", |
||||
"interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", |
||||
"aggregationHook": "0x402Fc106576462a892355d69ACF03D46A888ae88", |
||||
"protocolFee": "0xA8Aa5f14a5463a78E45CC068F11c867949F3E367", |
||||
"validatorAnnounce": "0x7024078130D9c2100fEA474DAD009C2d1703aCcd", |
||||
"index": { |
||||
"from": 33068482 |
||||
} |
||||
}, |
||||
"celo": { |
||||
"chainId": 42220, |
||||
"domainId": 42220, |
||||
"name": "celo", |
||||
"protocol": "ethereum", |
||||
"displayName": "Celo", |
||||
"nativeToken": { |
||||
"decimals": 18, |
||||
"name": "CELO", |
||||
"symbol": "CELO" |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://forno.celo.org" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "CeloScan", |
||||
"url": "https://celoscan.io", |
||||
"apiUrl": "https://api.celoscan.io/api", |
||||
"family": "etherscan" |
||||
}, |
||||
{ |
||||
"name": "Blockscout", |
||||
"url": "https://explorer.celo.org", |
||||
"apiUrl": "https://explorer.celo.org/mainnet/api", |
||||
"family": "blockscout" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 1, |
||||
"reorgPeriod": 0, |
||||
"estimateBlockTime": 5 |
||||
}, |
||||
"gnosisSafeTransactionServiceUrl": "https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/", |
||||
"storageGasOracle": "0xD9A9966E7dA9a7f0032bF449FB12696a638E673C", |
||||
"proxyAdmin": "0x90f9a2E9eCe93516d65FdaB726a3c62F5960a1b9", |
||||
"merkleRootMultisigIsmFactory": "0x4C96a1abc44dc846775CE702C9E9BE821D3b487c", |
||||
"messageIdMultisigIsmFactory": "0xaB402f227e892Ef37C105bf06619c0fa106a1fB2", |
||||
"aggregationIsmFactory": "0x1722dd970a1F56040712129f5Eeb76B003fd7500", |
||||
"aggregationHookFactory": "0xc3745652EFB8555A8b064A0EA78d295133d326D2", |
||||
"routingIsmFactory": "0xec748b5623f0B50E4c5eB1CFa7Bd46C3213608b6", |
||||
"merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366", |
||||
"interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", |
||||
"aggregationHook": "0xc65890329066FB20c339Bc5C22f1756e9D3a4fF5", |
||||
"protocolFee": "0x89886d431f9c3eEE64DCD6dAbA3f7D689D98D899", |
||||
"mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", |
||||
"validatorAnnounce": "0xCeF677b65FDaA6804d4403083bb12B8dB3991FE1", |
||||
"index": { |
||||
"from": 22208016 |
||||
} |
||||
}, |
||||
"ethereum": { |
||||
"chainId": 1, |
||||
"domainId": 1, |
||||
"name": "ethereum", |
||||
"protocol": "ethereum", |
||||
"displayName": "Ethereum", |
||||
"nativeToken": { |
||||
"name": "Ether", |
||||
"symbol": "ETH", |
||||
"decimals": 18 |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" |
||||
}, |
||||
{ |
||||
"http": "https://cloudflare-eth.com" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "Etherscan", |
||||
"url": "https://etherscan.io", |
||||
"apiUrl": "https://api.etherscan.io/api", |
||||
"family": "etherscan" |
||||
}, |
||||
{ |
||||
"name": "Blockscout", |
||||
"url": "https://blockscout.com/eth/mainnet", |
||||
"apiUrl": "https://blockscout.com/eth/mainnet/api", |
||||
"family": "blockscout" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 3, |
||||
"reorgPeriod": 14, |
||||
"estimateBlockTime": 13 |
||||
}, |
||||
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-mainnet.safe.global/", |
||||
"transactionOverrides": { |
||||
"maxFeePerGas": 150000000000, |
||||
"maxPriorityFeePerGas": 5000000000 |
||||
}, |
||||
"storageGasOracle": "0xc9a103990A8dB11b4f627bc5CD1D0c2685484Ec5", |
||||
"proxyAdmin": "0x75EE15Ee1B4A75Fa3e2fDF5DF3253c25599cc659", |
||||
"merkleRootMultisigIsmFactory": "0x47e8aF9e30C32Ab91060ED587894288786761B45", |
||||
"messageIdMultisigIsmFactory": "0xfA21D9628ADce86531854C2B7ef00F07394B0B69", |
||||
"aggregationIsmFactory": "0x46FA191Ad972D9674Ed752B69f9659A0d7b22846", |
||||
"aggregationHookFactory": "0x6D2555A8ba483CcF4409C39013F5e9a3285D3C9E", |
||||
"routingIsmFactory": "0xCb74c6aE411236CEE6803619916694BE86cF5987", |
||||
"merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA", |
||||
"interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", |
||||
"aggregationHook": "0xb87AC8EA4533AE017604E44470F7c1E550AC6F10", |
||||
"protocolFee": "0x8B05BF30F6247a90006c5837eA63C7905D79e6d8", |
||||
"mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", |
||||
"validatorAnnounce": "0xCe74905e51497b4adD3639366708b821dcBcff96", |
||||
"index": { |
||||
"from": 18466263 |
||||
} |
||||
}, |
||||
"gnosis": { |
||||
"chainId": 100, |
||||
"domainId": 100, |
||||
"name": "gnosis", |
||||
"protocol": "ethereum", |
||||
"displayName": "Gnosis", |
||||
"nativeToken": { |
||||
"name": "xDai", |
||||
"symbol": "xDai", |
||||
"decimals": 18 |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://rpc.gnosischain.com", |
||||
"pagination": { |
||||
"maxBlockRange": 10000, |
||||
"minBlockNumber": 25997478 |
||||
} |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "GnosisScan", |
||||
"url": "https://gnosisscan.io", |
||||
"apiUrl": "https://api.gnosisscan.io/api", |
||||
"family": "etherscan" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 1, |
||||
"reorgPeriod": 14, |
||||
"estimateBlockTime": 5 |
||||
}, |
||||
"gasCurrencyCoinGeckoId": "xdai", |
||||
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-gnosis-chain.safe.global/", |
||||
"storageGasOracle": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9", |
||||
"proxyAdmin": "0x81a92A1a272cb09d7b4970b07548463dC7aE0cB7", |
||||
"merkleRootMultisigIsmFactory": "0x8E273260EAd8B72A085B19346A676d355740e875", |
||||
"messageIdMultisigIsmFactory": "0x603f46cc520d2fc22957b81e206408590808F02F", |
||||
"aggregationIsmFactory": "0x11EF91d17c5ad3330DbCa709a8841743d3Af6819", |
||||
"aggregationHookFactory": "0xbC8AA096dabDf4A0200BB9f8D4Cbb644C3D86d7B", |
||||
"mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", |
||||
"routingIsmFactory": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", |
||||
"merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", |
||||
"interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", |
||||
"aggregationHook": "0xdD1FA1C12496474c1dDC67a658Ba81437F818861", |
||||
"protocolFee": "0x9c2214467Daf9e2e1F45b36d08ce0b9C65BFeA88", |
||||
"validatorAnnounce": "0x87ED6926abc9E38b9C7C19f835B41943b622663c", |
||||
"index": { |
||||
"from": 30715963 |
||||
} |
||||
}, |
||||
"moonbeam": { |
||||
"chainId": 1284, |
||||
"domainId": 1284, |
||||
"name": "moonbeam", |
||||
"protocol": "ethereum", |
||||
"displayName": "Moonbeam", |
||||
"nativeToken": { |
||||
"decimals": 18, |
||||
"name": "GLMR", |
||||
"symbol": "GLMR" |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://rpc.api.moonbeam.network" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "MoonScan", |
||||
"url": "https://moonscan.io", |
||||
"apiUrl": "https://api-moonbeam.moonscan.io/api", |
||||
"family": "etherscan" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 2, |
||||
"reorgPeriod": 2, |
||||
"estimateBlockTime": 12 |
||||
}, |
||||
"gnosisSafeTransactionServiceUrl": "https://transaction.multisig.moonbeam.network", |
||||
"storageGasOracle": "0x448b7ADB0dA36d41AA2AfDc9d63b97541A7b3819", |
||||
"proxyAdmin": "0x6A9cdA3dd1F593983BFd142Eb35e6ce4137bd5ce", |
||||
"merkleRootMultisigIsmFactory": "0xE2f485bc031Feb5a4C41C1967bf028653d75f0C3", |
||||
"messageIdMultisigIsmFactory": "0x84Df48F8f241f11d0fA302d09d73030429Bd9C73", |
||||
"aggregationIsmFactory": "0x40c6Abcb6A2CdC8882d4bEcaC47927005c7Bb8c2", |
||||
"aggregationHookFactory": "0x59cC3E7A49DdC4893eB8754c7908f96072A7DbE8", |
||||
"routingIsmFactory": "0x98Aa6239FfCcEc73A662a5e5e26Bc3fD7c7291B7", |
||||
"mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", |
||||
"merkleTreeHook": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", |
||||
"interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", |
||||
"aggregationHook": "0x23cca255aE83F57F39EAf9D14fB9FdaDF22D5863", |
||||
"protocolFee": "0xCd3e29A9D293DcC7341295996a118913F7c582c0", |
||||
"validatorAnnounce": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F", |
||||
"index": { |
||||
"from": 4763137 |
||||
} |
||||
}, |
||||
"optimism": { |
||||
"chainId": 10, |
||||
"domainId": 10, |
||||
"name": "optimism", |
||||
"protocol": "ethereum", |
||||
"displayName": "Optimism", |
||||
"nativeToken": { |
||||
"name": "Ether", |
||||
"symbol": "ETH", |
||||
"decimals": 18 |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://mainnet.optimism.io" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "Etherscan", |
||||
"url": "https://optimistic.etherscan.io", |
||||
"apiUrl": "https://api-optimistic.etherscan.io/api", |
||||
"family": "etherscan" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 1, |
||||
"reorgPeriod": 0, |
||||
"estimateBlockTime": 3 |
||||
}, |
||||
"gasCurrencyCoinGeckoId": "ethereum", |
||||
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-optimism.safe.global/", |
||||
"storageGasOracle": "0x27e88AeB8EA4B159d81df06355Ea3d20bEB1de38", |
||||
"proxyAdmin": "0xE047cb95FB3b7117989e911c6afb34771183fC35", |
||||
"merkleRootMultisigIsmFactory": "0xCA6Cb9Bc3cfF9E11003A06617cF934B684Bc78BC", |
||||
"messageIdMultisigIsmFactory": "0xAa4Be20E9957fE21602c74d7C3cF5CB1112EA9Ef", |
||||
"aggregationIsmFactory": "0x7491843F3A5Ba24E0f17a22645bDa04A1Ae2c584", |
||||
"aggregationHookFactory": "0x15DEeAB8dECDe553bb0B1F9C00984cbcae1af3D7", |
||||
"routingIsmFactory": "0x89E3530137aD51743536443a3EC838b502E72eb7", |
||||
"merkleTreeHook": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", |
||||
"interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", |
||||
"aggregationHook": "0x4ccC6d8eB79f2a1EC9bcb0f211fef7907631F91f", |
||||
"protocolFee": "0xD71Ff941120e8f935b8b1E2C1eD72F5d140FF458", |
||||
"mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", |
||||
"validatorAnnounce": "0x30f5b08e01808643221528BB2f7953bf2830Ef38", |
||||
"index": { |
||||
"from": 111554952 |
||||
} |
||||
}, |
||||
"polygon": { |
||||
"chainId": 137, |
||||
"domainId": 137, |
||||
"name": "polygon", |
||||
"protocol": "ethereum", |
||||
"displayName": "Polygon", |
||||
"nativeToken": { |
||||
"name": "Ether", |
||||
"symbol": "ETH", |
||||
"decimals": 18 |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://rpc-mainnet.matic.quiknode.pro", |
||||
"pagination": { |
||||
"maxBlockRange": 10000, |
||||
"minBlockNumber": 19657100 |
||||
} |
||||
}, |
||||
{ |
||||
"http": "https://polygon-rpc.com" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "PolygonScan", |
||||
"url": "https://polygonscan.com", |
||||
"apiUrl": "https://api.polygonscan.com/api", |
||||
"family": "etherscan" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 3, |
||||
"reorgPeriod": 256, |
||||
"estimateBlockTime": 2 |
||||
}, |
||||
"gasCurrencyCoinGeckoId": "matic-network", |
||||
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-polygon.safe.global/", |
||||
"transactionOverrides": { |
||||
"maxFeePerGas": 500000000000, |
||||
"maxPriorityFeePerGas": 100000000000 |
||||
}, |
||||
"storageGasOracle": "0xA3a24EC5670F1F416AB9fD554FcE2f226AE9D7eB", |
||||
"proxyAdmin": "0xC4F7590C5d30BE959225dC75640657954A86b980", |
||||
"merkleRootMultisigIsmFactory": "0xa9E0E18E78b098c2DE36c42E4DDEA13ce214c592", |
||||
"messageIdMultisigIsmFactory": "0xEa5Be2AD66BB1BA321B7aCf0A079fBE304B09Ca0", |
||||
"aggregationIsmFactory": "0x81AdDD9Ca89105063DaDEBd5B4408551Ce850E22", |
||||
"aggregationHookFactory": "0xFeeB86e70e4a640cDd29636CCE19BD6fe8628135", |
||||
"routingIsmFactory": "0xF0752A65ffB2153EaE53F6a70c858a87022d5c56", |
||||
"mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", |
||||
"merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6", |
||||
"interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", |
||||
"aggregationHook": "0x34dAb05650Cf590088bA18aF9d597f3e081bCc47", |
||||
"protocolFee": "0xF8F3629e308b4758F8396606405989F8D8C9c578", |
||||
"validatorAnnounce": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5", |
||||
"index": { |
||||
"from": 49352047 |
||||
} |
||||
}, |
||||
"polygonzkevm": { |
||||
"protocol": "ethereum", |
||||
"chainId": 1101, |
||||
"domainId": 1101, |
||||
"name": "polygonzkevm", |
||||
"displayName": "Polygon zkEVM", |
||||
"nativeToken": { |
||||
"name": "Ether", |
||||
"symbol": "ETH", |
||||
"decimals": 18 |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://polygonzkevm-mainnet.g.alchemy.com/v2/demo" |
||||
}, |
||||
{ |
||||
"http": "https://rpc.ankr.com/polygon_zkevm" |
||||
}, |
||||
{ |
||||
"http": "https://zkevm.polygonscan.com/" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "PolygonScan", |
||||
"url": "https://zkevm.polygonscan.com/", |
||||
"apiUrl": "https://api-zkevm.polygonscan.com/api", |
||||
"family": "etherscan" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 1, |
||||
"reorgPeriod": 1, |
||||
"estimateBlockTime": 10 |
||||
}, |
||||
"merkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", |
||||
"messageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", |
||||
"aggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", |
||||
"aggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", |
||||
"routingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", |
||||
"merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", |
||||
"proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", |
||||
"storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", |
||||
"interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", |
||||
"aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F", |
||||
"protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", |
||||
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", |
||||
"validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9", |
||||
"index": { |
||||
"from": 6789061 |
||||
} |
||||
}, |
||||
"scroll": { |
||||
"chainId": 534352, |
||||
"domainId": 534352, |
||||
"name": "scroll", |
||||
"protocol": "ethereum", |
||||
"displayName": "Scroll", |
||||
"nativeToken": { |
||||
"name": "Ether", |
||||
"symbol": "ETH", |
||||
"decimals": 18 |
||||
}, |
||||
"rpcUrls": [ |
||||
{ |
||||
"http": "https://scroll.blockpi.network/v1/rpc/public" |
||||
}, |
||||
{ |
||||
"http": "https://scroll-mainnet.public.blastapi.io" |
||||
} |
||||
], |
||||
"blockExplorers": [ |
||||
{ |
||||
"name": "Scroll Explorer", |
||||
"url": "https://scrollscan.com/", |
||||
"apiUrl": "https://api.scrollscan.com/api", |
||||
"family": "etherscan" |
||||
} |
||||
], |
||||
"blocks": { |
||||
"confirmations": 1, |
||||
"reorgPeriod": 1, |
||||
"estimateBlockTime": 3 |
||||
}, |
||||
"merkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", |
||||
"messageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", |
||||
"aggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", |
||||
"aggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", |
||||
"routingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", |
||||
"merkleTreeHook": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", |
||||
"proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", |
||||
"storageGasOracle": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", |
||||
"interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", |
||||
"aggregationHook": "0x9Bc0FAf446E128a618A88a2F28960Fb2Ca169faE", |
||||
"protocolFee": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", |
||||
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", |
||||
"validatorAnnounce": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", |
||||
"index": { |
||||
"from": 426670 |
||||
} |
||||
} |
||||
}, |
||||
"defaultRpcConsensusType": "fallback" |
||||
} |
@ -1,166 +0,0 @@ |
||||
{ |
||||
"chains": { |
||||
"celo": { |
||||
"name": "celo", |
||||
"domain": 42220, |
||||
"addresses": { |
||||
"mailbox": "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70", |
||||
"interchainGasPaymaster": "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918", |
||||
"validatorAnnounce": "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 0, |
||||
"index": { |
||||
"from": 16884144 |
||||
} |
||||
}, |
||||
"ethereum": { |
||||
"name": "ethereum", |
||||
"domain": 1, |
||||
"addresses": { |
||||
"mailbox": "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70", |
||||
"interchainGasPaymaster": "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918", |
||||
"validatorAnnounce": "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 20, |
||||
"index": { |
||||
"from": 16271503 |
||||
} |
||||
}, |
||||
"avalanche": { |
||||
"name": "avalanche", |
||||
"domain": 43114, |
||||
"addresses": { |
||||
"mailbox": "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70", |
||||
"interchainGasPaymaster": "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918", |
||||
"validatorAnnounce": "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 3, |
||||
"index": { |
||||
"from": 24145479 |
||||
} |
||||
}, |
||||
"polygon": { |
||||
"name": "polygon", |
||||
"domain": 137, |
||||
"addresses": { |
||||
"mailbox": "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70", |
||||
"interchainGasPaymaster": "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918", |
||||
"validatorAnnounce": "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 256, |
||||
"index": { |
||||
"from": 37313389 |
||||
} |
||||
}, |
||||
"bsc": { |
||||
"name": "bsc", |
||||
"domain": 56, |
||||
"addresses": { |
||||
"mailbox": "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70", |
||||
"interchainGasPaymaster": "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918", |
||||
"validatorAnnounce": "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 15, |
||||
"index": { |
||||
"from": 24248037 |
||||
} |
||||
}, |
||||
"arbitrum": { |
||||
"name": "arbitrum", |
||||
"domain": 42161, |
||||
"addresses": { |
||||
"mailbox": "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70", |
||||
"interchainGasPaymaster": "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918", |
||||
"validatorAnnounce": "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 0, |
||||
"index": { |
||||
"from": 49073182 |
||||
} |
||||
}, |
||||
"optimism": { |
||||
"name": "optimism", |
||||
"domain": 10, |
||||
"addresses": { |
||||
"mailbox": "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70", |
||||
"interchainGasPaymaster": "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918", |
||||
"validatorAnnounce": "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 0, |
||||
"index": { |
||||
"from": 55698988 |
||||
} |
||||
}, |
||||
"moonbeam": { |
||||
"name": "moonbeam", |
||||
"domain": 1284, |
||||
"addresses": { |
||||
"mailbox": "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70", |
||||
"interchainGasPaymaster": "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918", |
||||
"validatorAnnounce": "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 2, |
||||
"connection": { |
||||
"type": "http" |
||||
}, |
||||
"index": { |
||||
"from": 2595747 |
||||
} |
||||
}, |
||||
"gnosis": { |
||||
"name": "gnosis", |
||||
"domain": 100, |
||||
"addresses": { |
||||
"mailbox": "0x35231d4c2D8B8ADcB5617A638A0c4548684c7C70", |
||||
"interchainGasPaymaster": "0x6cA0B6D22da47f091B7613223cD4BB03a2d77918", |
||||
"validatorAnnounce": "0x9bBdef63594D5FFc2f370Fe52115DdFFe97Bc524" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 14, |
||||
"index": { |
||||
"from": 25900000 |
||||
} |
||||
}, |
||||
"nautilus": { |
||||
"name": "nautilus", |
||||
"domain": 22222, |
||||
"addresses": { |
||||
"mailbox": "0xF59557dfacDc5a1cb8A36Af43aA4819a6A891e88", |
||||
"interchainGasPaymaster": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", |
||||
"validatorAnnounce": "0x23ce76645EC601148fa451e751eeB75785b97A00" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 1, |
||||
"index": { |
||||
"from": 216377 |
||||
} |
||||
}, |
||||
"solana": { |
||||
"name": "solana", |
||||
"domain": 1399811149, |
||||
"addresses": { |
||||
"mailbox": "Ge9atjAc3Ltu91VTbNpJDCjZ9CFxFyck4h3YBcTF9XPq", |
||||
"interchainGasPaymaster": "FCNfmLSZLo5x7oNYmkYU8WdPUu7pj636P9CaMxkmaCp7", |
||||
"validatorAnnounce": "C88Lk5GR6cPxYoJxPbNDDEwsx5Kxn1wZEomvQ2So333g" |
||||
}, |
||||
"protocol": "sealevel", |
||||
"finalityBlocks": 0, |
||||
"connection": { |
||||
"type": "http", |
||||
"url": "https://api.mainnet-beta.solana.com" |
||||
}, |
||||
"index": { |
||||
"from": 1, |
||||
"mode": "sequence" |
||||
} |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -1,163 +0,0 @@ |
||||
{ |
||||
"chains": { |
||||
"alfajores": { |
||||
"name": "alfajores", |
||||
"domain": 44787, |
||||
"addresses": { |
||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 0, |
||||
"index": { |
||||
"from": 14863532 |
||||
} |
||||
}, |
||||
"fuji": { |
||||
"name": "fuji", |
||||
"domain": 43113, |
||||
"addresses": { |
||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 3, |
||||
"index": { |
||||
"from": 16330615 |
||||
} |
||||
}, |
||||
"mumbai": { |
||||
"name": "mumbai", |
||||
"domain": 80001, |
||||
"addresses": { |
||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 32, |
||||
"index": { |
||||
"from": 29390033 |
||||
} |
||||
}, |
||||
"bsctestnet": { |
||||
"name": "bsctestnet", |
||||
"domain": 97, |
||||
"addresses": { |
||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 9, |
||||
"index": { |
||||
"from": 25001629 |
||||
} |
||||
}, |
||||
"goerli": { |
||||
"name": "goerli", |
||||
"domain": 5, |
||||
"addresses": { |
||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 2, |
||||
"index": { |
||||
"from": 8039005 |
||||
} |
||||
}, |
||||
"sepolia": { |
||||
"name": "sepolia", |
||||
"domain": 11155111, |
||||
"addresses": { |
||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 2, |
||||
"index": { |
||||
"from": 3082913 |
||||
} |
||||
}, |
||||
"moonbasealpha": { |
||||
"name": "moonbasealpha", |
||||
"domain": 1287, |
||||
"addresses": { |
||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 1, |
||||
"index": { |
||||
"from": 3310405 |
||||
} |
||||
}, |
||||
"optimismgoerli": { |
||||
"name": "optimismgoerli", |
||||
"domain": 420, |
||||
"addresses": { |
||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 1, |
||||
"index": { |
||||
"from": 3055263 |
||||
} |
||||
}, |
||||
"arbitrumgoerli": { |
||||
"name": "arbitrumgoerli", |
||||
"domain": 421613, |
||||
"addresses": { |
||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 1, |
||||
"index": { |
||||
"from": 1941997 |
||||
} |
||||
}, |
||||
"solanadevnet": { |
||||
"name": "solanadevnet", |
||||
"domain": 1399811151, |
||||
"addresses": { |
||||
"mailbox": "4v25Dz9RccqUrTzmfHzJMsjd1iVoNrWzeJ4o6GYuJrVn", |
||||
"interchainGasPaymaster": "7hMPEGdgBQFsjEz3aaNwZp8WMFHs615zAM3erXBDJuJR", |
||||
"validatorAnnounce": "CMHKvdq4CopDf7qXnDCaTybS15QekQeRt4oUB219yxsp" |
||||
}, |
||||
"protocol": "sealevel", |
||||
"finalityBlocks": 0, |
||||
"connection": { |
||||
"type": "http", |
||||
"url": "https://api.devnet.solana.com" |
||||
}, |
||||
"index": { |
||||
"from": 1, |
||||
"mode": "sequence" |
||||
} |
||||
}, |
||||
"proteustestnet": { |
||||
"name": "proteustestnet", |
||||
"domain": 88002, |
||||
"addresses": { |
||||
"mailbox": "0x918D3924Fad8F71551D9081172e9Bb169745461e", |
||||
"interchainGasPaymaster": "0x06b62A9F5AEcc1E601D0E02732b4E1D0705DE7Db", |
||||
"validatorAnnounce": "0xEEea93d0d0287c71e47B3f62AFB0a92b9E8429a1" |
||||
}, |
||||
"protocol": "ethereum", |
||||
"finalityBlocks": 1, |
||||
"index": { |
||||
"from": 8609588 |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,44 +0,0 @@ |
||||
# Hyperlane-Agent Helm Chart |
||||
|
||||
![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square) |
||||
|
||||
A Helm Chart that encapsulates the deployment of the Hyperlane Rust Agent(s). It is currently designed to be deployed against a Google Kubernetes Engine cluster, but specification of another PVC Storage Class should be sufficient to make it compatible with other cloud providers. |
||||
|
||||
Additional documentation is present in comments in `yalues.yaml`. |
||||
|
||||
## Values |
||||
|
||||
| Key | Type | Default | Description | |
||||
| -------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
||||
| affinity | object | `{}` | | |
||||
| fullnameOverride | string | `""` | | |
||||
| image.pullPolicy | string | `"Always"` | | |
||||
| image.repository | string | `"gcr.io/clabs-optics/optics-agent"` | Main repository for Hyperlane Agent binaries, provided by cLabs | |
||||
| image.tag | string | `"latest"` | Overrides the image tag whose default is the chart appVersion. | |
||||
| imagePullSecrets | list | `[]` | | |
||||
| nameOverride | string | `""` | | |
||||
| nodeSelector | object | `{}` | | |
||||
| hyperlane | object | `{"outboxChain":{"address":null,"connectionType":null,"connectionUrl":null,"domain":null,"name":"goerli","protocol":null},enabled":false,"messageInterval":null,"signers":[{"key":"","name":"goerli"},{"key":"","name":"alfajores"}]},"processor":{"enabled":false,"pollingInterval":null,"signers":[{"key":"","name":"goerli"},{"key":"","name":"alfajores"}]},"relayer":{"enabled":false,"pollingInterval":null,"signers":[{"key":"","name":"goerli"},{"key":"","name":"alfajores"}]},"inboxChains":[{"address":null,"connectionType":null,"connectionUrl":null,"domain":null,"name":"alfajores","protocol":null}],"runEnv":"default","validator":{"signer":"","enabled":false,"pollingInterval":null,"signers":[{"key":"","name":"goerli"},{"key":"","name":"alfajores"}],"updatePause":null}}` | Hyperlane Overrides By Default, Hyperlane Agents load the config baked into the Docker Image Pass values here in order to override the values in the config Note: For successful operation, one _must_ pass signer keys as they are not baked into the image for security reasons. | |
||||
| hyperlane.outboxChain.address | string | `nil` | The contract address for the home contract | |
||||
| hyperlane.outboxChain.connectionUrl | string | `nil` | Connection string pointing to an RPC endpoint for the home chain | |
||||
| hyperlane.outboxChain.domain | string | `nil` | The hard-coded domain corresponding to this blockchain | |
||||
| hyperlane.outboxChain.protocol | string | `nil` | RPC Style | |
||||
| hyperlane.relayer.enabled | bool | `false` | Enables or disables the relayer | |
||||
| hyperlane.inboxChains | list | `[{"address":null,"connectionType":null,"connectionUrl":null,"domain":null,"name":"alfajores","protocol":null}]` | Replica chain overrides, a sequence | |
||||
| hyperlane.inboxChains[0].address | string | `nil` | The contract address for the replica contract | |
||||
| hyperlane.inboxChains[0].connectionUrl | string | `nil` | Connection string pointing to an RPC endpoint for the replica chain | |
||||
| hyperlane.validator.signer | string | `""` | Specialized key used by validator and watcher used to sign attestations, separate from validator.keys | |
||||
| hyperlane.validator.enabled | bool | `false` | Enables or disables the validator | |
||||
| hyperlane.validator.pollingInterval | string | `nil` | How long to wait between checking for updates | |
||||
| hyperlane.validator.signers | list | `[{"key":"","name":"goerli"},{"key":"","name":"alfajores"}]` | Trnsaction Signing keys for home and replica(s) | |
||||
| podAnnotations | object | `{}` | | |
||||
| podSecurityContext | object | `{}` | | |
||||
| replicaCount | int | `1` | | |
||||
| resources | object | `{}` | | |
||||
| securityContext | object | `{}` | | |
||||
| tolerations | list | `[]` | | |
||||
| volumeStorageClass | string | `"standard"` | Default to standard storageclass provided by GKE | |
||||
|
||||
--- |
||||
|
||||
Autogenerated from chart metadata using [helm-docs v1.5.0](https://github.com/norwoodj/helm-docs/releases/v1.5.0) |
@ -0,0 +1,9 @@ |
||||
{{/* |
||||
We truncate at 63 chars - (11 + (len $suffix)) because the controller-revision-hash label adds an 11 character suffix |
||||
to the pod name, and we want the -validator suffix to still be present, but are happy to truncate the preceding name. |
||||
See https://github.com/kubernetes/kubernetes/issues/64023 for controller-revision-hash details. |
||||
*/}} |
||||
{{- define "validator.fullname" -}} |
||||
{{- $suffix := "-validator" }} |
||||
{{- include "agent-common.fullname" . | trunc (int (sub 63 (add 11 (len $suffix)))) | trimSuffix "-" }}{{ print $suffix }} |
||||
{{- end }} |
@ -1,435 +0,0 @@ |
||||
//! This module is responsible for parsing the agent's settings using the old config format.
|
||||
|
||||
// TODO: Remove this module once we have finished migrating to the new format.
|
||||
|
||||
use std::{ |
||||
collections::{HashMap, HashSet}, |
||||
path::PathBuf, |
||||
}; |
||||
|
||||
use ethers_prometheus::middleware::PrometheusMiddlewareConf; |
||||
use eyre::{eyre, Context}; |
||||
use hyperlane_core::{cfg_unwrap_all, config::*, utils::hex_or_base58_to_h256, HyperlaneDomain}; |
||||
use serde::Deserialize; |
||||
|
||||
use super::envs::*; |
||||
use crate::settings::{ |
||||
chains::IndexSettings, trace::TracingConfig, ChainConf, ChainConnectionConf, |
||||
CheckpointSyncerConf, CoreContractAddresses, Settings, SignerConf, |
||||
}; |
||||
|
||||
/// Raw base settings.
|
||||
#[derive(Debug, Deserialize)] |
||||
#[serde(rename_all = "camelCase")] |
||||
pub struct DeprecatedRawSettings { |
||||
chains: Option<HashMap<String, DeprecatedRawChainConf>>, |
||||
defaultsigner: Option<DeprecatedRawSignerConf>, |
||||
metrics: Option<StrOrInt>, |
||||
tracing: Option<TracingConfig>, |
||||
} |
||||
|
||||
impl FromRawConf<DeprecatedRawSettings, Option<&HashSet<&str>>> for Settings { |
||||
fn from_config_filtered( |
||||
raw: DeprecatedRawSettings, |
||||
cwp: &ConfigPath, |
||||
filter: Option<&HashSet<&str>>, |
||||
) -> Result<Self, ConfigParsingError> { |
||||
let mut err = ConfigParsingError::default(); |
||||
let chains: HashMap<String, ChainConf> = if let Some(mut chains) = raw.chains { |
||||
let default_signer: Option<SignerConf> = raw.defaultsigner.and_then(|r| { |
||||
r.parse_config(&cwp.join("defaultsigner")) |
||||
.take_config_err(&mut err) |
||||
}); |
||||
if let Some(filter) = filter { |
||||
chains.retain(|k, _| filter.contains(&k.as_str())); |
||||
} |
||||
let chains_path = cwp + "chains"; |
||||
chains |
||||
.into_iter() |
||||
.map(|(k, v)| { |
||||
let cwp = &chains_path + &k; |
||||
let k = k.to_ascii_lowercase(); |
||||
let mut parsed: ChainConf = v.parse_config(&cwp)?; |
||||
if let Some(default_signer) = &default_signer { |
||||
parsed.signer.get_or_insert_with(|| default_signer.clone()); |
||||
} |
||||
Ok((k, parsed)) |
||||
}) |
||||
.filter_map(|res| match res { |
||||
Ok((k, v)) => Some((k, v)), |
||||
Err(e) => { |
||||
err.merge(e); |
||||
None |
||||
} |
||||
}) |
||||
.collect() |
||||
} else { |
||||
Default::default() |
||||
}; |
||||
let tracing = raw.tracing.unwrap_or_default(); |
||||
let metrics = raw |
||||
.metrics |
||||
.and_then(|port| port.try_into().take_err(&mut err, || cwp + "metrics")) |
||||
.unwrap_or(9090); |
||||
|
||||
err.into_result(Self { |
||||
chains, |
||||
metrics_port: metrics, |
||||
tracing, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
#[derive(Deserialize, Debug)] |
||||
#[serde(tag = "protocol", content = "connection", rename_all = "camelCase")] |
||||
enum DeprecatedRawChainConnectionConf { |
||||
Ethereum(h_eth::RawConnectionConf), |
||||
Fuel(h_fuel::DeprecatedRawConnectionConf), |
||||
Sealevel(h_sealevel::DeprecatedRawConnectionConf), |
||||
#[serde(other)] |
||||
Unknown, |
||||
} |
||||
|
||||
impl FromRawConf<DeprecatedRawChainConnectionConf> for ChainConnectionConf { |
||||
fn from_config_filtered( |
||||
raw: DeprecatedRawChainConnectionConf, |
||||
cwp: &ConfigPath, |
||||
_filter: (), |
||||
) -> ConfigResult<Self> { |
||||
use DeprecatedRawChainConnectionConf::*; |
||||
match raw { |
||||
Ethereum(r) => Ok(Self::Ethereum(r.parse_config(&cwp.join("connection"))?)), |
||||
Fuel(r) => Ok(Self::Fuel(r.parse_config(&cwp.join("connection"))?)), |
||||
Sealevel(r) => Ok(Self::Sealevel(r.parse_config(&cwp.join("connection"))?)), |
||||
Unknown => { |
||||
Err(eyre!("Unknown chain protocol")).into_config_result(|| cwp.join("protocol")) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Deserialize)] |
||||
#[serde(rename_all = "camelCase")] |
||||
struct DeprecatedRawCoreContractAddresses { |
||||
mailbox: Option<String>, |
||||
interchain_gas_paymaster: Option<String>, |
||||
validator_announce: Option<String>, |
||||
} |
||||
|
||||
impl FromRawConf<DeprecatedRawCoreContractAddresses> for CoreContractAddresses { |
||||
fn from_config_filtered( |
||||
raw: DeprecatedRawCoreContractAddresses, |
||||
cwp: &ConfigPath, |
||||
_filter: (), |
||||
) -> ConfigResult<Self> { |
||||
let mut err = ConfigParsingError::default(); |
||||
|
||||
macro_rules! parse_addr { |
||||
($name:ident) => { |
||||
let $name = raw |
||||
.$name |
||||
.ok_or_else(|| { |
||||
eyre!( |
||||
"Missing {} core contract address", |
||||
stringify!($name).replace('_', " ") |
||||
) |
||||
}) |
||||
.take_err(&mut err, || cwp + stringify!($name)) |
||||
.and_then(|v| { |
||||
hex_or_base58_to_h256(&v).take_err(&mut err, || cwp + stringify!($name)) |
||||
}); |
||||
}; |
||||
} |
||||
|
||||
parse_addr!(mailbox); |
||||
parse_addr!(interchain_gas_paymaster); |
||||
parse_addr!(validator_announce); |
||||
|
||||
cfg_unwrap_all!(cwp, err: [mailbox, interchain_gas_paymaster, validator_announce]); |
||||
|
||||
err.into_result(Self { |
||||
mailbox, |
||||
interchain_gas_paymaster, |
||||
validator_announce, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Deserialize)] |
||||
#[serde(rename_all = "camelCase")] |
||||
struct DeprecatedRawIndexSettings { |
||||
from: Option<StrOrInt>, |
||||
chunk: Option<StrOrInt>, |
||||
mode: Option<String>, |
||||
} |
||||
|
||||
impl FromRawConf<DeprecatedRawIndexSettings> for IndexSettings { |
||||
fn from_config_filtered( |
||||
raw: DeprecatedRawIndexSettings, |
||||
cwp: &ConfigPath, |
||||
_filter: (), |
||||
) -> ConfigResult<Self> { |
||||
let mut err = ConfigParsingError::default(); |
||||
|
||||
let from = raw |
||||
.from |
||||
.and_then(|v| v.try_into().take_err(&mut err, || cwp + "from")) |
||||
.unwrap_or_default(); |
||||
|
||||
let chunk_size = raw |
||||
.chunk |
||||
.and_then(|v| v.try_into().take_err(&mut err, || cwp + "chunk")) |
||||
.unwrap_or(1999); |
||||
|
||||
let mode = raw |
||||
.mode |
||||
.map(serde_json::Value::from) |
||||
.and_then(|m| { |
||||
serde_json::from_value(m) |
||||
.context("Invalid mode") |
||||
.take_err(&mut err, || cwp + "mode") |
||||
}) |
||||
.unwrap_or_default(); |
||||
|
||||
err.into_result(Self { |
||||
from, |
||||
chunk_size, |
||||
mode, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/// A raw chain setup is a domain ID, an address on that chain (where the
|
||||
/// mailbox is deployed) and details for connecting to the chain API.
|
||||
#[derive(Debug, Deserialize)] |
||||
#[serde(rename_all = "camelCase")] |
||||
pub struct DeprecatedRawChainConf { |
||||
name: Option<String>, |
||||
domain: Option<StrOrInt>, |
||||
pub(super) signer: Option<DeprecatedRawSignerConf>, |
||||
finality_blocks: Option<StrOrInt>, |
||||
addresses: Option<DeprecatedRawCoreContractAddresses>, |
||||
#[serde(flatten, default)] |
||||
connection: Option<DeprecatedRawChainConnectionConf>, |
||||
// TODO: if people actually use the metrics conf we should also add a raw form.
|
||||
#[serde(default)] |
||||
metrics_conf: Option<PrometheusMiddlewareConf>, |
||||
#[serde(default)] |
||||
index: Option<DeprecatedRawIndexSettings>, |
||||
} |
||||
|
||||
impl FromRawConf<DeprecatedRawChainConf> for ChainConf { |
||||
fn from_config_filtered( |
||||
raw: DeprecatedRawChainConf, |
||||
cwp: &ConfigPath, |
||||
_filter: (), |
||||
) -> ConfigResult<Self> { |
||||
let mut err = ConfigParsingError::default(); |
||||
|
||||
let connection = raw |
||||
.connection |
||||
.ok_or_else(|| eyre!("Missing `connection` configuration")) |
||||
.take_err(&mut err, || cwp + "connection") |
||||
.and_then(|r| r.parse_config(cwp).take_config_err(&mut err)); |
||||
|
||||
let domain = connection.as_ref().and_then(|c: &ChainConnectionConf| { |
||||
let protocol = c.protocol(); |
||||
let domain_id = raw |
||||
.domain |
||||
.ok_or_else(|| eyre!("Missing `domain` configuration")) |
||||
.take_err(&mut err, || cwp + "domain") |
||||
.and_then(|r| { |
||||
r.try_into() |
||||
.context("Invalid domain id, expected integer") |
||||
.take_err(&mut err, || cwp + "domain") |
||||
}); |
||||
let name = raw |
||||
.name |
||||
.as_deref() |
||||
.ok_or_else(|| eyre!("Missing domain `name` configuration")) |
||||
.take_err(&mut err, || cwp + "name"); |
||||
HyperlaneDomain::from_config(domain_id?, name?, protocol) |
||||
.take_err(&mut err, || cwp.clone()) |
||||
}); |
||||
|
||||
let addresses = raw |
||||
.addresses |
||||
.ok_or_else(|| eyre!("Missing `addresses` configuration for core contracts")) |
||||
.take_err(&mut err, || cwp + "addresses") |
||||
.and_then(|v| { |
||||
v.parse_config(&cwp.join("addresses")) |
||||
.take_config_err(&mut err) |
||||
}); |
||||
|
||||
let signer = raw.signer.and_then(|v| -> Option<SignerConf> { |
||||
v.parse_config(&cwp.join("signer")) |
||||
.take_config_err(&mut err) |
||||
}); |
||||
|
||||
let finality_blocks = raw |
||||
.finality_blocks |
||||
.and_then(|v| { |
||||
v.try_into() |
||||
.context("Invalid `finalityBlocks`, expected integer") |
||||
.take_err(&mut err, || cwp + "finality_blocks") |
||||
}) |
||||
.unwrap_or(0); |
||||
|
||||
let index = raw |
||||
.index |
||||
.and_then(|v| v.parse_config(&cwp.join("index")).take_config_err(&mut err)) |
||||
.unwrap_or_default(); |
||||
|
||||
let metrics_conf = raw.metrics_conf.unwrap_or_default(); |
||||
|
||||
cfg_unwrap_all!(cwp, err: [connection, domain, addresses]); |
||||
|
||||
err.into_result(Self { |
||||
connection, |
||||
domain, |
||||
addresses, |
||||
signer, |
||||
finality_blocks, |
||||
index, |
||||
metrics_conf, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/// Raw signer types
|
||||
#[derive(Debug, Deserialize, Default)] |
||||
#[serde(rename_all = "camelCase")] |
||||
pub struct DeprecatedRawSignerConf { |
||||
#[serde(rename = "type")] |
||||
signer_type: Option<String>, |
||||
key: Option<String>, |
||||
id: Option<String>, |
||||
region: Option<String>, |
||||
} |
||||
|
||||
/// Raw checkpoint syncer types
|
||||
#[derive(Debug, Deserialize)] |
||||
#[serde(tag = "type", rename_all = "camelCase")] |
||||
pub enum DeprecatedRawCheckpointSyncerConf { |
||||
/// A local checkpoint syncer
|
||||
LocalStorage { |
||||
/// Path
|
||||
path: Option<String>, |
||||
}, |
||||
/// A checkpoint syncer on S3
|
||||
S3 { |
||||
/// Bucket name
|
||||
bucket: Option<String>, |
||||
/// S3 Region
|
||||
region: Option<String>, |
||||
/// Folder name inside bucket - defaults to the root of the bucket
|
||||
folder: Option<String>, |
||||
}, |
||||
/// Unknown checkpoint syncer type was specified
|
||||
#[serde(other)] |
||||
Unknown, |
||||
} |
||||
|
||||
impl FromRawConf<DeprecatedRawSignerConf> for SignerConf { |
||||
fn from_config_filtered( |
||||
raw: DeprecatedRawSignerConf, |
||||
cwp: &ConfigPath, |
||||
_filter: (), |
||||
) -> ConfigResult<Self> { |
||||
let key_path = || cwp + "key"; |
||||
let region_path = || cwp + "region"; |
||||
|
||||
match raw.signer_type.as_deref() { |
||||
Some("hexKey") => Ok(Self::HexKey { |
||||
key: raw |
||||
.key |
||||
.ok_or_else(|| eyre!("Missing `key` for HexKey signer")) |
||||
.into_config_result(key_path)? |
||||
.parse() |
||||
.into_config_result(key_path)?, |
||||
}), |
||||
Some("aws") => Ok(Self::Aws { |
||||
id: raw |
||||
.id |
||||
.ok_or_else(|| eyre!("Missing `id` for Aws signer")) |
||||
.into_config_result(|| cwp + "id")?, |
||||
region: raw |
||||
.region |
||||
.ok_or_else(|| eyre!("Missing `region` for Aws signer")) |
||||
.into_config_result(region_path)? |
||||
.parse() |
||||
.into_config_result(region_path)?, |
||||
}), |
||||
Some(t) => Err(eyre!("Unknown signer type `{t}`")).into_config_result(|| cwp + "type"), |
||||
None if raw.key.is_some() => Ok(Self::HexKey { |
||||
key: raw.key.unwrap().parse().into_config_result(key_path)?, |
||||
}), |
||||
None if raw.id.is_some() | raw.region.is_some() => Ok(Self::Aws { |
||||
id: raw |
||||
.id |
||||
.ok_or_else(|| eyre!("Missing `id` for Aws signer")) |
||||
.into_config_result(|| cwp + "id")?, |
||||
region: raw |
||||
.region |
||||
.ok_or_else(|| eyre!("Missing `region` for Aws signer")) |
||||
.into_config_result(region_path)? |
||||
.parse() |
||||
.into_config_result(region_path)?, |
||||
}), |
||||
None => Ok(Self::Node), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl FromRawConf<DeprecatedRawCheckpointSyncerConf> for CheckpointSyncerConf { |
||||
fn from_config_filtered( |
||||
raw: DeprecatedRawCheckpointSyncerConf, |
||||
cwp: &ConfigPath, |
||||
_filter: (), |
||||
) -> ConfigResult<Self> { |
||||
match raw { |
||||
DeprecatedRawCheckpointSyncerConf::LocalStorage { path } => { |
||||
let path: PathBuf = path |
||||
.ok_or_else(|| eyre!("Missing `path` for LocalStorage checkpoint syncer")) |
||||
.into_config_result(|| cwp + "path")? |
||||
.parse() |
||||
.into_config_result(|| cwp + "path")?; |
||||
if !path.exists() { |
||||
std::fs::create_dir_all(&path) |
||||
.with_context(|| { |
||||
format!( |
||||
"Failed to create local checkpoint syncer storage directory at {:?}", |
||||
path |
||||
) |
||||
}) |
||||
.into_config_result(|| cwp + "path")?; |
||||
} else if !path.is_dir() { |
||||
Err(eyre!( |
||||
"LocalStorage checkpoint syncer path is not a directory" |
||||
)) |
||||
.into_config_result(|| cwp + "path")?; |
||||
} |
||||
Ok(Self::LocalStorage { path }) |
||||
} |
||||
DeprecatedRawCheckpointSyncerConf::S3 { |
||||
bucket, |
||||
folder, |
||||
region, |
||||
} => Ok(Self::S3 { |
||||
bucket: bucket |
||||
.ok_or_else(|| eyre!("Missing `bucket` for S3 checkpoint syncer")) |
||||
.into_config_result(|| cwp + "bucket")?, |
||||
folder, |
||||
region: region |
||||
.ok_or_else(|| eyre!("Missing `region` for S3 checkpoint syncer")) |
||||
.into_config_result(|| cwp + "region")? |
||||
.parse() |
||||
.into_config_result(|| cwp + "region")?, |
||||
}), |
||||
DeprecatedRawCheckpointSyncerConf::Unknown => { |
||||
Err(eyre!("Missing `type` for checkpoint syncer")) |
||||
.into_config_result(|| cwp + "type") |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
use std::fmt::Debug; |
||||
|
||||
use config::{ConfigError, Map, Source, Value, ValueKind}; |
||||
use convert_case::{Case, Casing}; |
||||
use derive_new::new; |
||||
use itertools::Itertools; |
||||
|
||||
#[derive(Clone, Debug, new)] |
||||
pub struct CaseAdapter<S> { |
||||
inner: S, |
||||
casing: Case, |
||||
} |
||||
|
||||
impl<S> Source for CaseAdapter<S> |
||||
where |
||||
S: Source + Clone + Send + Sync + 'static, |
||||
{ |
||||
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> { |
||||
Box::new(self.clone()) |
||||
} |
||||
|
||||
fn collect(&self) -> Result<Map<String, Value>, ConfigError> { |
||||
self.inner.collect().map(|m| { |
||||
m.into_iter() |
||||
.map(|(k, v)| recase_pair(k, v, self.casing)) |
||||
.collect() |
||||
}) |
||||
} |
||||
} |
||||
|
||||
fn recase_pair(key: String, mut val: Value, case: Case) -> (String, Value) { |
||||
let key = split_and_recase_key(".", Some(case), key); |
||||
match &mut val.kind { |
||||
ValueKind::Table(table) => { |
||||
let tmp = table |
||||
.drain() |
||||
.map(|(k, v)| recase_pair(k, v, case)) |
||||
.collect_vec(); |
||||
table.extend(tmp); |
||||
} |
||||
ValueKind::Array(ary) => { |
||||
let tmp = ary |
||||
.drain(..) |
||||
.map(|v| recase_pair(String::new(), v, case).1) |
||||
.collect_vec(); |
||||
ary.extend(tmp) |
||||
} |
||||
_ => {} |
||||
} |
||||
(key, val) |
||||
} |
||||
|
||||
/// Load a settings object from the config locations and re-join the components with the standard
|
||||
/// `config` crate separator `.`.
|
||||
fn split_and_recase_key(sep: &str, case: Option<Case>, key: String) -> String { |
||||
if let Some(case) = case { |
||||
// if case is given, replace case of each key component and separate them with `.`
|
||||
key.split(sep).map(|s| s.to_case(case)).join(".") |
||||
} else if !sep.is_empty() && sep != "." { |
||||
// Just standardize the separator to `.`
|
||||
key.replace(sep, ".") |
||||
} else { |
||||
// no changes needed if there was no separator defined and we are preserving case.
|
||||
key |
||||
} |
||||
} |
@ -1,343 +0,0 @@ |
||||
// TODO: Remove this file after deprecated config parsing has been removed.
|
||||
|
||||
use std::ffi::{OsStr, OsString}; |
||||
|
||||
use config::{ConfigError, Map, Source, Value, ValueKind}; |
||||
use convert_case::Case; |
||||
|
||||
use crate::settings::loader::split_and_recase_key; |
||||
|
||||
/// A source for loading configuration from command line arguments.
|
||||
/// Command line argument keys are case-insensitive, and the following forms are
|
||||
/// supported:
|
||||
///
|
||||
/// * `--key=value`
|
||||
/// * `--key="value"`
|
||||
/// * `--key='value'`
|
||||
/// * `--key value`
|
||||
/// * `--key` (value is an empty string)
|
||||
#[must_use] |
||||
#[derive(Clone, Debug, Default)] |
||||
pub struct DeprecatedCommandLineArguments { |
||||
/// Optional character sequence that separates each key segment in an
|
||||
/// environment key pattern. Consider a nested configuration such as
|
||||
/// `redis.password`, a separator of `-` would allow an environment key
|
||||
/// of `redis-password` to match.
|
||||
separator: Option<String>, |
||||
|
||||
/// Ignore empty env values (treat as unset).
|
||||
ignore_empty: bool, |
||||
|
||||
/// Alternate source for the environment. This can be used when you want to
|
||||
/// test your own code using this source, without the need to change the
|
||||
/// actual system environment variables.
|
||||
source: Option<Vec<OsString>>, |
||||
} |
||||
|
||||
#[allow(unused)] |
||||
impl DeprecatedCommandLineArguments { |
||||
pub fn separator(mut self, s: &str) -> Self { |
||||
self.separator = Some(s.into()); |
||||
self |
||||
} |
||||
|
||||
pub fn ignore_empty(mut self, ignore: bool) -> Self { |
||||
self.ignore_empty = ignore; |
||||
self |
||||
} |
||||
|
||||
pub fn source<I, S>(mut self, source: I) -> Self |
||||
where |
||||
I: IntoIterator<Item = S>, |
||||
S: AsRef<OsStr>, |
||||
{ |
||||
self.source = Some(source.into_iter().map(|s| s.as_ref().to_owned()).collect()); |
||||
self |
||||
} |
||||
} |
||||
|
||||
impl Source for DeprecatedCommandLineArguments { |
||||
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> { |
||||
Box::new((*self).clone()) |
||||
} |
||||
|
||||
fn collect(&self) -> Result<Map<String, Value>, ConfigError> { |
||||
let mut m = Map::new(); |
||||
let uri: String = "program argument".into(); |
||||
|
||||
let separator = self.separator.as_deref().unwrap_or("-"); |
||||
|
||||
let mut args = if let Some(source) = &self.source { |
||||
ArgumentParser::from_vec(source.clone()) |
||||
} else { |
||||
ArgumentParser::from_env() |
||||
}; |
||||
|
||||
while let Some((key, value)) = args |
||||
.next() |
||||
.transpose() |
||||
.map_err(|e| ConfigError::Foreign(Box::new(e)))? |
||||
{ |
||||
if self.ignore_empty && value.is_empty() { |
||||
continue; |
||||
} |
||||
|
||||
let mut key = split_and_recase_key(separator, Some(Case::Flat), key); |
||||
if key.ends_with("interchaingaspaymaster") { |
||||
key = key.replace("interchaingaspaymaster", "interchainGasPaymaster"); |
||||
} else if key.ends_with("validatorannounce") { |
||||
key = key.replace("validatorannounce", "validatorAnnounce"); |
||||
} |
||||
|
||||
m.insert(key, Value::new(Some(&uri), ValueKind::String(value))); |
||||
} |
||||
|
||||
let remaining = args.finish(); |
||||
if remaining.is_empty() { |
||||
Ok(m) |
||||
} else { |
||||
Err(ConfigError::Message("Could not parse all arguments".into())) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// An ultra simple CLI arguments parser.
|
||||
/// Adapted from pico-args 0.5.0.
|
||||
#[derive(Clone, Debug)] |
||||
pub struct ArgumentParser(Vec<OsString>); |
||||
|
||||
impl ArgumentParser { |
||||
/// Creates a parser from a vector of arguments.
|
||||
///
|
||||
/// The executable path **must** be removed.
|
||||
///
|
||||
/// This can be used for supporting `--` arguments to forward to another
|
||||
/// program.
|
||||
fn from_vec(args: Vec<OsString>) -> Self { |
||||
ArgumentParser(args) |
||||
} |
||||
|
||||
/// Creates a parser from [`env::args_os`].
|
||||
///
|
||||
/// The executable path will be removed.
|
||||
///
|
||||
/// [`env::args_os`]: https://doc.rust-lang.org/stable/std/env/fn.args_os.html
|
||||
fn from_env() -> Self { |
||||
let mut args: Vec<_> = std::env::args_os().collect(); |
||||
args.remove(0); |
||||
ArgumentParser(args) |
||||
} |
||||
|
||||
/// Returns a list of remaining arguments.
|
||||
///
|
||||
/// It's up to the caller what to do with them.
|
||||
/// One can report an error about unused arguments,
|
||||
/// other can use them for further processing.
|
||||
fn finish(self) -> Vec<OsString> { |
||||
self.0 |
||||
} |
||||
} |
||||
|
||||
impl Iterator for ArgumentParser { |
||||
type Item = Result<(String, String), Error>; |
||||
|
||||
fn next(&mut self) -> Option<Self::Item> { |
||||
let (k, v, kind, idx) = match self.find_next_kv_pair() { |
||||
Ok(Some(tup)) => tup, |
||||
Ok(None) => return None, |
||||
Err(e) => return Some(Err(e)), |
||||
}; |
||||
|
||||
match kind { |
||||
PairKind::SingleArgument => { |
||||
self.0.remove(idx); |
||||
} |
||||
PairKind::TwoArguments => { |
||||
self.0.remove(idx + 1); |
||||
self.0.remove(idx); |
||||
} |
||||
} |
||||
|
||||
Some(Ok((k, v))) |
||||
} |
||||
} |
||||
|
||||
// internal workings
|
||||
impl ArgumentParser { |
||||
#[inline(never)] |
||||
fn find_next_kv_pair(&mut self) -> Result<Option<(String, String, PairKind, usize)>, Error> { |
||||
let Some(idx) = self.index_of_next_key() else { |
||||
return Ok(None); |
||||
}; |
||||
// full term without leading '--'
|
||||
let term = &os_to_str(&self.0[idx])?[2..]; |
||||
if term.is_empty() { |
||||
return Err(Error::EmptyKey); |
||||
} |
||||
|
||||
if let Some((key, value)) = term.split_once('=') { |
||||
// Parse a `--key=value` pair.
|
||||
let key = key.to_owned(); |
||||
|
||||
// Check for quoted value.
|
||||
let value = if starts_with(value, b'"') { |
||||
if !ends_with(value, b'"') { |
||||
// A closing quote must be the same as an opening one.
|
||||
return Err(Error::UnmatchedQuote(key)); |
||||
} |
||||
&value[1..value.len() - 1] |
||||
} else if starts_with(value, b'\'') { |
||||
if !ends_with(value, b'\'') { |
||||
// A closing quote must be the same as an opening one.
|
||||
return Err(Error::UnmatchedQuote(key)); |
||||
} |
||||
&value[1..value.len() - 1] |
||||
} else { |
||||
value |
||||
}; |
||||
|
||||
Ok(Some((key, value.to_owned(), PairKind::SingleArgument, idx))) |
||||
} else { |
||||
// Parse a `--key value` pair.
|
||||
let key = term.to_owned(); |
||||
let value = self |
||||
.0 |
||||
.get(idx + 1) |
||||
.map(|v| os_to_str(v)) |
||||
.transpose()? |
||||
.unwrap_or(""); |
||||
|
||||
if value.is_empty() || value.starts_with('-') { |
||||
// the next value is another key
|
||||
Ok(Some((key, "".to_owned(), PairKind::SingleArgument, idx))) |
||||
} else { |
||||
Ok(Some((key, value.to_owned(), PairKind::TwoArguments, idx))) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn index_of_next_key(&self) -> Option<usize> { |
||||
self.0.iter().position(|v| { |
||||
#[cfg(unix)] |
||||
{ |
||||
use std::os::unix::ffi::OsStrExt; |
||||
v.len() >= 2 && &v.as_bytes()[0..2] == b"--" |
||||
} |
||||
#[cfg(not(unix))] |
||||
{ |
||||
v.len() >= 2 && v.to_str().map(|v| v.starts_with("--")).unwrap_or(false) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
#[inline] |
||||
fn starts_with(text: &str, c: u8) -> bool { |
||||
if text.is_empty() { |
||||
false |
||||
} else { |
||||
text.as_bytes()[0] == c |
||||
} |
||||
} |
||||
|
||||
#[inline] |
||||
fn ends_with(text: &str, c: u8) -> bool { |
||||
if text.is_empty() { |
||||
false |
||||
} else { |
||||
text.as_bytes()[text.len() - 1] == c |
||||
} |
||||
} |
||||
|
||||
#[inline] |
||||
fn os_to_str(text: &OsStr) -> Result<&str, Error> { |
||||
text.to_str().ok_or(Error::NonUtf8Argument) |
||||
} |
||||
|
||||
/// A list of possible errors.
|
||||
#[derive(Clone, Debug, thiserror::Error)] |
||||
pub enum Error { |
||||
/// Arguments must be a valid UTF-8 strings.
|
||||
#[error("argument is not a UTF-8 string")] |
||||
NonUtf8Argument, |
||||
|
||||
/// Found '--` or a key with nothing after the prefix
|
||||
#[error("key name is empty (possibly after removing prefix)")] |
||||
EmptyKey, |
||||
|
||||
/// Could not find closing quote for a value.
|
||||
#[error("unmatched quote in `{0}`")] |
||||
UnmatchedQuote(String), |
||||
} |
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)] |
||||
enum PairKind { |
||||
SingleArgument, |
||||
TwoArguments, |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod test { |
||||
use super::*; |
||||
|
||||
macro_rules! assert_arg { |
||||
($config:expr, $key:literal, $value:literal) => { |
||||
let origin = "program argument".to_owned(); |
||||
assert_eq!( |
||||
$config.remove($key), |
||||
Some(Value::new( |
||||
Some(&origin), |
||||
ValueKind::String($value.to_owned()) |
||||
)) |
||||
); |
||||
}; |
||||
} |
||||
|
||||
const ARGUMENTS: &[&str] = &[ |
||||
"--key-a", |
||||
"value-a", |
||||
"--keY-b=value-b", |
||||
"--key-c=\"value c\"", |
||||
"--KEY-d='valUE d'", |
||||
"--key-e=''", |
||||
"--key-F", |
||||
"--key-g=value-g", |
||||
"--key-h", |
||||
]; |
||||
|
||||
#[test] |
||||
fn default_case() { |
||||
let mut config = DeprecatedCommandLineArguments::default() |
||||
.source(ARGUMENTS) |
||||
.collect() |
||||
.unwrap(); |
||||
|
||||
assert_arg!(config, "key.a", "value-a"); |
||||
assert_arg!(config, "key.b", "value-b"); |
||||
assert_arg!(config, "key.c", "value c"); |
||||
assert_arg!(config, "key.d", "valUE d"); |
||||
assert_arg!(config, "key.e", ""); |
||||
assert_arg!(config, "key.f", ""); |
||||
assert_arg!(config, "key.g", "value-g"); |
||||
assert_arg!(config, "key.h", ""); |
||||
|
||||
assert!(config.is_empty()); |
||||
} |
||||
|
||||
#[test] |
||||
fn ignore_empty() { |
||||
let mut config = DeprecatedCommandLineArguments::default() |
||||
.source(ARGUMENTS) |
||||
.ignore_empty(true) |
||||
.collect() |
||||
.unwrap(); |
||||
|
||||
assert_arg!(config, "key.a", "value-a"); |
||||
assert_arg!(config, "key.b", "value-b"); |
||||
assert_arg!(config, "key.c", "value c"); |
||||
assert_arg!(config, "key.d", "valUE d"); |
||||
assert_arg!(config, "key.g", "value-g"); |
||||
|
||||
assert!(config.is_empty()); |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue