From a7c09a82a3d427bb6dbe226b07b979ead12e3e1e Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Sun, 27 Oct 2024 23:49:44 -0500 Subject: [PATCH 1/2] Start doc comment coverage for ethereum module --- src/ethereum/consensus/mod.rs | 18 ++++ src/ethereum/consensus/relayer.rs | 19 ++++ src/ethereum/mod.rs | 141 +++++++++++++++++++++++++++--- 3 files changed, 166 insertions(+), 12 deletions(-) diff --git a/src/ethereum/consensus/mod.rs b/src/ethereum/consensus/mod.rs index 29e9be25..f07fe7a2 100644 --- a/src/ethereum/consensus/mod.rs +++ b/src/ethereum/consensus/mod.rs @@ -35,6 +35,8 @@ use crate::error::Result; #[cfg(feature = "ethereum-full")] pub mod relayer; +/// Maintains the consensus state of an Ethereum chain, which can be updated via +/// consensus proofs based on the Altair light client protocol. #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct LightClient { lcs: LightClientStore, @@ -42,6 +44,8 @@ pub struct LightClient { } impl LightClient { + /// Create a new `LightClient` with the given bootstrap data and network + /// configuration. pub fn new(bootstrap: Bootstrap, network: Network) -> Result { let bootstrap = bootstrap.into(); @@ -57,6 +61,11 @@ impl LightClient { Ok(LightClient { lcs, network }) } + /// Verify and apply the update to the light client state. + /// + /// To minimize updates, this is designed to only allow updates that advance + /// to the next finalized slot, or to the next sync committee period. + /// There should be at most one update per epoch. pub fn update(&mut self, update: Update, now_seconds: u64) -> Result<()> { let expected_slot = (now_seconds - self.network.genesis_time) / 12; let genesis_root = (&self.network.genesis_vals_root.0).into(); @@ -79,10 +88,12 @@ impl LightClient { Ok(()) } + /// Get the most recently finalized slot. pub fn slot(&self) -> u64 { self.lcs.finalized_header.beacon.slot } + /// Get the most recently finalized block number. pub fn block_number(&self) -> u64 { *self .lcs @@ -93,6 +104,7 @@ impl LightClient { .block_number() } + /// Get the most recently finalized execution state root. pub fn state_root(&self) -> Bytes32 { self.lcs .finalized_header @@ -104,6 +116,7 @@ impl LightClient { .into() } + /// Get the underlying `LightClientStore`. pub fn light_client_store(&self) -> &LightClientStore { &self.lcs } @@ -238,6 +251,7 @@ impl Describe for LightClient { } } +/// The network parameters for an Ethereum chain. #[derive(Clone, Debug, Default, Encode, Decode, Serialize, Deserialize)] pub struct Network { pub genesis_vals_root: Bytes32, @@ -246,6 +260,7 @@ pub struct Network { } impl Network { + /// Network parameters for the Ethereum mainnet. pub fn ethereum_mainnet() -> Self { Network { genesis_vals_root: "0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95" @@ -256,6 +271,7 @@ impl Network { } } + /// Network parameters for the Ethereum Sepolia testnet. pub fn ethereum_sepolia() -> Self { Network { genesis_vals_root: "0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078" @@ -267,6 +283,8 @@ impl Network { } } +/// An update to the light client state, and all necessary proof and signature +/// data. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Update { pub attested_header: LightClientHeader, diff --git a/src/ethereum/consensus/relayer.rs b/src/ethereum/consensus/relayer.rs index 8b011818..7e8c7275 100644 --- a/src/ethereum/consensus/relayer.rs +++ b/src/ethereum/consensus/relayer.rs @@ -5,6 +5,16 @@ use serde::{Deserialize, Serialize}; use super::{Bootstrap, Bytes32, LightClient, Update}; use crate::error::Result; +/// Based on the current Nomic state machine state, get the updates needed to +/// bring the light client up to date with the Ethereum chain. +/// +/// This may include any number of updates that advance the light client to the +/// next light client period (256 epochs) and a finality update that advances +/// the light client to the most recently finalized slot within the current +/// period. +/// +/// If the light client is already up to date, this function will return an +/// empty vector. pub async fn get_updates>( app_client: &C, eth_client: &RpcClient, @@ -38,15 +48,19 @@ pub async fn get_updates>( Ok(updates) } +/// A client for the Ethereum Beacon API. pub struct RpcClient { rpc_addr: String, } impl RpcClient { + /// Create a new client to the Beacon API server with the given address. pub fn new(rpc_addr: String) -> Self { Self { rpc_addr } } + /// Get the updates, if any, to advance the light client from the given + /// start period to the current period, up to the given count. pub async fn get_updates( &self, start_period: u64, @@ -66,6 +80,7 @@ impl RpcClient { Ok(res) } + /// Get the most recent finality update. pub async fn get_finality_update(&self) -> Result> { let url = format!( "{}/eth/v1/beacon/light_client/finality_update", @@ -81,6 +96,7 @@ impl RpcClient { Ok(res) } + /// Get the block root for the given slot. pub async fn block_root(&self, slot: u64) -> Result> { let url = format!("{}/eth/v1/beacon/blocks/{}/root", self.rpc_addr, slot,); let response = get(&url) @@ -93,6 +109,7 @@ impl RpcClient { Ok(res) } + /// Get the bootstrap data for the given block root. pub async fn bootstrap(&self, block_root: Bytes32) -> Result> { let url = format!( "{}/eth/v1/beacon/light_client/bootstrap/{}", @@ -109,12 +126,14 @@ impl RpcClient { } } +/// A response from the Beacon API. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Response { pub version: Option, pub data: T, } +/// A response containing a block root. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Root { pub root: Bytes32, diff --git a/src/ethereum/mod.rs b/src/ethereum/mod.rs index 9f7391ad..02970046 100644 --- a/src/ethereum/mod.rs +++ b/src/ethereum/mod.rs @@ -1,3 +1,6 @@ +#![warn(missing_docs)] +#![warn(clippy::missing_docs_in_private_items)] + use alloy_core::{ primitives::keccak256, sol_types::{sol, SolValue}, @@ -71,19 +74,28 @@ pub mod relayer; #[cfg(feature = "ethereum-full")] pub mod signer; +/// How often to send messages updating to a new valset, in seconds. pub const VALSET_INTERVAL: u64 = 60 * 60 * 24; /// Gas price in microsats. pub const GAS_PRICE: u64 = 160_000; +/// Approximate gas cost for a transfer in wei, deducted from transfers from +/// Nomic to the destination chain. pub const APPROX_TRANSFER_GAS: u64 = 80_000; +/// Approximate gas cost for a contract call in wei, deducted from calls made on +/// the destination chain. pub const APPROX_CALL_GAS: u64 = 100_000; +/// The main state machine container for all Ethereum networks managed by Nomic. #[orga] pub struct Ethereum { + /// The Ethereum networks managed by Nomic, keyed by chain ID. pub networks: Map, } #[orga] impl Ethereum { + /// Advances the state of all networks managed by Nomic, should be called + /// once per Nomic block. pub fn step(&mut self, active_sigset: &SignatorySet) -> Result<()> { let ids: Vec<_> = self .networks @@ -98,6 +110,10 @@ impl Ethereum { Ok(()) } + /// Takes all pending transfers from all networks managed by Nomic, to be + /// moved into Nomic's Bitcoin state machine and eventually credited. These + /// transfers are either incoming from the remote EVM chain, or a bounceback + /// caused by a failed call or transfer. pub fn take_pending(&mut self) -> Result, Identity)>> { let ids: Vec<_> = self .networks @@ -112,6 +128,9 @@ impl Ethereum { Ok(pending) } + /// Verifies and applies a consensus light client update to the given + /// network. This should be called by relayers whenever there is a new + /// finality update from the remote chain's consensus process. #[call] pub fn relay_consensus_update( &mut self, @@ -129,6 +148,9 @@ impl Ethereum { net.light_client.update(update, now_seconds) } + /// Verifies a state proof and processes the incoming transfers returning + /// from the remote chain. This should be called by relayers whenever there + /// is a new state proof from the remote chain. #[call] pub fn relay_return( &mut self, @@ -153,6 +175,9 @@ impl Ethereum { conn.relay_return(network, state_root, state_proof) } + /// Verifies a signature from a valset signer for a given message within a + /// given connection then adds it to the state. This should be called by + /// signers whenever they have signed an outbox message. #[call] pub fn sign( &mut self, @@ -176,6 +201,8 @@ impl Ethereum { conn.sign(msg_index, pubkey, sig) } + /// Creates a new connection to the given bridge contract deployment on the + /// given remote chain and adds it to the state. pub fn create_connection( &mut self, chain_id: u32, @@ -203,6 +230,7 @@ impl Ethereum { Ok(()) } + /// Gets a list of all messages that need to be signed by the given signer. #[query] pub fn to_sign(&self, xpub: Xpub) -> Result { let mut to_sign = vec![]; @@ -240,6 +268,7 @@ impl Ethereum { Ok(to_sign) } + /// Gets a network by its chain ID. pub fn network(&self, network: u32) -> Result> { Ok(self .networks @@ -247,6 +276,7 @@ impl Ethereum { .ok_or_else(|| Error::App("Unknown network".to_string()))?) } + /// Gets a mutable reference to a network by its chain ID. pub fn network_mut(&mut self, network: u32) -> Result> { Ok(self .networks @@ -254,7 +284,17 @@ impl Ethereum { .ok_or_else(|| Error::App("Unknown network".to_string()))?) } - // TODO: we shouldn't need these: + /// Gets the current timestamp from the time context (e.g. from the + /// Tendermint block header). + fn now(&mut self) -> Result { + Ok(self + .context::