Skip to content

Commit

Permalink
Expose beacon randomness through new EVM precompile via new runtime m…
Browse files Browse the repository at this point in the history
…ethod (FIP-0095) (#1577)

* Adds a runtime method for fetching drand beacon randomness without necessarily mixing in additional entropy. Expose beacon randomness through new EVM precompile.

* Back to address ..06.

* Add tests for beacon randomness precompile.

---------

Co-authored-by: zenground0 <[email protected]>
  • Loading branch information
anorth and ZenGround0 authored Sep 27, 2024
1 parent a45fb87 commit 64391b8
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 16 deletions.
18 changes: 18 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,21 @@ pub(super) fn call_actor_shared<RT: Runtime>(

Ok(output)
}

/// Params:
///
/// | Param | Value |
/// |------------------|---------------------------|
/// | 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)
}
7 changes: 4 additions & 3 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 @@ -41,12 +41,13 @@ pub struct Precompiles<RT>(PhantomData<RT>);

impl<RT: Runtime> Precompiles<RT> {
/// FEVM specific precompiles (0xfe prefix)
const NATIVE_PRECOMPILES: PrecompileTable<RT, 5> = PrecompileTable([
const NATIVE_PRECOMPILES: PrecompileTable<RT, 6> = PrecompileTable([
Some(resolve_address::<RT>), // 0xfe00..01
Some(lookup_delegated_address::<RT>), // 0xfe00..02
Some(call_actor::<RT>), // 0xfe00..03
None, // 0xfe00..04 DISABLED
None, // 0xfe00..04 get_actor_type DISABLED
Some(call_actor_id::<RT>), // 0xfe00..05
Some(get_randomness::<RT>), // 0xfe00..06
]);

/// EVM specific precompiles
Expand Down
38 changes: 38 additions & 0 deletions actors/evm/tests/precompile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,44 @@ fn test_resolve_delegated() {
rt.reset();
}

#[test]
fn test_precompile_randomness() {
let (init, body) = PrecompileTest::test_runner_assembly();
let rt =
util::construct_and_verify(asm::new_contract("precompile-tester", &init, &body).unwrap());
let rand_epoch = 100;
let rand_epoch_u256 = U256::from(rand_epoch);
{
// Underlying syscall succeeds.
let result = U256::from(0xdeadbeefu32).to_bytes();
let test = PrecompileTest {
precompile_address: NativePrecompile::GetRandomness.eth_address(),
output_size: 32,
expected_exit_code: PrecompileExit::Success,
gas_avaliable: 10_000_000_000,
call_op: util::PrecompileCallOpcode::StaticCall,
input: rand_epoch_u256.to_bytes().to_vec(),
expected_return: result.to_vec(),
};
rt.expect_get_beacon_randomness(rand_epoch, result, ExitCode::OK);
test.run_test(&rt);
}
{
// Underlying syscall fails.
let test = PrecompileTest {
precompile_address: NativePrecompile::GetRandomness.eth_address(),
output_size: 32,
expected_exit_code: PrecompileExit::Reverted, // Precompile reverts due to syscall failure
gas_avaliable: 10_000_000_000,
call_op: util::PrecompileCallOpcode::StaticCall,
input: rand_epoch_u256.to_bytes().to_vec(),
expected_return: vec![],
};
rt.expect_get_beacon_randomness(rand_epoch, [0u8; 32], ExitCode::USR_ILLEGAL_ARGUMENT);
test.run_test(&rt);
}
}

#[test]
fn test_precompile_transfer() {
let (init, body) = util::PrecompileTest::test_runner_assembly();
Expand Down
3 changes: 2 additions & 1 deletion actors/evm/tests/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ pub enum NativePrecompile {
ResolveAddress = 1,
LookupDelegatedAddress = 2,
CallActor = 3,
GetActorType = 4,
GetActorTypeDISABLED = 4,
CallActorId = 5,
GetRandomness = 6,
}

#[allow(dead_code)]
Expand Down
27 changes: 17 additions & 10 deletions runtime/src/runtime/fvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ where
) -> Result<[u8; RANDOMNESS_LENGTH], ActorError> {
let digest = fvm::rand::get_chain_randomness(rand_epoch).map_err(|e| {
match e {
ErrorNumber::LimitExceeded => {
actor_error!(illegal_argument; "randomness lookback exceeded: {}", e)
ErrorNumber::LimitExceeded | ErrorNumber::IllegalArgument => {
actor_error!(illegal_argument; "invalid lookback epoch: {}", e)
}
e => actor_error!(assertion_failed; "get chain randomness failed with an unexpected error: {}", e),
}
Expand All @@ -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 | ErrorNumber::IllegalArgument => {
actor_error!(illegal_argument; "invalid lookback epoch: {}", 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
48 changes: 46 additions & 2 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,13 @@ pub struct ExpectRandomness {
out: [u8; RANDOMNESS_LENGTH],
}

#[derive(Clone, Debug)]
pub struct ExpectGetBeacon {
epoch: ChainEpoch,
out: [u8; RANDOMNESS_LENGTH],
exit_code: ExitCode,
}

#[derive(Debug)]
pub struct ExpectBatchVerifySeals {
input: Vec<SealVerifyInfo>,
Expand Down Expand Up @@ -748,6 +761,17 @@ 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],
exit_code: ExitCode,
) {
let a = ExpectGetBeacon { epoch, out, exit_code };
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 @@ -1017,7 +1041,6 @@ impl Runtime for MockRuntime {
.pop_front()
.expect("unexpected call to get_randomness_from_tickets");

assert!(epoch <= *self.epoch.borrow(), "attempt to get randomness from future");
assert_eq!(
expected.tag, tag,
"unexpected domain separation tag, expected: {:?}, actual: {:?}",
Expand Down Expand Up @@ -1050,7 +1073,6 @@ impl Runtime for MockRuntime {
.pop_front()
.expect("unexpected call to get_randomness_from_beacon");

assert!(epoch <= *self.epoch.borrow(), "attempt to get randomness from future");
assert_eq!(
expected.tag, tag,
"unexpected domain separation tag, expected: {:?}, actual: {:?}",
Expand All @@ -1070,6 +1092,28 @@ impl Runtime for MockRuntime {
Ok(expected.out)
}

fn get_beacon_randomness(
&self,
epoch: ChainEpoch,
) -> Result<[u8; RANDOMNESS_LENGTH], ActorError> {
let exp = self
.expectations
.borrow_mut()
.expect_get_beacon_randomness
.pop_front()
.expect("unexpected call to get_randomness_from_beacon");

assert_eq!(
exp.epoch, epoch,
"unexpected epoch, expected: {:?}, actual: {:?}",
exp.epoch, epoch
);
if exp.exit_code != ExitCode::OK {
return Err(ActorError::unchecked(exp.exit_code, "Expected Failure".to_string()));
}
Ok(exp.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 64391b8

Please sign in to comment.