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
anorth committed Aug 26, 2024
1 parent 8e742d8 commit 10f046d
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 22 deletions.
11 changes: 1 addition & 10 deletions actors/evm/src/interpreter/precompiles/fvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,7 @@ pub(super) fn call_actor_shared<RT: Runtime>(
/// | 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<RT: Runtime>(
system: &mut System<RT>,
Expand All @@ -241,13 +236,9 @@ pub(super) fn get_randomness<RT: Runtime>(
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)
}
5 changes: 2 additions & 3 deletions actors/evm/src/interpreter/precompiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,12 @@ pub struct Precompiles<RT>(PhantomData<RT>);

impl<RT: Runtime> Precompiles<RT> {
/// FEVM specific precompiles (0xfe prefix)
const NATIVE_PRECOMPILES: PrecompileTable<RT, 6> = PrecompileTable([
const NATIVE_PRECOMPILES: PrecompileTable<RT, 5> = PrecompileTable([
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
Some(get_randomness::<RT>), // 0xfe00..06
]);

/// EVM specific precompiles
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
1 change: 0 additions & 1 deletion runtime/src/runtime/randomness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ pub enum DomainSeparationTag {
MarketDealCronSeed = 8,
PoStChainCommit = 9,
EvmPrevRandao = 10,
EvmRandPrecompile = 11,
}

#[allow(unused)]
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 10f046d

Please sign in to comment.