diff --git a/integration_tests/tests/bridge.rs b/integration_tests/tests/bridge.rs index 054da0a0..e7d52e83 100644 --- a/integration_tests/tests/bridge.rs +++ b/integration_tests/tests/bridge.rs @@ -4,12 +4,14 @@ reason = "We don't care about these in tests" )] -use std::time::Duration; +use std::{ops::Deref as _, time::Duration}; use anyhow::Context as _; use borsh::BorshSerialize; use common::transaction::LeeTransaction; -use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext}; +use integration_tests::{ + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, wait_for_indexer_to_catch_up, +}; use lee::{ AccountId, execute_and_prove, privacy_preserving_transaction, program::Program, public_transaction, @@ -449,5 +451,26 @@ async fn bedrock_deposit_mints_to_vault_then_claim_succeeds() -> anyhow::Result< "Recipient balance should increase by claimed amount" ); + // The indexer must replay the deposit and claim blocks and reach the same + // state as the sequencer — including the bridge system account the deposit + // modifies, which is the case the hot fix unblocks. + wait_for_indexer_to_catch_up(&ctx).await?; + let bridge_account_id = lee::system_bridge_account_id(); + for account_id in [recipient_id, recipient_vault_id, bridge_account_id] { + let indexer_account = indexer_service_rpc::RpcClient::get_account( + // `deref` is needed for correct trait resolution + // of the async `get_account` method on `RpcClient` + ctx.indexer_client().deref(), + account_id.into(), + ) + .await?; + let sequencer_account = ctx.sequencer_client().get_account(account_id).await?; + assert_eq!( + indexer_account, + sequencer_account.into(), + "Indexer and sequencer diverged for account {account_id} after deposit" + ); + } + Ok(()) } diff --git a/lez/common/src/transaction.rs b/lez/common/src/transaction.rs index 6f6a4425..a74474a9 100644 --- a/lez/common/src/transaction.rs +++ b/lez/common/src/transaction.rs @@ -78,18 +78,9 @@ impl LeeTransaction { block_id: BlockId, timestamp: Timestamp, ) -> Result { - let diff = match self { - Self::Public(tx) => { - ValidatedStateDiff::from_public_transaction(tx, state, block_id, timestamp) - } - Self::PrivacyPreserving(tx) => ValidatedStateDiff::from_privacy_preserving_transaction( - tx, state, block_id, timestamp, - ), - Self::ProgramDeployment(tx) => { - ValidatedStateDiff::from_program_deployment_transaction(tx, state) - } - }?; + let diff = self.compute_state_diff(state, block_id, timestamp)?; + // system accounts guard let system_accounts = lee::CLOCK_PROGRAM_ACCOUNT_IDS.iter().copied().chain([ lee::system_faucet_account_id(), lee::system_bridge_account_id(), @@ -101,6 +92,28 @@ impl LeeTransaction { Ok(diff) } + /// Computes the validated state diff without enforcing the system-account + /// restriction. Shared by [`Self::validate_on_state`] and + /// [`Self::execute_without_system_accounts_check_on_state`]. + fn compute_state_diff( + &self, + state: &V03State, + block_id: BlockId, + timestamp: Timestamp, + ) -> Result { + match self { + Self::Public(tx) => { + ValidatedStateDiff::from_public_transaction(tx, state, block_id, timestamp) + } + Self::PrivacyPreserving(tx) => ValidatedStateDiff::from_privacy_preserving_transaction( + tx, state, block_id, timestamp, + ), + Self::ProgramDeployment(tx) => { + ValidatedStateDiff::from_program_deployment_transaction(tx, state) + } + } + } + /// Validates the transaction against the current state, rejects modifications to clock /// system accounts, and applies the resulting diff to the state. pub fn execute_check_on_state( @@ -115,6 +128,28 @@ impl LeeTransaction { state.apply_state_diff(diff); Ok(self) } + + /// Similar to [`Self::execute_check_on_state`], but skips the system-account guard. + /// + /// FIXME: HOT FIX (testnet v0.2): the indexer replays blocks the sequencer already + /// accepted, including sequencer-generated deposit transactions that + /// legitimately modify the bridge account. The `TransactionOrigin::Sequencer` + /// tag that lets the sequencer bypass the guard is not carried in the block, + /// so the indexer cannot yet distinguish deposit txs from user txs. + /// + /// REMOVE ME when the indexer can authenticate deposit transactions. + pub fn execute_without_system_accounts_check_on_state( + self, + state: &mut V03State, + block_id: BlockId, + timestamp: Timestamp, + ) -> Result { + let diff = self + .compute_state_diff(state, block_id, timestamp) + .inspect_err(|err| warn!("Error at transition {err:#?}"))?; + state.apply_state_diff(diff); + Ok(self) + } } impl From for LeeTransaction { diff --git a/lez/indexer/core/src/block_store.rs b/lez/indexer/core/src/block_store.rs index 878afbe4..f00c94c5 100644 --- a/lez/indexer/core/src/block_store.rs +++ b/lez/indexer/core/src/block_store.rs @@ -171,7 +171,10 @@ impl IndexerStore { transaction .clone() .transaction_stateless_check()? - .execute_check_on_state( + // FIXME: HOT FIX (testnet v0.2): does not check for system account updates due to + // sequencer-generated deposit tx'es; + // CHANGE ME back to `execute_check_on_state` when the indexer can authenticate deposit transactions + .execute_without_system_accounts_check_on_state( &mut state_guard, block.header.block_id, block.header.timestamp, diff --git a/lez/storage/src/indexer/mod.rs b/lez/storage/src/indexer/mod.rs index 51df8042..b682a4d7 100644 --- a/lez/storage/src/indexer/mod.rs +++ b/lez/storage/src/indexer/mod.rs @@ -208,7 +208,10 @@ impl RocksDBIO { "transaction pre check failed with err {err:?}" )) })? - .execute_check_on_state( + // FIXME: HOT FIX (testnet v0.2): does not check for system account updates due to + // sequencer-generated deposit tx'es; + // CHANGE ME back to `execute_check_on_state` when the indexer can authenticate deposit transactions + .execute_without_system_accounts_check_on_state( &mut breakpoint, block.header.block_id, block.header.timestamp,