diff --git a/engine-standalone-storage/src/relayer_db/mod.rs b/engine-standalone-storage/src/relayer_db/mod.rs index d36355438..b19434c5b 100644 --- a/engine-standalone-storage/src/relayer_db/mod.rs +++ b/engine-standalone-storage/src/relayer_db/mod.rs @@ -155,6 +155,7 @@ where transaction: crate::sync::types::TransactionKind::Submit(tx), promise_data: Vec::new(), raw_input: transaction_bytes, + action_hash: H256::default(), }; storage.set_transaction_included(tx_hash, &tx_msg, &diff)?; } @@ -268,6 +269,7 @@ mod test { transaction: TransactionKind::Unknown, promise_data: Vec::new(), raw_input: Vec::new(), + action_hash: H256::default(), }, &diff, ) diff --git a/engine-standalone-storage/src/sync/mod.rs b/engine-standalone-storage/src/sync/mod.rs index 803c75fe0..a16310537 100644 --- a/engine-standalone-storage/src/sync/mod.rs +++ b/engine-standalone-storage/src/sync/mod.rs @@ -384,6 +384,10 @@ where let predecessor_account_id = transaction_message.caller.clone(); let near_receipt_id = transaction_message.near_receipt_id; let current_account_id = engine_account_id; + let random_seed = compute_random_seed( + &transaction_message.action_hash, + &block_metadata.random_seed, + ); let env = env::Fixed { signer_account_id, current_account_id, @@ -391,7 +395,7 @@ where block_height, block_timestamp: block_metadata.timestamp, attached_deposit: transaction_message.attached_near, - random_seed: block_metadata.random_seed, + random_seed, prepaid_gas: DEFAULT_PREPAID_GAS, }; @@ -435,6 +439,16 @@ where (tx_hash, diff, result) } +/// Based on nearcore implementation: +/// +fn compute_random_seed(action_hash: &H256, block_random_value: &H256) -> H256 { + const BYTES_LEN: usize = 32 + 32; + let mut bytes: Vec = Vec::with_capacity(BYTES_LEN); + bytes.extend_from_slice(action_hash.as_bytes()); + bytes.extend_from_slice(block_random_value.as_bytes()); + aurora_engine_sdk::sha256(&bytes) +} + /// Handles all transaction kinds other than `submit`. /// The `submit` transaction kind is special because it is the only one where the transaction hash /// differs from the NEAR receipt hash. diff --git a/engine-standalone-storage/src/sync/types.rs b/engine-standalone-storage/src/sync/types.rs index 06ae54ade..4d859b317 100644 --- a/engine-standalone-storage/src/sync/types.rs +++ b/engine-standalone-storage/src/sync/types.rs @@ -53,6 +53,14 @@ pub struct TransactionMessage { pub promise_data: Vec>>, /// Raw bytes passed as input when executed in the Near Runtime. pub raw_input: Vec, + /// A Near protocol quantity equal to + /// `sha256(receipt_id || block_hash || le_bytes(u64 - action_index))`. + /// This quantity is used together with the block random seed + /// to generate the random value available to the transaction. + /// nearcore references: + /// - https://github.com/near/nearcore/blob/00ca2f3f73e2a547ba881f76ecc59450dbbef6e2/core/primitives/src/utils.rs#L261 + /// - https://github.com/near/nearcore/blob/00ca2f3f73e2a547ba881f76ecc59450dbbef6e2/core/primitives/src/utils.rs#L295 + pub action_hash: H256, } impl TransactionMessage { @@ -679,6 +687,7 @@ enum BorshableTransactionMessage<'a> { V1(BorshableTransactionMessageV1<'a>), V2(BorshableTransactionMessageV2<'a>), V3(BorshableTransactionMessageV3<'a>), + V4(BorshableTransactionMessageV4<'a>), } #[derive(BorshDeserialize, BorshSerialize)] @@ -720,6 +729,21 @@ struct BorshableTransactionMessageV3<'a> { pub raw_input: Cow<'a, Vec>, } +#[derive(BorshDeserialize, BorshSerialize)] +struct BorshableTransactionMessageV4<'a> { + pub block_hash: [u8; 32], + pub near_receipt_id: [u8; 32], + pub position: u16, + pub succeeded: bool, + pub signer: Cow<'a, AccountId>, + pub caller: Cow<'a, AccountId>, + pub attached_near: u128, + pub transaction: BorshableTransactionKind<'a>, + pub promise_data: Cow<'a, Vec>>>, + pub raw_input: Cow<'a, Vec>, + pub action_hash: [u8; 32], +} + impl<'a> From<&'a TransactionMessage> for BorshableTransactionMessage<'a> { fn from(t: &'a TransactionMessage) -> Self { Self::V3(BorshableTransactionMessageV3 { @@ -756,6 +780,7 @@ impl<'a> TryFrom> for TransactionMessage { transaction, promise_data: Vec::new(), raw_input, + action_hash: H256::default(), }) } BorshableTransactionMessage::V2(t) => { @@ -772,6 +797,7 @@ impl<'a> TryFrom> for TransactionMessage { transaction, promise_data: t.promise_data.into_owned(), raw_input, + action_hash: H256::default(), }) } BorshableTransactionMessage::V3(t) => Ok(Self { @@ -785,6 +811,20 @@ impl<'a> TryFrom> for TransactionMessage { transaction: t.transaction.try_into()?, promise_data: t.promise_data.into_owned(), raw_input: t.raw_input.into_owned(), + action_hash: H256::default(), + }), + BorshableTransactionMessage::V4(t) => Ok(Self { + block_hash: H256(t.block_hash), + near_receipt_id: H256(t.near_receipt_id), + position: t.position, + succeeded: t.succeeded, + signer: t.signer.into_owned(), + caller: t.caller.into_owned(), + attached_near: t.attached_near, + transaction: t.transaction.try_into()?, + promise_data: t.promise_data.into_owned(), + raw_input: t.raw_input.into_owned(), + action_hash: H256(t.action_hash), }), } } diff --git a/engine-tests/src/tests/random.rs b/engine-tests/src/tests/random.rs index 473887ab1..35b4f9358 100644 --- a/engine-tests/src/tests/random.rs +++ b/engine-tests/src/tests/random.rs @@ -1,12 +1,17 @@ use crate::utils; use crate::utils::solidity::random::{Random, RandomConstructor}; use aurora_engine_types::H256; +use rand::SeedableRng; #[test] fn test_random_number_precompile() { let random_seed = H256::from_slice(vec![7; 32].as_slice()); - let mut signer = utils::Signer::random(); - let mut runner = utils::deploy_runner().with_random_seed(random_seed); + let secret_key = { + let mut rng = rand::rngs::StdRng::from_seed(random_seed.0); + libsecp256k1::SecretKey::random(&mut rng) + }; + let mut signer = utils::Signer::new(secret_key); + let mut runner = utils::deploy_runner().with_block_random_value(random_seed); let random_ctr = RandomConstructor::load(); let nonce = signer.use_nonce(); @@ -14,6 +19,13 @@ fn test_random_number_precompile() { .deploy_contract(&signer.secret_key, |ctr| ctr.deploy(nonce), random_ctr) .into(); + // Value derived from `random_seed` above together with the `action_hash` + // of the following transaction. + let expected_value = H256::from_slice( + &hex::decode("1a71249ace8312de8ed3640c852d5d542b04b2caec668325f6e18811244e7f5c").unwrap(), + ); + runner.context.random_seed = expected_value.0.to_vec(); + let counter_value = random.random_seed(&mut runner, &mut signer); - assert_eq!(counter_value, random_seed); + assert_eq!(counter_value, expected_value); } diff --git a/engine-tests/src/tests/repro.rs b/engine-tests/src/tests/repro.rs index 35b7eede4..2615e8c44 100644 --- a/engine-tests/src/tests/repro.rs +++ b/engine-tests/src/tests/repro.rs @@ -171,7 +171,7 @@ fn repro_common(context: &ReproContext) { let mut standalone = standalone::StandaloneRunner::default(); json_snapshot::initialize_engine_state(&standalone.storage, snapshot).unwrap(); let standalone_result = standalone - .submit_raw("submit", &runner.context, &[]) + .submit_raw("submit", &runner.context, &[], None) .unwrap(); assert_eq!( submit_result.try_to_vec().unwrap(), diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs index f490c2a2f..ed658418c 100644 --- a/engine-tests/src/tests/sanity.rs +++ b/engine-tests/src/tests/sanity.rs @@ -212,14 +212,18 @@ fn test_transaction_to_zero_address() { // Prior to the fix the zero address is interpreted as None, causing a contract deployment. // It also incorrectly derives the sender address, so does not increment the right nonce. context.block_height = ZERO_ADDRESS_FIX_HEIGHT - 1; - let result = runner.submit_raw(utils::SUBMIT, &context, &[]).unwrap(); + let result = runner + .submit_raw(utils::SUBMIT, &context, &[], None) + .unwrap(); assert_eq!(result.gas_used, 53_000); runner.env.block_height = ZERO_ADDRESS_FIX_HEIGHT; assert_eq!(runner.get_nonce(&address), U256::zero()); // After the fix this transaction is simply a transfer of 0 ETH to the zero address context.block_height = ZERO_ADDRESS_FIX_HEIGHT; - let result = runner.submit_raw(utils::SUBMIT, &context, &[]).unwrap(); + let result = runner + .submit_raw(utils::SUBMIT, &context, &[], None) + .unwrap(); assert_eq!(result.gas_used, 21_000); runner.env.block_height = ZERO_ADDRESS_FIX_HEIGHT + 1; assert_eq!(runner.get_nonce(&address), U256::one()); diff --git a/engine-tests/src/tests/standalone/call_tracer.rs b/engine-tests/src/tests/standalone/call_tracer.rs index 78d973d22..4e437efa4 100644 --- a/engine-tests/src/tests/standalone/call_tracer.rs +++ b/engine-tests/src/tests/standalone/call_tracer.rs @@ -1,4 +1,4 @@ -use crate::prelude::H256; +use crate::prelude::{H160, H256}; use crate::utils::solidity::erc20::{ERC20Constructor, ERC20}; use crate::utils::{self, standalone, Signer}; use aurora_engine_modexp::AuroraModExp; @@ -50,13 +50,32 @@ fn test_trace_precompile_direct_call() { runner.init_evm(); + let input = hex::decode("0000ca110000").unwrap(); + let precompile_cost = { + use aurora_engine_precompiles::Precompile; + let context = evm::Context { + address: H160::default(), + caller: H160::default(), + apparent_value: U256::zero(), + }; + let result = + aurora_engine_precompiles::identity::Identity.run(&input, None, &context, false); + result.unwrap().cost.as_u64() + }; let tx = aurora_engine_transactions::legacy::TransactionLegacy { nonce: signer.use_nonce().into(), gas_price: U256::zero(), gas_limit: u64::MAX.into(), - to: Some(aurora_engine_precompiles::random::RandomSeed::ADDRESS), + to: Some(aurora_engine_precompiles::identity::Identity::ADDRESS), value: Wei::zero(), - data: Vec::new(), + data: input.clone(), + }; + let intrinsic_cost = { + let signed_tx = + utils::sign_transaction(tx.clone(), Some(runner.chain_id), &signer.secret_key); + let kind = aurora_engine_transactions::EthTransactionKind::Legacy(signed_tx); + let norm_tx = aurora_engine_transactions::NormalizedEthTransaction::try_from(kind).unwrap(); + norm_tx.intrinsic_gas(&evm::Config::shanghai()).unwrap() }; let mut listener = CallTracer::default(); @@ -71,12 +90,12 @@ fn test_trace_precompile_direct_call() { let expected_trace = call_tracer::CallFrame { call_type: call_tracer::CallType::Call, from: utils::address_from_secret_key(&signer.secret_key), - to: Some(aurora_engine_precompiles::random::RandomSeed::ADDRESS), + to: Some(aurora_engine_precompiles::identity::Identity::ADDRESS), value: U256::zero(), gas: u64::MAX, - gas_used: 21000_u64, - input: Vec::new(), - output: [0u8; 32].to_vec(), + gas_used: intrinsic_cost + precompile_cost, + input: input.clone(), + output: input, error: None, calls: Vec::new(), }; diff --git a/engine-tests/src/tests/standalone/storage.rs b/engine-tests/src/tests/standalone/storage.rs index e72c79500..1e987c06f 100644 --- a/engine-tests/src/tests/standalone/storage.rs +++ b/engine-tests/src/tests/standalone/storage.rs @@ -273,6 +273,7 @@ fn test_transaction_index() { transaction: TransactionKind::Unknown, promise_data: Vec::new(), raw_input: Vec::new(), + action_hash: H256::default(), }; let tx_included = engine_standalone_storage::TransactionIncluded { block_hash, diff --git a/engine-tests/src/tests/standalone/sync.rs b/engine-tests/src/tests/standalone/sync.rs index 43f8a7476..1a14a1633 100644 --- a/engine-tests/src/tests/standalone/sync.rs +++ b/engine-tests/src/tests/standalone/sync.rs @@ -64,6 +64,7 @@ fn test_consume_deposit_message() { transaction: tx_kind, promise_data: Vec::new(), raw_input, + action_hash: H256::default(), }; let outcome = sync::consume_message::( @@ -102,6 +103,7 @@ fn test_consume_deposit_message() { // (which is `true` because the proof is valid in this case). promise_data: vec![Some(true.try_to_vec().unwrap())], raw_input, + action_hash: H256::default(), }; let outcome = sync::consume_message::( @@ -136,6 +138,7 @@ fn test_consume_deposit_message() { transaction: tx_kind, promise_data: Vec::new(), raw_input, + action_hash: H256::default(), }; let outcome = sync::consume_message::( @@ -170,6 +173,7 @@ fn test_consume_deploy_message() { transaction: tx_kind, promise_data: Vec::new(), raw_input, + action_hash: H256::default(), }; let outcome = sync::consume_message::( @@ -226,6 +230,7 @@ fn test_consume_deploy_erc20_message() { transaction: tx_kind, promise_data: Vec::new(), raw_input, + action_hash: H256::default(), }; // Deploy ERC-20 (this would be the flow for bridging a new NEP-141 to Aurora) @@ -268,6 +273,7 @@ fn test_consume_deploy_erc20_message() { transaction: tx_kind, promise_data: Vec::new(), raw_input, + action_hash: H256::default(), }; // Mint new tokens (via ft_on_transfer flow, same as the bridge) @@ -334,6 +340,7 @@ fn test_consume_ft_on_transfer_message() { transaction: tx_kind, promise_data: Vec::new(), raw_input, + action_hash: H256::default(), }; let outcome = sync::consume_message::( @@ -382,6 +389,7 @@ fn test_consume_call_message() { transaction: tx_kind, promise_data: Vec::new(), raw_input, + action_hash: H256::default(), }; let outcome = sync::consume_message::( @@ -436,6 +444,7 @@ fn test_consume_submit_message() { transaction: tx_kind, promise_data: Vec::new(), raw_input, + action_hash: H256::default(), }; let outcome = sync::consume_message::( diff --git a/engine-tests/src/tests/standalone/tracing.rs b/engine-tests/src/tests/standalone/tracing.rs index 616a574bf..bdcbcf72e 100644 --- a/engine-tests/src/tests/standalone/tracing.rs +++ b/engine-tests/src/tests/standalone/tracing.rs @@ -73,6 +73,7 @@ fn test_evm_tracing_with_storage() { transaction: engine_standalone_storage::sync::types::TransactionKind::Unknown, promise_data: Vec::new(), raw_input: Vec::new(), + action_hash: H256::default(), }, diff, maybe_result: Ok(None), diff --git a/engine-tests/src/utils/mod.rs b/engine-tests/src/utils/mod.rs index 2018c467a..d031d305c 100644 --- a/engine-tests/src/utils/mod.rs +++ b/engine-tests/src/utils/mod.rs @@ -98,6 +98,12 @@ pub struct AuroraRunner { // Empty by default. Can be set in tests if the transaction should be // executed as if it was a callback. pub promise_results: Vec, + // None by default. Can be set if the transaction requires randomness + // from the Near runtime. + // Note: this only sets the random value for the block, the random + // value available in the runtime is derived from this value and + // another hash that depends on the transaction itself. + pub block_random_value: Option, } /// Same as `AuroraRunner`, but consumes `self` on execution (thus preventing building on @@ -234,7 +240,12 @@ impl AuroraRunner { self.previous_logs = outcome.logs.clone(); if let Some(standalone_runner) = &mut self.standalone_runner { - standalone_runner.submit_raw(method_name, &self.context, &self.promise_results)?; + standalone_runner.submit_raw( + method_name, + &self.context, + &self.promise_results, + self.block_random_value, + )?; self.validate_standalone(); } @@ -539,8 +550,8 @@ impl AuroraRunner { outcome.return_data.as_value().unwrap() } - pub fn with_random_seed(mut self, random_seed: H256) -> Self { - self.context.random_seed = random_seed.as_bytes().to_vec(); + pub const fn with_block_random_value(mut self, random_seed: H256) -> Self { + self.block_random_value = Some(random_seed); self } @@ -645,6 +656,7 @@ impl Default for AuroraRunner { previous_logs: Vec::new(), standalone_runner: Some(standalone::StandaloneRunner::default()), promise_results: Vec::new(), + block_random_value: None, } } } diff --git a/engine-tests/src/utils/standalone/mod.rs b/engine-tests/src/utils/standalone/mod.rs index b437d9661..d22592836 100644 --- a/engine-tests/src/utils/standalone/mod.rs +++ b/engine-tests/src/utils/standalone/mod.rs @@ -198,6 +198,7 @@ impl StandaloneRunner { method_name: &str, ctx: &near_vm_logic::VMContext, promise_results: &[PromiseResult], + block_random_value: Option, ) -> Result { let mut env = self.env.clone(); env.block_height = ctx.block_height; @@ -207,8 +208,8 @@ impl StandaloneRunner { env.current_account_id = ctx.current_account_id.as_ref().parse().unwrap(); env.signer_account_id = ctx.signer_account_id.as_ref().parse().unwrap(); env.prepaid_gas = NearGas::new(ctx.prepaid_gas); - if ctx.random_seed.len() == 32 { - env.random_seed = H256::from_slice(&ctx.random_seed); + if let Some(value) = block_random_value { + env.random_seed = value; } let promise_data: Vec<_> = promise_results @@ -239,6 +240,19 @@ impl StandaloneRunner { ); tx_msg.transaction = transaction_kind; + if ctx.random_seed.len() == 32 { + let runtime_random_value = { + use near_primitives_core::hash::CryptoHash; + let action_hash = CryptoHash(tx_msg.action_hash.0); + let random_seed = CryptoHash(env.random_seed.0); + near_primitives::utils::create_random_seed(u32::MAX, action_hash, random_seed) + }; + assert_eq!( + ctx.random_seed, runtime_random_value, + "Runtime random value should match computed value when it is specified" + ); + } + let outcome = sync::execute_transaction_message::(storage, tx_msg).unwrap(); self.cumulative_diff.append(outcome.diff.clone()); storage::commit(storage, &outcome); @@ -314,6 +328,13 @@ impl StandaloneRunner { PromiseResult::Successful(bytes) => Some(bytes.clone()), }) .collect(); + let action_hash = { + let mut bytes = Vec::with_capacity(32 + 32 + 8); + bytes.extend_from_slice(transaction_hash.as_bytes()); + bytes.extend_from_slice(block_hash.as_bytes()); + bytes.extend_from_slice(&(u64::MAX - u64::from(transaction_position)).to_le_bytes()); + aurora_engine_sdk::sha256(&bytes) + }; TransactionMessage { block_hash, near_receipt_id: transaction_hash, @@ -325,6 +346,7 @@ impl StandaloneRunner { transaction: TransactionKind::Unknown, promise_data, raw_input, + action_hash, } }