Skip to content

Commit

Permalink
Make merkle proofs optional on multisig ISM (#2173)
Browse files Browse the repository at this point in the history
### Description

Validators currently sign `(root, index)` checkpoints and during
verification, a `message` is passed as calldata, an `id()` is derived,
and a `proof` of `id()` at `index` in `root` is verified

This provides “all or nothing” censorship resistance guarantees because
a validator can only sign roots to allow any contained messages to be
processed.

We have considered alternatives where validators sign `message` directly
and we lose censorship resistance in exchange for eliminating merkle
proof verification gas costs.

However, if validators sign `(root, index, message)` tuples, we can skip
merkle proof verification on the destination chain while still
maintaining censorship resistance by providing two valid metadata
formats:

1. existing validator signatures and merkle proof verification of
inclusion
2. including merkle proof verification for pathway where validators are
censoring `message`

It’s worth noting the validator is required to index event data to
produce this new signature format. However, this does not require
historical indexing and new validators being spun up can simply begin
indexing from tip.

See #2187 for
validator changes
See #2248 for
relayer and e2e test changes

### Drive-by changes

Merkle index also optional

### Related issues

- Fixes #2192

### Backward compatibility

- new ISM deployment is necessary (we could upgrade implementation in
theory)
- Validator and relayer upgrades

### Testing

Unit (fuzz) Tests, E2E tests
  • Loading branch information
yorhodes authored May 25, 2023
1 parent 8aa7b62 commit 50f04db
Show file tree
Hide file tree
Showing 60 changed files with 1,877 additions and 732 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
toolchain: stable
profile: minimal

- name: Install Foundry
uses: onbjerg/foundry-toolchain@v1

- name: rust cache
uses: Swatinem/rust-cache@v2
with:
Expand Down
3 changes: 3 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ async-trait = { version = "0.1" }
color-eyre = { version = "0.6" }
config = "~0.13.3"
derive-new = "0.5"
derive_more = "0.99"
enum_dispatch = "0.3"
ethers = { git = "https://github.com/hyperlane-xyz/ethers-rs", tag = "2023-02-10-01" }
ethers-contract = { git = "https://github.com/hyperlane-xyz/ethers-rs", tag = "2023-02-10-01", features = ["legacy"] }
Expand Down
1 change: 1 addition & 0 deletions rust/agents/relayer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ hyperlane-base = { path = "../../hyperlane-base" }
hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" }
num-derive.workspace = true
num-traits.workspace = true
derive_more.workspace = true

[dev-dependencies]
tokio-test = "0.4"
Expand Down
83 changes: 36 additions & 47 deletions rust/agents/relayer/src/msg/metadata/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,33 @@ use std::{collections::HashMap, fmt::Debug};
use async_trait::async_trait;
use derive_new::new;
use eyre::{Context, Result};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use tokio::sync::RwLock;
use tracing::{debug, instrument, warn};
use tracing::{debug, info, instrument, warn};

use hyperlane_base::{
ChainConf, CheckpointSyncer, CheckpointSyncerConf, CoreMetrics, MultisigCheckpointSyncer,
};
use hyperlane_core::accumulator::merkle::Proof;
use hyperlane_core::{
HyperlaneDomain, HyperlaneMessage, MultisigIsm, MultisigSignedCheckpoint, RoutingIsm,
Checkpoint, HyperlaneDomain, HyperlaneMessage, ModuleType, MultisigIsm, RoutingIsm,
ValidatorAnnounce, H160, H256,
};

use crate::merkle_tree_builder::MerkleTreeBuilder;
use crate::msg::metadata::{MultisigIsmMetadataBuilder, RoutingIsmMetadataBuilder};
use crate::msg::metadata::multisig::{
LegacyMultisigMetadataBuilder, MerkleRootMultisigMetadataBuilder,
MessageIdMultisigMetadataBuilder,
};
use crate::msg::metadata::RoutingIsmMetadataBuilder;

#[derive(Debug, thiserror::Error)]
pub enum MetadataBuilderError {
#[error("Unknown or invalid module type ({0})")]
UnsupportedModuleType(u8),
UnsupportedModuleType(ModuleType),
#[error("Exceeded max depth when building metadata ({0})")]
MaxDepthExceeded(u32),
}

#[derive(FromPrimitive, Clone, Debug)]
pub enum SupportedIsmTypes {
Routing = 1,
// Aggregation = 2,
LegacyMultisig = 3,
Multisig = 4,
}

#[async_trait]
pub trait MetadataBuilder: Send + Sync {
#[allow(clippy::async_yields_async)]
Expand Down Expand Up @@ -71,7 +65,7 @@ impl Debug for BaseMetadataBuilder {

#[async_trait]
impl MetadataBuilder for BaseMetadataBuilder {
#[instrument(err, skip(self))]
#[instrument(err, skip(self), fields(domain=self.domain().name()))]
async fn build(
&self,
ism_address: H256,
Expand All @@ -84,17 +78,16 @@ impl MetadataBuilder for BaseMetadataBuilder {
.await
.context(CTX)?;
let module_type = ism.module_type().await.context(CTX)?;
let supported_type = SupportedIsmTypes::from_u8(module_type)
.ok_or(MetadataBuilderError::UnsupportedModuleType(module_type))
.context(CTX)?;
let base = self.clone_with_incremented_depth()?;

let metadata_builder: Box<dyn MetadataBuilder> = match supported_type {
SupportedIsmTypes::Multisig => Box::new(MultisigIsmMetadataBuilder::new(base, false)),
SupportedIsmTypes::LegacyMultisig => {
Box::new(MultisigIsmMetadataBuilder::new(base, true))
let metadata_builder: Box<dyn MetadataBuilder> = match module_type {
ModuleType::LegacyMultisig => Box::new(LegacyMultisigMetadataBuilder::new(base)),
ModuleType::MerkleRootMultisig => {
Box::new(MerkleRootMultisigMetadataBuilder::new(base))
}
SupportedIsmTypes::Routing => Box::new(RoutingIsmMetadataBuilder::new(base)),
ModuleType::MessageIdMultisig => Box::new(MessageIdMultisigMetadataBuilder::new(base)),
ModuleType::Routing => Box::new(RoutingIsmMetadataBuilder::new(base)),
_ => return Err(MetadataBuilderError::UnsupportedModuleType(module_type).into()),
};
metadata_builder
.build(ism_address, message)
Expand All @@ -118,35 +111,31 @@ impl BaseMetadataBuilder {
}
}

pub async fn get_proof(
&self,
message: &HyperlaneMessage,
checkpoint: MultisigSignedCheckpoint,
) -> Result<Proof> {
pub async fn get_proof(&self, nonce: u32, checkpoint: Checkpoint) -> Result<Option<Proof>> {
const CTX: &str = "When fetching message proof";
self.origin_prover_sync
let proof = self
.origin_prover_sync
.read()
.await
.get_proof(message.nonce, checkpoint.checkpoint.index)
.context(CTX)
.get_proof(nonce, checkpoint.index)
.context(CTX)?;

// checkpoint may be fraudulent if the root does not
// match the canonical root at the checkpoint's index
if proof.root() != checkpoint.root {
info!(
?checkpoint,
canonical_root = ?proof.root(),
"Could not fetch metadata: checkpoint root does not match canonical root from merkle proof"
);
Ok(None)
} else {
Ok(Some(proof))
}
}

pub async fn fetch_checkpoint(
&self,
validators: &Vec<H256>,
threshold: usize,
message: &HyperlaneMessage,
) -> Result<Option<MultisigSignedCheckpoint>> {
const CTX: &str = "When fetching checkpoint signatures";
let highest_known_nonce = self.origin_prover_sync.read().await.count() - 1;
let checkpoint_syncer = self
.build_checkpoint_syncer(validators)
.await
.context(CTX)?;
checkpoint_syncer
.fetch_checkpoint_in_range(validators, threshold, message.nonce, highest_known_nonce)
.await
.context(CTX)
pub async fn highest_known_nonce(&self) -> u32 {
self.origin_prover_sync.read().await.count() - 1
}

pub async fn build_routing_ism(&self, address: H256) -> Result<Box<dyn RoutingIsm>> {
Expand Down
1 change: 0 additions & 1 deletion rust/agents/relayer/src/msg/metadata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ mod routing;

pub(crate) use base::BaseMetadataBuilder;
pub(crate) use base::MetadataBuilder;
use multisig::MultisigIsmMetadataBuilder;
use routing::RoutingIsmMetadataBuilder;
185 changes: 0 additions & 185 deletions rust/agents/relayer/src/msg/metadata/multisig.rs

This file was deleted.

Loading

0 comments on commit 50f04db

Please sign in to comment.