feat: Add block tag support in `reorgPeriod` parameter (#4629)

### Description

<!--
What's included in this PR?
-->
Allows setting an arbitrary string in `reorgPeriod`, which is used as a
block tag to get the finalized block. Currently, only the Ethereum
connector supports this feature with the following tags:
- finalized
- safe
- pending
- latest
- earliest

This is currently useful for EVM-compatible chains that don't have a
fixed reorganization period, but instead rely on the block tag
`Finalized` to indicate finality. For such chains, you should set
`reorgPeriod` to `finalized` instead of a number of blocks.


### Drive-by changes

<!--
Are there any minor or drive-by changes also included?
-->

### Related issues

<!--
- Fixes #[issue number here]
-->

### Backward compatibility

<!--
Are these changes backward compatible? Are there any infrastructure
implications, e.g. changes that would prohibit deploying older commits
using this infra tooling?

Yes/No
-->
Yes
### Testing

<!--
What kind of testing have these changes undergone?

None/Manual/Unit Tests
-->

---------

Co-authored-by: Daniel Savu <23065004+daniel-savu@users.noreply.github.com>
pull/4736/head
Alexander Pastushenka 1 month ago committed by GitHub
parent cd7a263eb6
commit c3e9268f1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      .changeset/strange-beers-buy.md
  2. 12
      rust/main/agents/validator/src/settings.rs
  3. 26
      rust/main/agents/validator/src/submit.rs
  4. 17
      rust/main/agents/validator/src/validator.rs
  5. 9
      rust/main/chains/hyperlane-cosmos/src/mailbox/contract.rs
  6. 22
      rust/main/chains/hyperlane-cosmos/src/merkle_tree_hook.rs
  7. 25
      rust/main/chains/hyperlane-cosmos/src/utils.rs
  8. 34
      rust/main/chains/hyperlane-ethereum/src/config.rs
  9. 28
      rust/main/chains/hyperlane-ethereum/src/contracts/interchain_gas.rs
  10. 37
      rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs
  11. 51
      rust/main/chains/hyperlane-ethereum/src/contracts/merkle_tree_hook.rs
  12. 2
      rust/main/chains/hyperlane-ethereum/src/contracts/mod.rs
  13. 35
      rust/main/chains/hyperlane-ethereum/src/contracts/utils.rs
  14. 24
      rust/main/chains/hyperlane-ethereum/src/tx.rs
  15. 9
      rust/main/chains/hyperlane-fuel/src/mailbox.rs
  16. 10
      rust/main/chains/hyperlane-sealevel/src/mailbox.rs
  17. 18
      rust/main/chains/hyperlane-sealevel/src/merkle_tree_hook.rs
  18. 42
      rust/main/hyperlane-base/src/settings/chains.rs
  19. 8
      rust/main/hyperlane-base/src/settings/checkpoint_syncer.rs
  20. 6
      rust/main/hyperlane-base/src/settings/parser/mod.rs
  21. 114
      rust/main/hyperlane-core/src/chain.rs
  22. 9
      rust/main/hyperlane-core/src/error.rs
  23. 7
      rust/main/hyperlane-core/src/traits/mailbox.rs
  24. 14
      rust/main/hyperlane-core/src/traits/merkle_tree_hook.rs
  25. 6
      rust/main/hyperlane-core/src/types/reorg.rs
  26. 12
      rust/main/hyperlane-test/src/mocks/mailbox.rs
  27. 13
      typescript/cli/src/config/chain.ts
  28. 2
      typescript/infra/config/registry.ts
  29. 4
      typescript/infra/src/config/agent/validator.ts
  30. 10
      typescript/sdk/src/metadata/chainMetadata.test.ts
  31. 9
      typescript/sdk/src/metadata/chainMetadataTypes.ts

@ -0,0 +1,7 @@
---
'@hyperlane-xyz/infra': minor
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---
Add support for an arbitrary string in `reorgPeriod`, which is used as a block tag to get the finalized block.

@ -15,7 +15,9 @@ use hyperlane_base::{
CheckpointSyncerConf, Settings, SignerConf,
},
};
use hyperlane_core::{cfg_unwrap_all, config::*, HyperlaneDomain, HyperlaneDomainProtocol};
use hyperlane_core::{
cfg_unwrap_all, config::*, HyperlaneDomain, HyperlaneDomainProtocol, ReorgPeriod,
};
use serde::Deserialize;
use serde_json::Value;
@ -36,8 +38,8 @@ pub struct ValidatorSettings {
pub validator: SignerConf,
/// The checkpoint syncer configuration
pub checkpoint_syncer: CheckpointSyncerConf,
/// The reorg_period in blocks
pub reorg_period: u64,
/// The reorg configuration
pub reorg_period: ReorgPeriod,
/// How frequently to check for new checkpoints
pub interval: Duration,
}
@ -122,8 +124,8 @@ impl FromRawConf<RawValidatorSettings> for ValidatorSettings {
.get_key(origin_chain_name)
.get_opt_key("blocks")
.get_opt_key("reorgPeriod")
.parse_u64()
.unwrap_or(1);
.parse_value("Invalid reorgPeriod")
.unwrap_or(ReorgPeriod::from_blocks(1));
cfg_unwrap_all!(cwp, err: [base, origin_chain, validator, checkpoint_syncer]);

@ -1,4 +1,3 @@
use std::num::NonZeroU64;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::vec;
@ -14,13 +13,13 @@ use hyperlane_core::{
accumulator::incremental::IncrementalMerkle, Checkpoint, CheckpointWithMessageId,
HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneSignerExt,
};
use hyperlane_core::{ChainResult, MerkleTreeHook, ReorgEvent};
use hyperlane_core::{ChainResult, MerkleTreeHook, ReorgEvent, ReorgPeriod};
use hyperlane_ethereum::SingletonSignerHandle;
#[derive(Clone)]
pub(crate) struct ValidatorSubmitter {
interval: Duration,
reorg_period: Option<NonZeroU64>,
reorg_period: ReorgPeriod,
signer: SingletonSignerHandle,
merkle_tree_hook: Arc<dyn MerkleTreeHook>,
checkpoint_syncer: Arc<dyn CheckpointSyncer>,
@ -31,7 +30,7 @@ pub(crate) struct ValidatorSubmitter {
impl ValidatorSubmitter {
pub(crate) fn new(
interval: Duration,
reorg_period: u64,
reorg_period: ReorgPeriod,
merkle_tree_hook: Arc<dyn MerkleTreeHook>,
signer: SingletonSignerHandle,
checkpoint_syncer: Arc<dyn CheckpointSyncer>,
@ -39,7 +38,7 @@ impl ValidatorSubmitter {
metrics: ValidatorSubmitterMetrics,
) -> Self {
Self {
reorg_period: NonZeroU64::new(reorg_period),
reorg_period,
interval,
merkle_tree_hook,
signer,
@ -94,7 +93,8 @@ impl ValidatorSubmitter {
// Lag by reorg period because this is our correctness checkpoint.
let latest_checkpoint = call_and_retry_indefinitely(|| {
let merkle_tree_hook = self.merkle_tree_hook.clone();
Box::pin(async move { merkle_tree_hook.latest_checkpoint(self.reorg_period).await })
let reorg_period = self.reorg_period.clone();
Box::pin(async move { merkle_tree_hook.latest_checkpoint(&reorg_period).await })
})
.await;
@ -211,7 +211,7 @@ impl ValidatorSubmitter {
correctness_checkpoint.root,
checkpoint.index,
chrono::Utc::now().timestamp() as u64,
self.reorg_period.map(|x| x.get()).unwrap_or(0),
self.reorg_period.clone(),
);
error!(
?checkpoint,
@ -486,9 +486,9 @@ mod test {
#[async_trait]
impl MerkleTreeHook for MerkleTreeHook {
async fn tree(&self, lag: Option<NonZeroU64>) -> ChainResult<IncrementalMerkle>;
async fn count(&self, lag: Option<NonZeroU64>) -> ChainResult<u32>;
async fn latest_checkpoint(&self, lag: Option<NonZeroU64>) -> ChainResult<Checkpoint>;
async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult<IncrementalMerkle>;
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32>;
async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult<Checkpoint>;
}
}
@ -532,7 +532,7 @@ mod test {
expected_local_merkle_tree: &IncrementalMerkle,
mock_onchain_merkle_tree: &IncrementalMerkle,
unix_timestamp: u64,
expected_reorg_period: u64,
expected_reorg_period: ReorgPeriod,
) {
assert_eq!(
reorg_event.canonical_merkle_root,
@ -617,7 +617,7 @@ mod test {
&expected_local_merkle_tree,
&mock_onchain_merkle_tree_clone,
unix_timestamp,
expected_reorg_period,
ReorgPeriod::from_blocks(expected_reorg_period),
);
Ok(())
});
@ -625,7 +625,7 @@ mod test {
// instantiate the validator submitter
let validator_submitter = ValidatorSubmitter::new(
Duration::from_secs(1),
expected_reorg_period,
ReorgPeriod::from_blocks(expected_reorg_period),
Arc::new(mock_merkle_tree_hook),
dummy_singleton_handle(),
Arc::new(mock_checkpoint_syncer),

@ -1,4 +1,4 @@
use std::{num::NonZeroU64, sync::Arc, time::Duration};
use std::{sync::Arc, time::Duration};
use crate::server as validator_server;
use async_trait::async_trait;
@ -19,8 +19,8 @@ use hyperlane_base::{
use hyperlane_core::{
Announcement, ChainResult, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneSigner,
HyperlaneSignerExt, Mailbox, MerkleTreeHook, MerkleTreeInsertion, TxOutcome, ValidatorAnnounce,
H256, U256,
HyperlaneSignerExt, Mailbox, MerkleTreeHook, MerkleTreeInsertion, ReorgPeriod, TxOutcome,
ValidatorAnnounce, H256, U256,
};
use hyperlane_ethereum::{SingletonSigner, SingletonSignerHandle};
@ -44,7 +44,7 @@ pub struct Validator {
signer: SingletonSignerHandle,
// temporary holder until `run` is called
signer_instance: Option<Box<SingletonSigner>>,
reorg_period: u64,
reorg_period: ReorgPeriod,
interval: Duration,
checkpoint_syncer: Arc<dyn CheckpointSyncer>,
core_metrics: Arc<CoreMetrics>,
@ -184,12 +184,10 @@ impl BaseAgent for Validator {
// announce the validator after spawning the signer task
self.announce().await.expect("Failed to announce validator");
let reorg_period = NonZeroU64::new(self.reorg_period);
// Ensure that the merkle tree hook has count > 0 before we begin indexing
// messages or submitting checkpoints.
loop {
match self.merkle_tree_hook.count(reorg_period).await {
match self.merkle_tree_hook.count(&self.reorg_period).await {
Ok(0) => {
info!("Waiting for first message in merkle tree hook");
sleep(self.interval).await;
@ -241,7 +239,7 @@ impl Validator {
async fn run_checkpoint_submitters(&self) -> Vec<Instrumented<JoinHandle<()>>> {
let submitter = ValidatorSubmitter::new(
self.interval,
self.reorg_period,
self.reorg_period.clone(),
self.merkle_tree_hook.clone(),
self.signer.clone(),
self.checkpoint_syncer.clone(),
@ -249,10 +247,9 @@ impl Validator {
ValidatorSubmitterMetrics::new(&self.core.metrics, &self.origin_chain),
);
let reorg_period = NonZeroU64::new(self.reorg_period);
let tip_tree = self
.merkle_tree_hook
.tree(reorg_period)
.tree(&self.reorg_period)
.await
.expect("failed to get merkle tree");
// This function is only called after we have already checked that the

@ -8,7 +8,7 @@ use tracing::instrument;
use hyperlane_core::{
utils::bytes_to_hex, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract,
HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Mailbox, RawHyperlaneMessage,
TxCostEstimate, TxOutcome, H256, U256,
ReorgPeriod, TxCostEstimate, TxOutcome, H256, U256,
};
use crate::grpc::WasmProvider;
@ -17,7 +17,7 @@ use crate::payloads::mailbox::{
GeneralMailboxQuery, ProcessMessageRequest, ProcessMessageRequestInner,
};
use crate::types::tx_response_to_outcome;
use crate::utils::get_block_height_for_lag;
use crate::utils::get_block_height_for_reorg_period;
use crate::{payloads, ConnectionConf, CosmosAddress, CosmosProvider, Signer};
#[derive(Clone, Debug)]
@ -82,8 +82,9 @@ impl HyperlaneChain for CosmosMailbox {
impl Mailbox for CosmosMailbox {
#[instrument(level = "debug", err, ret, skip(self))]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
async fn count(&self, lag: Option<NonZeroU64>) -> ChainResult<u32> {
let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?;
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32> {
let block_height =
get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?;
self.nonce_at_block(block_height).await
}

@ -10,15 +10,15 @@ use hyperlane_core::accumulator::incremental::IncrementalMerkle;
use hyperlane_core::{
ChainCommunicationError, ChainResult, Checkpoint, ContractLocator, HyperlaneChain,
HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, LogMeta,
MerkleTreeHook, MerkleTreeInsertion, SequenceAwareIndexer, H256, H512,
MerkleTreeHook, MerkleTreeInsertion, ReorgPeriod, SequenceAwareIndexer, H256, H512,
};
use crate::grpc::WasmProvider;
use crate::payloads::{general, merkle_tree_hook};
use crate::rpc::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider};
use crate::utils::{
execute_and_parse_log_futures, get_block_height_for_lag, parse_logs_in_range, parse_logs_in_tx,
CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64,
execute_and_parse_log_futures, get_block_height_for_reorg_period, parse_logs_in_range,
parse_logs_in_tx, CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64,
};
use crate::{ConnectionConf, CosmosProvider, HyperlaneCosmosError, Signer};
@ -76,12 +76,13 @@ impl MerkleTreeHook for CosmosMerkleTreeHook {
/// Return the incremental merkle tree in storage
#[instrument(level = "debug", err, ret, skip(self))]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
async fn tree(&self, lag: Option<NonZeroU64>) -> ChainResult<IncrementalMerkle> {
async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult<IncrementalMerkle> {
let payload = merkle_tree_hook::MerkleTreeRequest {
tree: general::EmptyStruct {},
};
let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?;
let block_height =
get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?;
let data = self
.provider
@ -110,23 +111,26 @@ impl MerkleTreeHook for CosmosMerkleTreeHook {
}
/// Gets the current leaf count of the merkle tree
async fn count(&self, lag: Option<NonZeroU64>) -> ChainResult<u32> {
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32> {
let payload = merkle_tree_hook::MerkleTreeCountRequest {
count: general::EmptyStruct {},
};
let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?;
let block_height =
get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?;
self.count_at_block(block_height).await
}
#[instrument(level = "debug", err, ret, skip(self))]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
async fn latest_checkpoint(&self, lag: Option<NonZeroU64>) -> ChainResult<Checkpoint> {
async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult<Checkpoint> {
let payload = merkle_tree_hook::CheckPointRequest {
check_point: general::EmptyStruct {},
};
let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?;
let block_height =
get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?;
let data = self
.provider

@ -11,7 +11,7 @@ use tendermint::Hash;
use tokio::task::JoinHandle;
use tracing::warn;
use hyperlane_core::{ChainCommunicationError, ChainResult, Indexed, LogMeta, H256};
use hyperlane_core::{ChainCommunicationError, ChainResult, Indexed, LogMeta, ReorgPeriod, H256};
use crate::grpc::{WasmGrpcProvider, WasmProvider};
use crate::rpc::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider};
@ -24,20 +24,25 @@ pub(crate) const CONTRACT_ADDRESS_ATTRIBUTE_KEY: &str = "_contract_address";
pub(crate) static CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64: Lazy<String> =
Lazy::new(|| BASE64.encode(CONTRACT_ADDRESS_ATTRIBUTE_KEY));
/// Given a lag, returns the block height at the moment.
/// If the lag is None, a block height of None is given, indicating that the
/// tip directly can be used.
pub(crate) async fn get_block_height_for_lag(
/// Given a `reorg_period`, returns the block height at the moment.
/// If the `reorg_period` is None, a block height of None is given,
/// indicating that the tip directly can be used.
pub(crate) async fn get_block_height_for_reorg_period(
provider: &WasmGrpcProvider,
lag: Option<NonZeroU64>,
reorg_period: &ReorgPeriod,
) -> ChainResult<Option<u64>> {
let block_height = match lag {
Some(lag) => {
let block_height = match reorg_period {
ReorgPeriod::Blocks(blocks) => {
let tip = provider.latest_block_height().await?;
let block_height = tip - lag.get();
let block_height = tip - blocks.get() as u64;
Some(block_height)
}
None => None,
ReorgPeriod::None => None,
ReorgPeriod::Tag(_) => {
return Err(ChainCommunicationError::InvalidReorgPeriod(
reorg_period.clone(),
))
}
};
Ok(block_height)

@ -1,4 +1,5 @@
use hyperlane_core::{config::OperationBatchConfig, U256};
use ethers_core::types::{BlockId, BlockNumber};
use hyperlane_core::{config::OperationBatchConfig, ChainCommunicationError, ReorgPeriod, U256};
use url::Url;
/// Ethereum RPC connection configuration
@ -52,3 +53,34 @@ pub struct TransactionOverrides {
/// Max priority fee per gas to use for EIP-1559 transactions.
pub max_priority_fee_per_gas: Option<U256>,
}
/// Ethereum reorg period
#[derive(Copy, Clone, Debug)]
pub enum EthereumReorgPeriod {
/// Number of blocks
Blocks(u32),
/// A block tag
Tag(BlockId),
}
impl TryFrom<&ReorgPeriod> for EthereumReorgPeriod {
type Error = ChainCommunicationError;
fn try_from(value: &ReorgPeriod) -> Result<Self, Self::Error> {
match value {
ReorgPeriod::None => Ok(EthereumReorgPeriod::Blocks(0)),
ReorgPeriod::Blocks(blocks) => Ok(EthereumReorgPeriod::Blocks(blocks.get())),
ReorgPeriod::Tag(tag) => {
let tag = match tag.as_str() {
"latest" => BlockNumber::Latest,
"finalized" => BlockNumber::Finalized,
"safe" => BlockNumber::Safe,
"earliest" => BlockNumber::Earliest,
"pending" => BlockNumber::Pending,
_ => return Err(ChainCommunicationError::InvalidReorgPeriod(value.clone())),
};
Ok(EthereumReorgPeriod::Tag(tag.into()))
}
}
}
}

@ -9,18 +9,18 @@ use async_trait::async_trait;
use ethers::prelude::Middleware;
use hyperlane_core::rpc_clients::call_and_retry_indefinitely;
use hyperlane_core::{
ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain,
HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer,
InterchainGasPaymaster, InterchainGasPayment, LogMeta, SequenceAwareIndexer, H160, H256, H512,
ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain,
HyperlaneProvider, Indexed, Indexer, InterchainGasPaymaster, InterchainGasPayment, LogMeta,
SequenceAwareIndexer, H160, H256, H512,
};
use tracing::instrument;
use super::utils::fetch_raw_logs_and_meta;
use super::utils::{fetch_raw_logs_and_meta, get_finalized_block_number};
use crate::interfaces::i_interchain_gas_paymaster::{
GasPaymentFilter, IInterchainGasPaymaster as EthereumInterchainGasPaymasterInternal,
IINTERCHAINGASPAYMASTER_ABI,
};
use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider};
use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider, EthereumReorgPeriod};
impl<M> Display for EthereumInterchainGasPaymasterInternal<M>
where
@ -33,7 +33,7 @@ where
pub struct InterchainGasPaymasterIndexerBuilder {
pub mailbox_address: H160,
pub reorg_period: u32,
pub reorg_period: EthereumReorgPeriod,
}
#[async_trait]
@ -63,7 +63,7 @@ where
{
contract: Arc<EthereumInterchainGasPaymasterInternal<M>>,
provider: Arc<M>,
reorg_period: u32,
reorg_period: EthereumReorgPeriod,
}
impl<M> EthereumInterchainGasPaymasterIndexer<M>
@ -71,7 +71,11 @@ where
M: Middleware + 'static,
{
/// Create new EthereumInterchainGasPaymasterIndexer
pub fn new(provider: Arc<M>, locator: &ContractLocator, reorg_period: u32) -> Self {
pub fn new(
provider: Arc<M>,
locator: &ContractLocator,
reorg_period: EthereumReorgPeriod,
) -> Self {
Self {
contract: Arc::new(EthereumInterchainGasPaymasterInternal::new(
locator.address,
@ -122,13 +126,7 @@ where
#[instrument(level = "debug", err, ret, skip(self))]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
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))
get_finalized_block_number(&self.provider, &self.reorg_period).await
}
async fn fetch_logs_by_tx_hash(

@ -2,7 +2,6 @@
#![allow(missing_docs)]
use std::collections::HashMap;
use std::num::NonZeroU64;
use std::ops::RangeInclusive;
use std::sync::Arc;
@ -14,7 +13,7 @@ use ethers_contract::builders::ContractCall;
use ethers_contract::{Multicall, MulticallResult};
use futures_util::future::join_all;
use hyperlane_core::rpc_clients::call_and_retry_indefinitely;
use hyperlane_core::{BatchResult, QueueOperation, H512};
use hyperlane_core::{BatchResult, QueueOperation, ReorgPeriod, H512};
use itertools::Itertools;
use tracing::instrument;
@ -31,11 +30,14 @@ use crate::interfaces::i_mailbox::{
IMailbox as EthereumMailboxInternal, ProcessCall, IMAILBOX_ABI,
};
use crate::interfaces::mailbox::DispatchFilter;
use crate::tx::{call_with_lag, fill_tx_gas_params, report_tx};
use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider, TransactionOverrides};
use crate::tx::{call_with_reorg_period, fill_tx_gas_params, report_tx};
use crate::{
BuildableWithProvider, ConnectionConf, EthereumProvider, EthereumReorgPeriod,
TransactionOverrides,
};
use super::multicall::{self, build_multicall};
use super::utils::fetch_raw_logs_and_meta;
use super::utils::{fetch_raw_logs_and_meta, get_finalized_block_number};
impl<M> std::fmt::Display for EthereumMailboxInternal<M>
where
@ -47,7 +49,7 @@ where
}
pub struct SequenceIndexerBuilder {
pub reorg_period: u32,
pub reorg_period: EthereumReorgPeriod,
}
#[async_trait]
@ -70,7 +72,7 @@ impl BuildableWithProvider for SequenceIndexerBuilder {
}
pub struct DeliveryIndexerBuilder {
pub reorg_period: u32,
pub reorg_period: EthereumReorgPeriod,
}
#[async_trait]
@ -100,7 +102,7 @@ where
{
contract: Arc<EthereumMailboxInternal<M>>,
provider: Arc<M>,
reorg_period: u32,
reorg_period: EthereumReorgPeriod,
}
impl<M> EthereumMailboxIndexer<M>
@ -108,7 +110,11 @@ where
M: Middleware + 'static,
{
/// Create new EthereumMailboxIndexer
pub fn new(provider: Arc<M>, locator: &ContractLocator, reorg_period: u32) -> Self {
pub fn new(
provider: Arc<M>,
locator: &ContractLocator,
reorg_period: EthereumReorgPeriod,
) -> Self {
let contract = Arc::new(EthereumMailboxInternal::new(
locator.address,
provider.clone(),
@ -122,13 +128,7 @@ where
#[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))
get_finalized_block_number(&self.provider, &self.reorg_period).await
}
}
@ -460,8 +460,9 @@ where
M: Middleware + 'static,
{
#[instrument(skip(self))]
async fn count(&self, maybe_lag: Option<NonZeroU64>) -> ChainResult<u32> {
let call = call_with_lag(self.contract.nonce(), &self.provider, maybe_lag).await?;
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32> {
let call =
call_with_reorg_period(self.contract.nonce(), &self.provider, reorg_period).await?;
let nonce = call.call().await?;
Ok(nonce)
}

@ -1,5 +1,4 @@
#![allow(missing_docs)]
use std::num::NonZeroU64;
use std::ops::RangeInclusive;
use std::sync::Arc;
@ -10,18 +9,18 @@ use hyperlane_core::rpc_clients::call_and_retry_indefinitely;
use tracing::instrument;
use hyperlane_core::{
ChainCommunicationError, ChainResult, Checkpoint, ContractLocator, HyperlaneChain,
HyperlaneContract, HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, LogMeta,
MerkleTreeHook, MerkleTreeInsertion, SequenceAwareIndexer, H256, H512,
ChainResult, Checkpoint, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain,
HyperlaneProvider, Indexed, Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, ReorgPeriod,
SequenceAwareIndexer, H256, H512,
};
use crate::interfaces::merkle_tree_hook::{
InsertedIntoTreeFilter, MerkleTreeHook as MerkleTreeHookContract, Tree,
};
use crate::tx::call_with_lag;
use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider};
use crate::tx::call_with_reorg_period;
use crate::{BuildableWithProvider, ConnectionConf, EthereumProvider, EthereumReorgPeriod};
use super::utils::fetch_raw_logs_and_meta;
use super::utils::{fetch_raw_logs_and_meta, get_finalized_block_number};
// We don't need the reverse of this impl, so it's ok to disable the clippy lint
#[allow(clippy::from_over_into)]
@ -58,7 +57,7 @@ impl BuildableWithProvider for MerkleTreeHookBuilder {
}
pub struct MerkleTreeHookIndexerBuilder {
pub reorg_period: u32,
pub reorg_period: EthereumReorgPeriod,
}
#[async_trait]
@ -88,7 +87,7 @@ where
{
contract: Arc<MerkleTreeHookContract<M>>,
provider: Arc<M>,
reorg_period: u32,
reorg_period: EthereumReorgPeriod,
}
impl<M> EthereumMerkleTreeHookIndexer<M>
@ -96,7 +95,11 @@ where
M: Middleware + 'static,
{
/// Create new EthereumMerkleTreeHookIndexer
pub fn new(provider: Arc<M>, locator: &ContractLocator, reorg_period: u32) -> Self {
pub fn new(
provider: Arc<M>,
locator: &ContractLocator,
reorg_period: EthereumReorgPeriod,
) -> Self {
Self {
contract: Arc::new(MerkleTreeHookContract::new(
locator.address,
@ -143,13 +146,7 @@ where
#[instrument(level = "debug", err, skip(self))]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
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))
get_finalized_block_number(&self.provider, &self.reorg_period).await
}
async fn fetch_logs_by_tx_hash(
@ -253,9 +250,13 @@ 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?;
async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult<Checkpoint> {
let call = call_with_reorg_period(
self.contract.latest_checkpoint(),
&self.provider,
reorg_period,
)
.await?;
let (root, index) = call.call().await?;
Ok(Checkpoint {
@ -268,15 +269,17 @@ where
#[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?;
async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult<IncrementalMerkle> {
let call =
call_with_reorg_period(self.contract.tree(), &self.provider, reorg_period).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?;
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32> {
let call =
call_with_reorg_period(self.contract.count(), &self.provider, reorg_period).await?;
let count = call.call().await?;
Ok(count)
}

@ -1,5 +1,7 @@
pub use {interchain_gas::*, mailbox::*, merkle_tree_hook::*, validator_announce::*};
pub(crate) use utils::get_finalized_block_number;
mod interchain_gas;
mod mailbox;
mod merkle_tree_hook;

@ -6,7 +6,10 @@ use ethers::{
types::{H160 as EthersH160, H256 as EthersH256},
};
use ethers_contract::{ContractError, EthEvent, LogMeta as EthersLogMeta};
use hyperlane_core::{ChainResult, LogMeta, H512};
use hyperlane_core::{ChainCommunicationError, ChainResult, LogMeta, H512};
use tracing::instrument;
use crate::EthereumReorgPeriod;
pub async fn fetch_raw_logs_and_meta<T: EthEvent, M>(
tx_hash: H512,
@ -44,3 +47,33 @@ where
.collect();
Ok(logs)
}
#[instrument(level = "trace", err, ret, skip(provider))]
pub async fn get_finalized_block_number<M>(
provider: &M,
reorg_period: &EthereumReorgPeriod,
) -> ChainResult<u32>
where
M: Middleware + 'static,
{
let number = match *reorg_period {
EthereumReorgPeriod::Blocks(blocks) => provider
.get_block_number()
.await
.map_err(ChainCommunicationError::from_other)?
.as_u32()
.saturating_sub(blocks),
EthereumReorgPeriod::Tag(tag) => provider
.get_block(tag)
.await
.map_err(ChainCommunicationError::from_other)?
.and_then(|block| block.number)
.ok_or(ChainCommunicationError::CustomError(
"Unable to get finalized block number".into(),
))?
.as_u32(),
};
Ok(number)
}

@ -1,4 +1,3 @@
use std::num::NonZeroU64;
use std::sync::Arc;
use std::time::Duration;
@ -16,10 +15,12 @@ use ethers_core::{
EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE,
},
};
use hyperlane_core::{utils::bytes_to_hex, ChainCommunicationError, ChainResult, H256, U256};
use hyperlane_core::{
utils::bytes_to_hex, ChainCommunicationError, ChainResult, ReorgPeriod, H256, U256,
};
use tracing::{debug, error, info, warn};
use crate::{Middleware, TransactionOverrides};
use crate::{get_finalized_block_number, EthereumReorgPeriod, Middleware, TransactionOverrides};
/// An amount of gas to add to the estimated gas
pub const GAS_ESTIMATE_BUFFER: u32 = 75_000;
@ -216,23 +217,20 @@ where
Ok((base_fee_per_gas, max_fee_per_gas, max_priority_fee_per_gas))
}
pub(crate) async fn call_with_lag<M, T>(
pub(crate) async fn call_with_reorg_period<M, T>(
call: ethers::contract::builders::ContractCall<M, T>,
provider: &M,
maybe_lag: Option<NonZeroU64>,
reorg_period: &ReorgPeriod,
) -> ChainResult<ethers::contract::builders::ContractCall<M, T>>
where
M: Middleware + 'static,
T: Detokenize,
{
if let Some(lag) = maybe_lag {
let fixed_block_number: BlockNumber = provider
.get_block_number()
.await
.map_err(ChainCommunicationError::from_other)?
.saturating_sub(lag.get().into())
.into();
Ok(call.block(fixed_block_number))
if !reorg_period.is_none() {
let reorg_period = EthereumReorgPeriod::try_from(reorg_period)?;
let block = get_finalized_block_number(provider, &reorg_period).await? as u64;
Ok(call.block(block))
} else {
Ok(call)
}

@ -10,13 +10,12 @@ use fuels::{
use hyperlane_core::{
utils::bytes_to_hex, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi,
HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider,
Indexed, Indexer, LogMeta, Mailbox, RawHyperlaneMessage, SequenceAwareIndexer, TxCostEstimate,
TxOutcome, H256, H512, U256,
Indexed, Indexer, LogMeta, Mailbox, RawHyperlaneMessage, ReorgPeriod, SequenceAwareIndexer,
TxCostEstimate, TxOutcome, H256, H512, U256,
};
use std::{
collections::HashMap,
fmt::{Debug, Formatter},
num::NonZeroU64,
ops::RangeInclusive,
};
use tracing::{instrument, warn};
@ -74,9 +73,9 @@ impl Debug for FuelMailbox {
impl Mailbox for FuelMailbox {
#[instrument(level = "debug", err, ret, skip(self))]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
async fn count(&self, lag: Option<NonZeroU64>) -> ChainResult<u32> {
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32> {
assert!(
lag.is_none(),
reorg_period.is_none(),
"Fuel does not support querying point-in-time"
);
self.contract

@ -12,8 +12,8 @@ use hyperlane_core::{
ChainCommunicationError::ContractError, ChainResult, Checkpoint, ContractLocator, Decode as _,
Encode as _, FixedPointNumber, HyperlaneAbi, HyperlaneChain, HyperlaneContract,
HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, KnownHyperlaneDomain,
LogMeta, Mailbox, MerkleTreeHook, SequenceAwareIndexer, TxCostEstimate, TxOutcome, H256, H512,
U256,
LogMeta, Mailbox, MerkleTreeHook, ReorgPeriod, SequenceAwareIndexer, TxCostEstimate, TxOutcome,
H256, H512, U256,
};
use hyperlane_sealevel_interchain_security_module_interface::{
InterchainSecurityModuleInstruction, VerifyInstruction,
@ -416,8 +416,8 @@ impl std::fmt::Debug for SealevelMailbox {
#[async_trait]
impl Mailbox for SealevelMailbox {
#[instrument(err, ret, skip(self))]
async fn count(&self, _maybe_lag: Option<NonZeroU64>) -> ChainResult<u32> {
<Self as MerkleTreeHook>::count(self, _maybe_lag).await
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32> {
<Self as MerkleTreeHook>::count(self, reorg_period).await
}
#[instrument(err, ret, skip(self))]
@ -755,7 +755,7 @@ impl SequenceAwareIndexer<HyperlaneMessage> for SealevelMailboxIndexer {
async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option<u32>, u32)> {
let tip = Indexer::<HyperlaneMessage>::get_finalized_block_number(self).await?;
// TODO: need to make sure the call and tip are at the same height?
let count = Mailbox::count(&self.mailbox, None).await?;
let count = Mailbox::count(&self.mailbox, &ReorgPeriod::None).await?;
Ok((Some(count), tip))
}
}

@ -1,11 +1,11 @@
use std::{num::NonZeroU64, ops::RangeInclusive};
use std::ops::RangeInclusive;
use async_trait::async_trait;
use derive_new::new;
use hyperlane_core::{
accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint,
HyperlaneChain, HyperlaneMessage, Indexed, Indexer, LogMeta, MerkleTreeHook,
MerkleTreeInsertion, SequenceAwareIndexer,
MerkleTreeInsertion, ReorgPeriod, SequenceAwareIndexer,
};
use hyperlane_sealevel_mailbox::accounts::OutboxAccount;
use tracing::instrument;
@ -16,9 +16,9 @@ use crate::{SealevelMailbox, SealevelMailboxIndexer};
impl MerkleTreeHook for SealevelMailbox {
#[instrument(err, ret, skip(self))]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
async fn tree(&self, lag: Option<NonZeroU64>) -> ChainResult<IncrementalMerkle> {
async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult<IncrementalMerkle> {
assert!(
lag.is_none(),
reorg_period.is_none(),
"Sealevel does not support querying point-in-time"
);
@ -35,13 +35,13 @@ impl MerkleTreeHook for SealevelMailbox {
#[instrument(err, ret, skip(self))]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
async fn latest_checkpoint(&self, lag: Option<NonZeroU64>) -> ChainResult<Checkpoint> {
async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult<Checkpoint> {
assert!(
lag.is_none(),
reorg_period.is_none(),
"Sealevel does not support querying point-in-time"
);
let tree = self.tree(lag).await?;
let tree = self.tree(reorg_period).await?;
let root = tree.root();
let count: u32 = tree
@ -64,8 +64,8 @@ impl MerkleTreeHook for SealevelMailbox {
#[instrument(err, ret, skip(self))]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
async fn count(&self, _maybe_lag: Option<NonZeroU64>) -> ChainResult<u32> {
let tree = self.tree(_maybe_lag).await?;
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32> {
let tree = self.tree(reorg_period).await?;
tree.count()
.try_into()

@ -10,13 +10,13 @@ use hyperlane_core::{
config::OperationBatchConfig, AggregationIsm, CcipReadIsm, ContractLocator, HyperlaneAbi,
HyperlaneDomain, HyperlaneDomainProtocol, HyperlaneMessage, HyperlaneProvider, IndexMode,
InterchainGasPaymaster, InterchainGasPayment, InterchainSecurityModule, Mailbox,
MerkleTreeHook, MerkleTreeInsertion, MultisigIsm, RoutingIsm, SequenceAwareIndexer,
ValidatorAnnounce, H256,
MerkleTreeHook, MerkleTreeInsertion, MultisigIsm, ReorgPeriod, RoutingIsm,
SequenceAwareIndexer, ValidatorAnnounce, H256,
};
use hyperlane_cosmos as h_cosmos;
use hyperlane_ethereum::{
self as h_eth, BuildableWithProvider, EthereumInterchainGasPaymasterAbi, EthereumMailboxAbi,
EthereumValidatorAnnounceAbi,
EthereumReorgPeriod, EthereumValidatorAnnounceAbi,
};
use hyperlane_fuel as h_fuel;
use hyperlane_sealevel as h_sealevel;
@ -45,7 +45,7 @@ pub struct ChainConf {
/// Signer configuration for this chain
pub signer: Option<SignerConf>,
/// The reorg period of the chain, i.e. the number of blocks until finality
pub reorg_period: u32,
pub reorg_period: ReorgPeriod,
/// Addresses of contracts on the chain
pub addresses: CoreContractAddresses,
/// The chain connection details
@ -272,13 +272,13 @@ impl ChainConf {
match &self.connection {
ChainConnectionConf::Ethereum(conf) => {
let reorg_period =
EthereumReorgPeriod::try_from(&self.reorg_period).context(ctx)?;
self.build_ethereum(
conf,
&locator,
metrics,
h_eth::SequenceIndexerBuilder {
reorg_period: self.reorg_period,
},
h_eth::SequenceIndexerBuilder { reorg_period },
)
.await
}
@ -289,11 +289,12 @@ impl ChainConf {
}
ChainConnectionConf::Cosmos(conf) => {
let signer = self.cosmos_signer().await.context(ctx)?;
let reorg_period = self.reorg_period.as_blocks().context(ctx)?;
let indexer = Box::new(h_cosmos::CosmosMailboxDispatchIndexer::new(
conf.clone(),
locator,
signer,
self.reorg_period,
reorg_period,
)?);
Ok(indexer as Box<dyn SequenceAwareIndexer<HyperlaneMessage>>)
}
@ -311,13 +312,13 @@ impl ChainConf {
match &self.connection {
ChainConnectionConf::Ethereum(conf) => {
let reorg_period =
EthereumReorgPeriod::try_from(&self.reorg_period).context(ctx)?;
self.build_ethereum(
conf,
&locator,
metrics,
h_eth::DeliveryIndexerBuilder {
reorg_period: self.reorg_period,
},
h_eth::DeliveryIndexerBuilder { reorg_period },
)
.await
}
@ -328,11 +329,12 @@ impl ChainConf {
}
ChainConnectionConf::Cosmos(conf) => {
let signer = self.cosmos_signer().await.context(ctx)?;
let reorg_period = self.reorg_period.as_blocks().context(ctx)?;
let indexer = Box::new(h_cosmos::CosmosMailboxDeliveryIndexer::new(
conf.clone(),
locator,
signer,
self.reorg_period,
reorg_period,
)?);
Ok(indexer as Box<dyn SequenceAwareIndexer<H256>>)
}
@ -389,13 +391,15 @@ impl ChainConf {
match &self.connection {
ChainConnectionConf::Ethereum(conf) => {
let reorg_period =
EthereumReorgPeriod::try_from(&self.reorg_period).context(ctx)?;
self.build_ethereum(
conf,
&locator,
metrics,
h_eth::InterchainGasPaymasterIndexerBuilder {
mailbox_address: self.addresses.mailbox.into(),
reorg_period: self.reorg_period,
reorg_period,
},
)
.await
@ -408,10 +412,11 @@ impl ChainConf {
Ok(indexer as Box<dyn SequenceAwareIndexer<InterchainGasPayment>>)
}
ChainConnectionConf::Cosmos(conf) => {
let reorg_period = self.reorg_period.as_blocks().context(ctx)?;
let indexer = Box::new(h_cosmos::CosmosInterchainGasPaymasterIndexer::new(
conf.clone(),
locator,
self.reorg_period,
reorg_period,
)?);
Ok(indexer as Box<dyn SequenceAwareIndexer<InterchainGasPayment>>)
}
@ -429,13 +434,13 @@ impl ChainConf {
match &self.connection {
ChainConnectionConf::Ethereum(conf) => {
let reorg_period =
EthereumReorgPeriod::try_from(&self.reorg_period).context(ctx)?;
self.build_ethereum(
conf,
&locator,
metrics,
h_eth::MerkleTreeHookIndexerBuilder {
reorg_period: self.reorg_period,
},
h_eth::MerkleTreeHookIndexerBuilder { reorg_period },
)
.await
}
@ -450,12 +455,13 @@ impl ChainConf {
}
ChainConnectionConf::Cosmos(conf) => {
let signer = self.cosmos_signer().await.context(ctx)?;
let reorg_period = self.reorg_period.as_blocks().context(ctx)?;
let indexer = Box::new(h_cosmos::CosmosMerkleTreeHookIndexer::new(
conf.clone(),
locator,
// TODO: remove signer requirement entirely
signer,
self.reorg_period,
reorg_period,
)?);
Ok(indexer as Box<dyn SequenceAwareIndexer<MerkleTreeInsertion>>)
}

@ -180,7 +180,7 @@ mod test {
use std::panic::AssertUnwindSafe;
use futures_util::FutureExt;
use hyperlane_core::{ReorgEvent, H256};
use hyperlane_core::{ReorgEvent, ReorgPeriod, H256};
#[tokio::test]
async fn test_build_and_validate() {
@ -209,7 +209,7 @@ mod test {
.unwrap();
let dummy_checkpoint_index = 56;
let unix_timestamp = 1620000000;
let reorg_period = 5;
let reorg_period = ReorgPeriod::from_blocks(5);
let dummy_reorg_event = ReorgEvent {
local_merkle_root: dummy_local_merkle_root,
canonical_merkle_root: dummy_canonical_merkle_root,
@ -237,7 +237,9 @@ mod test {
canonical_merkle_root: 0xb437b888332ef12f7260c7f679aad3c96b91ab81c2dc7242f8b290f0b6bba92b,
checkpoint_index: 56,
unix_timestamp: 1620000000,
reorg_period: 5,
reorg_period: Blocks(
5,
),
}. Please resolve the reorg to continue."#
);
} else {

@ -19,7 +19,7 @@ use url::Url;
use h_cosmos::RawCosmosAmount;
use hyperlane_core::{
cfg_unwrap_all, config::*, HyperlaneDomain, HyperlaneDomainProtocol,
HyperlaneDomainTechnicalStack, IndexMode,
HyperlaneDomainTechnicalStack, IndexMode, ReorgPeriod,
};
use crate::settings::{
@ -136,8 +136,8 @@ fn parse_chain(
.chain(&mut err)
.get_opt_key("blocks")
.get_key("reorgPeriod")
.parse_u32()
.unwrap_or(1);
.parse_value("Invalid reorgPeriod")
.unwrap_or(ReorgPeriod::from_blocks(1));
let rpcs = parse_base_and_override_urls(&chain, "rpcUrls", "customRpcUrls", "http", &mut err);

@ -3,16 +3,20 @@
use std::{
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
num::NonZeroU32,
};
use derive_new::new;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use serde::Serialize;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "strum")]
use strum::{EnumIter, EnumString, IntoStaticStr};
use crate::{utils::many_to_one, HyperlaneProtocolError, IndexMode, H160, H256};
use crate::{
utils::many_to_one, ChainCommunicationError, HyperlaneProtocolError, IndexMode, H160, H256,
};
#[derive(Debug, Clone)]
pub struct Address(pub bytes::Bytes);
@ -39,6 +43,80 @@ impl<'a> std::fmt::Display for ContractLocator<'a> {
}
}
#[derive(Default, Debug, Clone, PartialEq)]
pub enum ReorgPeriod {
#[default]
None,
Blocks(NonZeroU32),
Tag(String),
}
impl ReorgPeriod {
pub fn from_blocks(blocks: u32) -> Self {
NonZeroU32::try_from(blocks)
.map(ReorgPeriod::Blocks)
.unwrap_or(ReorgPeriod::None)
}
pub fn as_blocks(&self) -> Result<u32, ChainCommunicationError> {
match self {
ReorgPeriod::None => Ok(0),
ReorgPeriod::Blocks(blocks) => Ok(blocks.get()),
ReorgPeriod::Tag(_) => Err(ChainCommunicationError::InvalidReorgPeriod(self.clone())),
}
}
pub fn is_none(&self) -> bool {
matches!(self, ReorgPeriod::None)
}
}
impl Serialize for ReorgPeriod {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ReorgPeriod::None => serializer.serialize_u32(0),
ReorgPeriod::Blocks(blocks) => serializer.serialize_u32(blocks.get()),
ReorgPeriod::Tag(tag) => serializer.serialize_str(tag),
}
}
}
impl<'de> Deserialize<'de> for ReorgPeriod {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de;
struct ReorgPeriodVisitor;
impl<'de> de::Visitor<'de> for ReorgPeriodVisitor {
type Value = ReorgPeriod;
fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
f.write_str("reorgPeriod as a number or string")
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
let v = v.try_into().map_err(de::Error::custom)?;
Ok(ReorgPeriod::from_blocks(v))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
match v.parse::<u32>() {
Ok(v) => self.visit_u32(v),
Err(_) => Ok(ReorgPeriod::Tag(v.to_string())),
}
}
}
deserializer.deserialize_any(ReorgPeriodVisitor)
}
}
/// All domains supported by Hyperlane.
#[derive(FromPrimitive, PartialEq, Eq, Debug, Clone, Copy, Hash, Serialize)]
#[cfg_attr(
@ -505,9 +583,9 @@ impl HyperlaneDomain {
#[cfg(test)]
#[cfg(feature = "strum")]
mod tests {
use std::str::FromStr;
use std::{num::NonZeroU32, str::FromStr};
use crate::KnownHyperlaneDomain;
use crate::{KnownHyperlaneDomain, ReorgPeriod};
#[test]
fn domain_strings() {
@ -560,4 +638,32 @@ mod tests {
);
assert!("foo".parse::<KnownHyperlaneDomain>().is_err());
}
#[test]
fn parse_reorg_period() {
assert_eq!(
serde_json::from_value::<ReorgPeriod>(0.into()).unwrap(),
ReorgPeriod::None
);
assert_eq!(
serde_json::from_value::<ReorgPeriod>("0".into()).unwrap(),
ReorgPeriod::None
);
assert_eq!(
serde_json::from_value::<ReorgPeriod>(12.into()).unwrap(),
ReorgPeriod::Blocks(NonZeroU32::new(12).unwrap())
);
assert_eq!(
serde_json::from_value::<ReorgPeriod>("12".into()).unwrap(),
ReorgPeriod::Blocks(NonZeroU32::new(12).unwrap())
);
assert_eq!(
serde_json::from_value::<ReorgPeriod>("finalized".into()).unwrap(),
ReorgPeriod::Tag("finalized".into())
);
}
}

@ -10,8 +10,10 @@ use crate::config::StrOrIntParseError;
use crate::rpc_clients::RpcClientError;
use std::string::FromUtf8Error;
use crate::HyperlaneProviderError;
use crate::{Error as PrimitiveTypeError, HyperlaneSignerError, H256, U256};
use crate::{
Error as PrimitiveTypeError, HyperlaneProviderError, HyperlaneSignerError, ReorgPeriod, H256,
U256,
};
/// The result of interacting with a chain.
pub type ChainResult<T> = Result<T, ChainCommunicationError>;
@ -157,6 +159,9 @@ pub enum ChainCommunicationError {
/// Hyperlane signer error
#[error("{0}")]
HyperlaneSignerError(#[from] HyperlaneSignerError),
/// Invalid reorg period
#[error("Invalid reorg period: {0:?}")]
InvalidReorgPeriod(ReorgPeriod),
}
impl ChainCommunicationError {

@ -1,12 +1,11 @@
use std::fmt::Debug;
use std::num::NonZeroU64;
use async_trait::async_trait;
use derive_new::new;
use crate::{
traits::TxOutcome, utils::domain_hash, BatchItem, ChainCommunicationError, ChainResult,
HyperlaneContract, HyperlaneMessage, QueueOperation, TxCostEstimate, H256, U256,
HyperlaneContract, HyperlaneMessage, QueueOperation, ReorgPeriod, TxCostEstimate, H256, U256,
};
/// Interface for the Mailbox chain contract. Allows abstraction over different
@ -20,9 +19,9 @@ pub trait Mailbox: HyperlaneContract + Send + Sync + Debug {
/// Gets the current leaf count of the merkle tree
///
/// - `lag` is how far behind the current block to query, if not specified
/// - `reorg_period` is how far behind the current block to query, if not specified
/// it will query at the latest block.
async fn count(&self, lag: Option<NonZeroU64>) -> ChainResult<u32>;
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32>;
/// Fetch the status of a message
async fn delivered(&self, id: H256) -> ChainResult<bool>;

@ -1,11 +1,11 @@
use std::fmt::Debug;
use std::num::NonZeroU64;
use async_trait::async_trait;
use auto_impl::auto_impl;
use crate::{
accumulator::incremental::IncrementalMerkle, ChainResult, Checkpoint, HyperlaneContract,
ReorgPeriod,
};
/// Interface for the MerkleTreeHook chain contract. Allows abstraction over different
@ -15,19 +15,19 @@ use crate::{
pub trait MerkleTreeHook: HyperlaneContract + Send + Sync + Debug {
/// Return the incremental merkle tree in storage
///
/// - `lag` is how far behind the current block to query, if not specified
/// - `reorg_period` is how far behind the current block to query, if not specified
/// it will query at the latest block.
async fn tree(&self, lag: Option<NonZeroU64>) -> ChainResult<IncrementalMerkle>;
async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult<IncrementalMerkle>;
/// Gets the current leaf count of the merkle tree
///
/// - `lag` is how far behind the current block to query, if not specified
/// - `reorg_period` is how far behind the current block to query, if not specified
/// it will query at the latest block.
async fn count(&self, lag: Option<NonZeroU64>) -> ChainResult<u32>;
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32>;
/// Get the latest checkpoint.
///
/// - `lag` is how far behind the current block to query, if not specified
/// - `reorg_period` is how far behind the current block to query, if not specified
/// it will query at the latest block.
async fn latest_checkpoint(&self, lag: Option<NonZeroU64>) -> ChainResult<Checkpoint>;
async fn latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult<Checkpoint>;
}

@ -1,7 +1,7 @@
use derive_new::new;
use serde::{Deserialize, Serialize};
use crate::H256;
use crate::{ReorgPeriod, H256};
/// Details about a detected chain reorg, from an agent's perspective
#[derive(Debug, Clone, Serialize, Deserialize, new)]
@ -15,6 +15,6 @@ pub struct ReorgEvent {
pub checkpoint_index: u32,
/// the timestamp when the reorg was detected, in seconds since the Unix epoch
pub unix_timestamp: u64,
/// the reorg period configured for the agent, in blocks
pub reorg_period: u64,
/// the reorg period configured for the agent
pub reorg_period: ReorgPeriod,
}

@ -1,7 +1,5 @@
#![allow(non_snake_case)]
use std::num::NonZeroU64;
use async_trait::async_trait;
use mockall::*;
@ -28,11 +26,11 @@ mock! {
nonce: usize,
) -> ChainResult<Option<H256>> {}
pub fn _tree(&self, maybe_lag: Option<NonZeroU64>) -> ChainResult<IncrementalMerkle> {}
pub fn _tree(&self, reorg_period: &ReorgPeriod) -> ChainResult<IncrementalMerkle> {}
pub fn _count(&self, maybe_lag: Option<NonZeroU64>) -> ChainResult<u32> {}
pub fn _count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32> {}
pub fn _latest_checkpoint(&self, maybe_lag: Option<NonZeroU64>) -> ChainResult<Checkpoint> {}
pub fn _latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult<Checkpoint> {}
pub fn _default_ism(&self) -> ChainResult<H256> {}
pub fn _recipient_ism(&self, recipient: H256) -> ChainResult<H256> {}
@ -68,8 +66,8 @@ impl std::fmt::Debug for MockMailboxContract {
#[async_trait]
impl Mailbox for MockMailboxContract {
async fn count(&self, maybe_lag: Option<NonZeroU64>) -> ChainResult<u32> {
self._count(maybe_lag)
async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult<u32> {
self._count(reorg_period)
}
async fn default_ism(&self) -> ChainResult<H256> {

@ -168,6 +168,11 @@ async function addBlockOrGasConfig(metadata: ChainMetadata): Promise<void> {
}
async function addBlockConfig(metadata: ChainMetadata): Promise<void> {
const parseReorgPeriod = (value: string) => {
const parsed = parseInt(value, 10);
return isNaN(parsed) ? value : parsed;
};
const wantBlockConfig = await confirm({
message: 'Do you want to add block config for this chain',
});
@ -179,8 +184,10 @@ async function addBlockConfig(metadata: ChainMetadata): Promise<void> {
});
const blockReorgPeriod = await input({
message:
'Enter no. of blocks before a transaction has a near-zero chance of reverting (0-500):',
validate: (value) => parseInt(value) >= 0 && parseInt(value) <= 500,
'Enter no. of blocks before a transaction has a near-zero chance of reverting (0-500) or block tag:',
validate: (value) =>
isNaN(parseInt(value)) ||
(parseInt(value) >= 0 && parseInt(value) <= 500),
});
const blockTimeEstimate = await input({
message: 'Enter the rough estimate of time per block in seconds (0-20):',
@ -188,7 +195,7 @@ async function addBlockConfig(metadata: ChainMetadata): Promise<void> {
});
metadata.blocks = {
confirmations: parseInt(blockConfirmation, 10),
reorgPeriod: parseInt(blockReorgPeriod, 10),
reorgPeriod: parseReorgPeriod(blockReorgPeriod),
estimateBlockTime: parseInt(blockTimeEstimate, 10),
};
}

@ -78,7 +78,7 @@ export function getDomainId(chainName: ChainName): number {
return resolveDomainId(chain);
}
export function getReorgPeriod(chainName: ChainName): number {
export function getReorgPeriod(chainName: ChainName): string | number {
const chain = getChain(chainName);
return resolveReorgPeriod(chain);
}

@ -26,8 +26,8 @@ export type ValidatorBaseChainConfigMap = ChainMap<ValidatorBaseChainConfig>;
export interface ValidatorBaseChainConfig {
// How frequently to check for new checkpoints
interval: number;
// The reorg_period in blocks; overrides chain metadata
reorgPeriod: number;
// The reorg_period in blocks or block tag; overrides chain metadata
reorgPeriod: string | number;
// Individual validator agents
validators: Array<ValidatorBaseConfig>;
}

@ -62,6 +62,16 @@ describe('ChainMetadataSchema', () => {
grpcUrls: [],
}),
).to.eq(true);
expect(
isValidChainMetadata({
...minimalSchema,
blocks: {
confirmations: 1,
reorgPeriod: 'finalized',
},
}),
).to.eq(true);
});
it('Rejects invalid schemas', () => {

@ -125,8 +125,11 @@ export const ChainMetadataSchemaObject = z.object({
confirmations: ZUint.describe(
'Number of blocks to wait before considering a transaction confirmed.',
),
reorgPeriod: ZUint.optional().describe(
'Number of blocks before a transaction has a near-zero chance of reverting.',
reorgPeriod: z
.union([ZUint, z.string()])
.optional()
.describe(
'Number of blocks before a transaction has a near-zero chance of reverting or block tag.',
),
estimateBlockTime: z
.number()
@ -371,7 +374,7 @@ export function getChainIdNumber(chainMetadata: ChainMetadata): number {
else throw new Error('ChainId is not a number, chain may be of Cosmos type');
}
export function getReorgPeriod(chainMetadata: ChainMetadata): number {
export function getReorgPeriod(chainMetadata: ChainMetadata): string | number {
if (chainMetadata.blocks?.reorgPeriod !== undefined)
return chainMetadata.blocks.reorgPeriod;
else throw new Error('Chain has no reorg period');

Loading…
Cancel
Save