diff --git a/actors/evm/src/interpreter/precompiles/fvm.rs b/actors/evm/src/interpreter/precompiles/fvm.rs index dd9c52723..fc6fc2098 100644 --- a/actors/evm/src/interpreter/precompiles/fvm.rs +++ b/actors/evm/src/interpreter/precompiles/fvm.rs @@ -220,3 +220,25 @@ pub(super) fn call_actor_shared( Ok(output) } + +/// Params: +/// +/// | Param | Value | +/// |------------------|---------------------------| +/// | randomness_epoch | U256 - low i64 | +/// +/// Errors if unable to fetch randomness +pub(super) fn get_randomness( + system: &mut System, + input: &[u8], + _: PrecompileContext, +) -> PrecompileResult { + let mut input_params = ValueReader::new(input); + + let randomness_epoch = input_params.read_value()?; + + let randomness = system.rt.get_beacon_randomness( + randomness_epoch, + ); + 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 5faf7f68d..b66f719d3 100644 --- a/actors/evm/src/interpreter/precompiles/mod.rs +++ b/actors/evm/src/interpreter/precompiles/mod.rs @@ -13,7 +13,7 @@ mod evm; mod fvm; use evm::{blake2f, ec_add, ec_mul, ec_pairing, ec_recover, identity, modexp, ripemd160, sha256}; -use fvm::{call_actor, call_actor_id, lookup_delegated_address, resolve_address}; +use fvm::{call_actor, call_actor_id, get_randomness, lookup_delegated_address, resolve_address}; type PrecompileFn = fn(&mut System, &[u8], PrecompileContext) -> PrecompileResult; pub type PrecompileResult = Result, PrecompileError>; @@ -45,7 +45,7 @@ impl Precompiles { 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 ]); 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/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) }