Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose beacon randomness through new EVM precompile via new runtime method (FIP-0095) #1577

Merged
merged 3 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
anorth marked this conversation as resolved.
Show resolved Hide resolved
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
Loading