From 10f046d7ed757eea836e0b838e18702c5fd81654 Mon Sep 17 00:00:00 2001 From: Alex North <445306+anorth@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:45:08 +1200 Subject: [PATCH] Adds a runtime method for fetching drand beacon randomness without necessarily mixing in additional entropy. Expose beacon randomness through new EVM precompile. --- actors/evm/src/interpreter/precompiles/fvm.rs | 11 +---- actors/evm/src/interpreter/precompiles/mod.rs | 5 +-- runtime/src/runtime/fvm.rs | 23 ++++++---- runtime/src/runtime/mod.rs | 8 ++++ runtime/src/runtime/randomness.rs | 1 - runtime/src/test_utils.rs | 42 +++++++++++++++++++ test_vm/src/messaging.rs | 7 ++++ 7 files changed, 75 insertions(+), 22 deletions(-) diff --git a/actors/evm/src/interpreter/precompiles/fvm.rs b/actors/evm/src/interpreter/precompiles/fvm.rs index 69b12abc6..fc6fc2098 100644 --- a/actors/evm/src/interpreter/precompiles/fvm.rs +++ b/actors/evm/src/interpreter/precompiles/fvm.rs @@ -226,12 +226,7 @@ pub(super) fn call_actor_shared( /// | Param | Value | /// |------------------|---------------------------| /// | randomness_epoch | U256 - low i64 | -/// | entropy_length | U256 - low u32 | -/// | entropy | input\[32..] (right padded)| /// -/// any bytes in between values are ignored -/// -/// Returns empty array if invalid randomness type /// Errors if unable to fetch randomness pub(super) fn get_randomness( system: &mut System, @@ -241,13 +236,9 @@ pub(super) fn get_randomness( let mut input_params = ValueReader::new(input); let randomness_epoch = input_params.read_value()?; - let entropy_length: u32 = input_params.read_value()?; - let entropy = input_params.read_padded(entropy_length.try_into().unwrap_or(0)); - let randomness = system.rt.get_randomness_from_beacon( - fil_actors_runtime::runtime::DomainSeparationTag::EvmRandPrecompile, + let randomness = system.rt.get_beacon_randomness( randomness_epoch, - &entropy, ); randomness.map(|r| r.to_vec()).map_err(|_| PrecompileError::InvalidInput) } diff --git a/actors/evm/src/interpreter/precompiles/mod.rs b/actors/evm/src/interpreter/precompiles/mod.rs index 046bf0bab..b66f719d3 100644 --- a/actors/evm/src/interpreter/precompiles/mod.rs +++ b/actors/evm/src/interpreter/precompiles/mod.rs @@ -41,13 +41,12 @@ pub struct Precompiles(PhantomData); impl Precompiles { /// FEVM specific precompiles (0xfe prefix) - const NATIVE_PRECOMPILES: PrecompileTable = PrecompileTable([ + const NATIVE_PRECOMPILES: PrecompileTable = PrecompileTable([ Some(resolve_address::), // 0xfe00..01 Some(lookup_delegated_address::), // 0xfe00..02 Some(call_actor::), // 0xfe00..03 - None, // 0xfe00..04 DISABLED + Some(get_randomness::), // 0xfe00..04 Some(call_actor_id::), // 0xfe00..05 - Some(get_randomness::), // 0xfe00..06 ]); /// EVM specific precompiles diff --git a/runtime/src/runtime/fvm.rs b/runtime/src/runtime/fvm.rs index c87991dfb..12b9dfbae 100644 --- a/runtime/src/runtime/fvm.rs +++ b/runtime/src/runtime/fvm.rs @@ -255,14 +255,7 @@ where rand_epoch: ChainEpoch, entropy: &[u8], ) -> Result<[u8; RANDOMNESS_LENGTH], ActorError> { - let digest = fvm::rand::get_beacon_randomness(rand_epoch).map_err(|e| { - match e { - ErrorNumber::LimitExceeded => { - actor_error!(illegal_argument; "randomness lookback exceeded: {}", e) - } - e => actor_error!(assertion_failed; "get beacon randomness failed with an unexpected error: {}", e), - } - })?; + let digest = self.get_beacon_randomness(rand_epoch)?; Ok(draw_randomness( fvm::crypto::hash_blake2b, &digest, @@ -272,6 +265,20 @@ where )) } + fn get_beacon_randomness( + &self, + rand_epoch: ChainEpoch, + ) -> Result<[u8; RANDOMNESS_LENGTH], ActorError> { + fvm::rand::get_beacon_randomness(rand_epoch).map_err(|e| { + match e { + ErrorNumber::LimitExceeded => { + actor_error!(illegal_argument; "randomness lookback exceeded: {}", e) + } + e => actor_error!(assertion_failed; "get beacon randomness failed with an unexpected error: {}", e), + } + }) + } + fn get_state_root(&self) -> Result { Ok(fvm::sself::root()?) } diff --git a/runtime/src/runtime/mod.rs b/runtime/src/runtime/mod.rs index ec1e03b3e..5ce9d1424 100644 --- a/runtime/src/runtime/mod.rs +++ b/runtime/src/runtime/mod.rs @@ -117,6 +117,14 @@ pub trait Runtime: Primitives + RuntimePolicy { entropy: &[u8], ) -> Result<[u8; RANDOMNESS_LENGTH], ActorError>; + /// Returns a (pseudo)random byte array drawing from the latest + /// beacon from a given epoch. + /// This randomness is not tied to any fork of the chain, and is unbiasable. + fn get_beacon_randomness( + &self, + rand_epoch: ChainEpoch, + ) -> Result<[u8; RANDOMNESS_LENGTH], ActorError>; + /// Initializes the state object. /// This is only valid when the state has not yet been initialized. /// NOTE: we should also limit this to being invoked during the constructor method diff --git a/runtime/src/runtime/randomness.rs b/runtime/src/runtime/randomness.rs index 8283ad814..bb7d05d84 100644 --- a/runtime/src/runtime/randomness.rs +++ b/runtime/src/runtime/randomness.rs @@ -20,7 +20,6 @@ pub enum DomainSeparationTag { MarketDealCronSeed = 8, PoStChainCommit = 9, EvmPrevRandao = 10, - EvmRandPrecompile = 11, } #[allow(unused)] diff --git a/runtime/src/test_utils.rs b/runtime/src/test_utils.rs index 4c10379ce..eff3bd023 100644 --- a/runtime/src/test_utils.rs +++ b/runtime/src/test_utils.rs @@ -200,6 +200,7 @@ pub struct Expectations { pub expect_verify_consensus_fault: Option, pub expect_get_randomness_tickets: VecDeque, pub expect_get_randomness_beacon: VecDeque, + pub expect_get_beacon_randomness: VecDeque, pub expect_batch_verify_seals: Option, pub expect_aggregate_verify_seals: Option, pub expect_replica_verify: VecDeque, @@ -285,6 +286,11 @@ impl Expectations { "expect_get_randomness_beacon {:?}, not received", this.expect_get_randomness_beacon ); + assert!( + this.expect_get_beacon_randomness.is_empty(), + "expect_get_beacon_randomness {:?}, not received", + this.expect_get_beacon_randomness + ); assert!( this.expect_batch_verify_seals.is_none(), "expect_batch_verify_seals {:?}, not received", @@ -422,6 +428,12 @@ pub struct ExpectRandomness { out: [u8; RANDOMNESS_LENGTH], } +#[derive(Clone, Debug)] +pub struct ExpectGetBeacon { + epoch: ChainEpoch, + out: [u8; RANDOMNESS_LENGTH], +} + #[derive(Debug)] pub struct ExpectBatchVerifySeals { input: Vec, @@ -748,6 +760,16 @@ impl MockRuntime { self.expectations.borrow_mut().expect_get_randomness_beacon.push_back(a); } + #[allow(dead_code)] + pub fn expect_get_beacon_randomness( + &self, + epoch: ChainEpoch, + out: [u8; RANDOMNESS_LENGTH], + ) { + let a = ExpectGetBeacon { epoch, out }; + self.expectations.borrow_mut().expect_get_beacon_randomness.push_back(a); + } + #[allow(dead_code)] pub fn expect_batch_verify_seals( &self, @@ -1070,6 +1092,26 @@ impl Runtime for MockRuntime { Ok(expected.out) } + fn get_beacon_randomness( + &self, + epoch: ChainEpoch, + ) -> Result<[u8; RANDOMNESS_LENGTH], ActorError> { + let expected = self + .expectations + .borrow_mut() + .expect_get_beacon_randomness + .pop_front() + .expect("unexpected call to get_randomness_from_beacon"); + + assert!(epoch <= *self.epoch.borrow(), "attempt to get randomness from future"); + assert_eq!( + expected.epoch, epoch, + "unexpected epoch, expected: {:?}, actual: {:?}", + expected.epoch, epoch + ); + Ok(expected.out) + } + fn create(&self, obj: &T) -> Result<(), ActorError> { if self.state.borrow().is_some() { return Err(actor_error!(illegal_state; "state already constructed")); diff --git a/test_vm/src/messaging.rs b/test_vm/src/messaging.rs index 70521f9c3..2257bad83 100644 --- a/test_vm/src/messaging.rs +++ b/test_vm/src/messaging.rs @@ -549,6 +549,13 @@ impl<'invocation> Runtime for InvocationCtx<'invocation> { Ok(TEST_VM_RAND_ARRAY) } + fn get_beacon_randomness( + &self, + _rand_epoch: ChainEpoch, + ) -> Result<[u8; RANDOMNESS_LENGTH], ActorError> { + Ok(TEST_VM_RAND_ARRAY) + } + fn get_state_root(&self) -> Result { Ok(self.v.actor(&self.to()).unwrap().state) }