Skip to content

Commit

Permalink
Fix(standalone): properly compute runtime random value
Browse files Browse the repository at this point in the history
  • Loading branch information
birchmd committed Nov 7, 2023
1 parent 18778f2 commit cc9c800
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 19 deletions.
2 changes: 2 additions & 0 deletions engine-standalone-storage/src/relayer_db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
}
Expand Down Expand Up @@ -268,6 +269,7 @@ mod test {
transaction: TransactionKind::Unknown,
promise_data: Vec::new(),
raw_input: Vec::new(),
action_hash: H256::default(),
},
&diff,
)
Expand Down
16 changes: 15 additions & 1 deletion engine-standalone-storage/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,14 +384,18 @@ 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,
predecessor_account_id,
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,
};

Expand Down Expand Up @@ -435,6 +439,16 @@ where
(tx_hash, diff, result)
}

/// Based on nearcore implementation:
/// <https://github.com/near/nearcore/blob/00ca2f3f73e2a547ba881f76ecc59450dbbef6e2/core/primitives/src/utils.rs#L295>
fn compute_random_seed(action_hash: &H256, block_random_value: &H256) -> H256 {
const BYTES_LEN: usize = 32 + 32;
let mut bytes: Vec<u8> = 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.
Expand Down
40 changes: 40 additions & 0 deletions engine-standalone-storage/src/sync/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ pub struct TransactionMessage {
pub promise_data: Vec<Option<Vec<u8>>>,
/// Raw bytes passed as input when executed in the Near Runtime.
pub raw_input: Vec<u8>,
/// 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 {
Expand Down Expand Up @@ -679,6 +687,7 @@ enum BorshableTransactionMessage<'a> {
V1(BorshableTransactionMessageV1<'a>),
V2(BorshableTransactionMessageV2<'a>),
V3(BorshableTransactionMessageV3<'a>),
V4(BorshableTransactionMessageV4<'a>),
}

#[derive(BorshDeserialize, BorshSerialize)]
Expand Down Expand Up @@ -720,6 +729,21 @@ struct BorshableTransactionMessageV3<'a> {
pub raw_input: Cow<'a, Vec<u8>>,
}

#[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<Option<Vec<u8>>>>,
pub raw_input: Cow<'a, Vec<u8>>,
pub action_hash: [u8; 32],
}

impl<'a> From<&'a TransactionMessage> for BorshableTransactionMessage<'a> {
fn from(t: &'a TransactionMessage) -> Self {
Self::V3(BorshableTransactionMessageV3 {
Expand Down Expand Up @@ -756,6 +780,7 @@ impl<'a> TryFrom<BorshableTransactionMessage<'a>> for TransactionMessage {
transaction,
promise_data: Vec::new(),
raw_input,
action_hash: H256::default(),
})
}
BorshableTransactionMessage::V2(t) => {
Expand All @@ -772,6 +797,7 @@ impl<'a> TryFrom<BorshableTransactionMessage<'a>> for TransactionMessage {
transaction,
promise_data: t.promise_data.into_owned(),
raw_input,
action_hash: H256::default(),
})
}
BorshableTransactionMessage::V3(t) => Ok(Self {
Expand All @@ -785,6 +811,20 @@ impl<'a> TryFrom<BorshableTransactionMessage<'a>> 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),
}),
}
}
Expand Down
18 changes: 15 additions & 3 deletions engine-tests/src/tests/random.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
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();
let random: Random = runner
.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);
}
2 changes: 1 addition & 1 deletion engine-tests/src/tests/repro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
8 changes: 6 additions & 2 deletions engine-tests/src/tests/sanity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
33 changes: 26 additions & 7 deletions engine-tests/src/tests/standalone/call_tracer.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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(),
};
Expand Down
1 change: 1 addition & 0 deletions engine-tests/src/tests/standalone/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions engine-tests/src/tests/standalone/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<AuroraModExp>(
Expand Down Expand Up @@ -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::<AuroraModExp>(
Expand Down Expand Up @@ -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::<AuroraModExp>(
Expand Down Expand Up @@ -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::<AuroraModExp>(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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::<AuroraModExp>(
Expand Down Expand Up @@ -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::<AuroraModExp>(
Expand Down Expand Up @@ -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::<AuroraModExp>(
Expand Down
1 change: 1 addition & 0 deletions engine-tests/src/tests/standalone/tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
18 changes: 15 additions & 3 deletions engine-tests/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PromiseResult>,
// 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<H256>,
}

/// Same as `AuroraRunner`, but consumes `self` on execution (thus preventing building on
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
}
}
}
Expand Down
Loading

0 comments on commit cc9c800

Please sign in to comment.