diff --git a/.changelog/unreleased/bug-fixes/1856-eth-contract-nonce-fixes.md b/.changelog/unreleased/bug-fixes/1856-eth-contract-nonce-fixes.md new file mode 100644 index 0000000000..0e11191cd3 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1856-eth-contract-nonce-fixes.md @@ -0,0 +1,2 @@ +- Miscellaneous Ethereum smart contract nonce fixes + ([\#1856](https://github.com/anoma/namada/pull/1856)) \ No newline at end of file diff --git a/core/src/types/ethereum_events.rs b/core/src/types/ethereum_events.rs index f7097e9749..57c8047fdd 100644 --- a/core/src/types/ethereum_events.rs +++ b/core/src/types/ethereum_events.rs @@ -51,11 +51,14 @@ impl Ord for Uint { } impl Uint { - /// Convert to a little endian byte representation of - /// a uint256. + /// Convert to an Ethereum-compatible byte representation. + /// + /// The Ethereum virtual machine employs big-endian integers + /// (Wood, 2014), therefore the returned byte array has the + /// same endianness. pub fn to_bytes(self) -> [u8; 32] { let mut bytes = [0; 32]; - ethUint(self.0).to_little_endian(&mut bytes); + ethUint(self.0).to_big_endian(&mut bytes); bytes } diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 6814fae8ec..7ff30ef07c 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -165,6 +165,9 @@ trait ShouldRelay { where E: Middleware, E::Error: std::fmt::Display; + + /// Try to recover from an error that has happened. + fn try_recover(err: String) -> Error; } impl ShouldRelay for DoNotCheckNonce { @@ -179,6 +182,11 @@ impl ShouldRelay for DoNotCheckNonce { { std::future::ready(Ok(())) } + + #[inline] + fn try_recover(err: String) -> Error { + Error::recoverable(err) + } } impl ShouldRelay for CheckNonce { @@ -216,6 +224,11 @@ impl ShouldRelay for CheckNonce { } }) } + + #[inline] + fn try_recover(err: String) -> Error { + Error::critical(err) + } } /// Relay result for [`CheckNonce`]. @@ -360,7 +373,7 @@ where } RelayResult::Receipt { receipt } => { if receipt.is_successful() { - tracing::info!(?receipt, "Ethereum transfer succeded"); + tracing::info!(?receipt, "Ethereum transfer succeeded"); } else { tracing::error!(?receipt, "Ethereum transfer failed"); } @@ -447,16 +460,18 @@ where "Failed to fetch latest validator set nonce: {err}" ); }) - .map(|e| Epoch(e.as_u64())) + .map(|e| e.as_u64() as i128) }); let shell = RPC.shell(); let nam_current_epoch_fut = shell.epoch(nam_client).map(|result| { - result.map_err(|err| { - tracing::error!( - "Failed to fetch the latest epoch in Namada: {err}" - ); - }) + result + .map_err(|err| { + tracing::error!( + "Failed to fetch the latest epoch in Namada: {err}" + ); + }) + .map(|Epoch(e)| e as i128) }); let (nam_current_epoch, gov_current_epoch) = @@ -469,8 +484,11 @@ where "Fetched the latest epochs" ); - match nam_current_epoch.cmp(&gov_current_epoch) { - Ordering::Equal => { + let new_epoch = match nam_current_epoch - gov_current_epoch { + // NB: a namada epoch should always be one behind the nonce + // in the governance contract, for the latter to be considered + // up to date + -1 => { tracing::debug!( "Nothing to do, since the validator set in the Governance \ contract is up to date", @@ -478,16 +496,21 @@ where last_call_succeeded = false; continue; } - Ordering::Less => { + 0.. => { + let e = gov_current_epoch + 1; + // consider only the lower 64-bits + Epoch((e & (u64::MAX as i128)) as u64) + } + // NB: if the nonce difference is lower than 0, somehow the state + // of namada managed to fall behind the state of the smart contract + _ => { tracing::error!("The Governance contract is ahead of Namada!"); last_call_succeeded = false; continue; } - Ordering::Greater => {} - } + }; // update epoch in the contract - let new_epoch = gov_current_epoch + 1u64; args.epoch = Some(new_epoch); let result = relay_validator_set_update_once::( @@ -502,7 +525,7 @@ where }; last_call_succeeded = receipt.is_successful(); if last_call_succeeded { - tracing::info!(?receipt, "Ethereum transfer succeded"); + tracing::info!(?receipt, "Ethereum transfer succeeded"); tracing::info!(?new_epoch, "Updated the validator set"); } else { tracing::error!(?receipt, "Ethereum transfer failed"); @@ -557,11 +580,18 @@ where .map_err(|e| Error::critical(e.to_string()))? .next() }; + + if hints::unlikely(epoch_to_relay == Epoch(0)) { + return Err(Error::critical( + "There is no validator set update proof for epoch 0", + )); + } + let shell = RPC.shell().eth_bridge(); let encoded_proof_fut = shell.read_valset_upd_proof(nam_client, &epoch_to_relay); - let bridge_current_epoch = Epoch(epoch_to_relay.0.saturating_sub(2)); + let bridge_current_epoch = epoch_to_relay - 1; let shell = RPC.shell().eth_bridge(); let encoded_validator_set_args_fut = shell.read_consensus_valset(nam_client, &bridge_current_epoch); @@ -575,7 +605,7 @@ where encoded_validator_set_args_fut, governance_address_fut ) - .map_err(|err| Error::recoverable(err.to_string()))?; + .map_err(|err| R::try_recover(err.to_string()))?; let (bridge_hash, gov_hash, signatures): ( [u8; 32],