From 7e4c59a0a381fda5926cbd0a554c26330ba7de11 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 20 Sep 2024 01:24:28 -0400 Subject: [PATCH] Have the Router track its deployment block Prevents a consensus split where some nodes would drop transfers if their node didn't think the Router was deployed, and some would handle them. --- .../ethereum/router/contracts/Router.sol | 9 ++++++++ processor/ethereum/router/src/lib.rs | 21 +++++++++++++++++-- processor/ethereum/src/rpc.rs | 12 +++++++++-- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/processor/ethereum/router/contracts/Router.sol b/processor/ethereum/router/contracts/Router.sol index 9100f59e..d82c0d90 100644 --- a/processor/ethereum/router/contracts/Router.sol +++ b/processor/ethereum/router/contracts/Router.sol @@ -7,6 +7,9 @@ import "Schnorr.sol"; // _ is used as a prefix for internal functions and smart-contract-scoped variables contract Router { + // The block at which this contract was deployed. + uint256 private _deploymentBlock; + // Nonce is incremented for each command executed, preventing replays uint256 private _nonce; @@ -63,6 +66,8 @@ contract Router { } constructor(bytes32 initialSeraiKey) _updateSeraiKeyAtEndOfFn(0, initialSeraiKey) { + _deploymentBlock = block.number; + // We consumed nonce 0 when setting the initial Serai key _nonce = 1; // Nonces are incremented by 1 upon account creation, prior to any code execution, per EIP-161 @@ -230,6 +235,10 @@ contract Router { return _nonce; } + function deploymentBlock() external view returns (uint256) { + return _deploymentBlock; + } + function smartContractNonce() external view returns (uint256) { return _smartContractNonce; } diff --git a/processor/ethereum/router/src/lib.rs b/processor/ethereum/router/src/lib.rs index 248523b8..d78b3218 100644 --- a/processor/ethereum/router/src/lib.rs +++ b/processor/ethereum/router/src/lib.rs @@ -11,7 +11,7 @@ use alloy_consensus::TxLegacy; use alloy_sol_types::{SolValue, SolConstructor, SolCall, SolEvent}; -use alloy_rpc_types_eth::Filter; +use alloy_rpc_types_eth::{TransactionInput, TransactionRequest, Filter}; use alloy_transport::{TransportErrorKind, RpcError}; use alloy_simple_request_transport::SimpleRequest; use alloy_provider::{Provider, RootProvider}; @@ -296,6 +296,23 @@ impl Router { self.1 } + /// Fetch the block this contract was deployed at. + pub async fn deployment_block(&self) -> Result> { + let call = TransactionRequest::default() + .to(self.address()) + .input(TransactionInput::new(abi::deploymentBlockCall::new(()).abi_encode().into())); + let bytes = self.0.call(&call).await?; + let deployment_block = abi::deploymentBlockCall::abi_decode_returns(&bytes, true) + .map_err(|e| { + TransportErrorKind::Custom( + format!("node returned a non-u256 for function returning u256: {e:?}").into(), + ) + })? + ._0; + + Ok(deployment_block.try_into().unwrap()) + } + /// Get the message to be signed in order to update the key for Serai. pub fn update_serai_key_message(chain_id: U256, nonce: u64, key: &PublicKey) -> Vec { ( @@ -420,7 +437,7 @@ impl Router { */ if let Some(matched) = Erc20::match_top_level_transfer(&self.0, tx_hash, self.1).await? { // Mark this log index as used so it isn't used again - transfer_check.insert(matched.log_index); + transfer_check.insert(matched.id.1); } // Find a matching transfer log diff --git a/processor/ethereum/src/rpc.rs b/processor/ethereum/src/rpc.rs index 1eaa4988..a5533484 100644 --- a/processor/ethereum/src/rpc.rs +++ b/processor/ethereum/src/rpc.rs @@ -156,10 +156,12 @@ impl ScannerFeed for Rpc { // The Router wasn't deployed yet so we cannot have any on-chain interactions // If the Router has been deployed by the block we've synced to, it won't have any events // for these blocks anways, so this doesn't risk a consensus split - // TODO: This does as we can have top-level transfers to the router before it's deployed return Ok(FullEpoch { epoch, instructions, executed }); }; + let router_deployment_block = router.deployment_block().await?; + + // TODO: Use a LocalSet and handle all these in parallel let mut to_check = epoch.end_hash; while to_check != epoch.prior_end_hash { let to_check_block = self @@ -177,6 +179,12 @@ impl ScannerFeed for Rpc { })? .header; + // If this is before the Router was deployed, move on + if to_check_block.number < router_deployment_block { + // This is sa + break; + } + instructions.append( &mut router.in_instructions(to_check_block.number, &HashSet::from(TOKENS)).await?, ); @@ -187,7 +195,7 @@ impl ScannerFeed for Rpc { .await? { instructions.push(EthereumInInstruction { - id: (id, u64::MAX), + id, from, coin: EthereumCoin::Erc20(token), amount,