Skip to content

Commit

Permalink
Adds a runtime method for fetching drand beacon randomness without ne…
Browse files Browse the repository at this point in the history
…cessarily mixing in additional entropy. Expose beacon randomness through new EVM precompile.
  • Loading branch information
ZenGround0 authored and anorth committed Aug 26, 2024
1 parent c3c41c5 commit ca3aff5
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 10 deletions.
22 changes: 22 additions & 0 deletions actors/evm/src/interpreter/precompiles/fvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,25 @@ pub(super) fn call_actor_shared<RT: Runtime>(

Ok(output)
}

/// Params:
///
/// | Param | Value |
/// |------------------|---------------------------|
/// | randomness_epoch | U256 - low i64 |
///
/// Errors if unable to fetch randomness
pub(super) fn get_randomness<RT: Runtime>(
system: &mut System<RT>,
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)
}
4 changes: 2 additions & 2 deletions actors/evm/src/interpreter/precompiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RT> = fn(&mut System<RT>, &[u8], PrecompileContext) -> PrecompileResult;
pub type PrecompileResult = Result<Vec<u8>, PrecompileError>;
Expand Down Expand Up @@ -45,7 +45,7 @@ impl<RT: Runtime> Precompiles<RT> {
Some(resolve_address::<RT>), // 0xfe00..01
Some(lookup_delegated_address::<RT>), // 0xfe00..02
Some(call_actor::<RT>), // 0xfe00..03
None, // 0xfe00..04 DISABLED
Some(get_randomness::<RT>), // 0xfe00..04
Some(call_actor_id::<RT>), // 0xfe00..05
]);

Expand Down
23 changes: 15 additions & 8 deletions runtime/src/runtime/fvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Cid, ActorError> {
Ok(fvm::sself::root()?)
}
Expand Down
8 changes: 8 additions & 0 deletions runtime/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions runtime/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ pub struct Expectations {
pub expect_verify_consensus_fault: Option<ExpectVerifyConsensusFault>,
pub expect_get_randomness_tickets: VecDeque<ExpectRandomness>,
pub expect_get_randomness_beacon: VecDeque<ExpectRandomness>,
pub expect_get_beacon_randomness: VecDeque<ExpectGetBeacon>,
pub expect_batch_verify_seals: Option<ExpectBatchVerifySeals>,
pub expect_aggregate_verify_seals: Option<ExpectAggregateVerifySeals>,
pub expect_replica_verify: VecDeque<ExpectReplicaVerify>,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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<SealVerifyInfo>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<T: Serialize>(&self, obj: &T) -> Result<(), ActorError> {
if self.state.borrow().is_some() {
return Err(actor_error!(illegal_state; "state already constructed"));
Expand Down
7 changes: 7 additions & 0 deletions test_vm/src/messaging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Cid, ActorError> {
Ok(self.v.actor(&self.to()).unwrap().state)
}
Expand Down

0 comments on commit ca3aff5

Please sign in to comment.