Skip to content

Commit

Permalink
Merge pull request #51 from NethermindEth/proposer_duties
Browse files Browse the repository at this point in the history
Proposer duties taken from CL
  • Loading branch information
smartprogrammer93 authored Jul 17, 2024
2 parents 0e30434 + 652df1d commit 6b21d85
Show file tree
Hide file tree
Showing 13 changed files with 1,902 additions and 407 deletions.
1,451 changes: 1,310 additions & 141 deletions Node/Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ reqwest = "0.12"
hex = "0.4"
tiny-keccak = "2.0"
secp256k1 = "0.29"
beacon-api-client = { git = "https://github.com/ralexstokes/ethereum-consensus", package = "beacon-api-client" }

[dev-dependencies]
mockito = "1.4"
79 changes: 79 additions & 0 deletions Node/src/ethereum_l1/consensus_layer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#![allow(dead_code)] // TODO: remove
use anyhow::Error;
use beacon_api_client::{mainnet::MainnetClientTypes, Client, GenesisDetails, ProposerDuty};
use reqwest;

pub struct ConsensusLayer {
client: Client<MainnetClientTypes>,
}

impl ConsensusLayer {
pub fn new(rpc_url: &str) -> Result<Self, Error> {
let client = Client::new(reqwest::Url::parse(rpc_url)?);
Ok(Self { client })
}

pub async fn get_current_epoch(&self) -> Result<u64, Error> {
let header = self.client.get_beacon_header_at_head().await?;
let slot = header.header.message.slot;
Ok(slot / 32)
}

pub async fn get_lookahead(&self, epoch: u64) -> Result<Vec<ProposerDuty>, Error> {
let (_, duties) = self.client.get_proposer_duties(epoch).await?;
Ok(duties)
}

pub async fn get_genesis_data(&self) -> Result<GenesisDetails, Error> {
self.client.get_genesis_details().await.map_err(Error::new)
}
}

#[cfg(test)]
pub mod tests {
use super::*;
use tokio;

#[tokio::test]
async fn test_get_lookahead() {
let server = setup_server().await;
let cl = ConsensusLayer::new(server.url().as_str()).unwrap();
let duties = cl.get_lookahead(1).await.unwrap();

assert_eq!(duties.len(), 32);
assert_eq!(duties[0].slot, 32);
}

#[tokio::test]
async fn test_get_genesis_data() {
let server = setup_server().await;
let cl = ConsensusLayer::new(server.url().as_str()).unwrap();
let genesis_data = cl.get_genesis_data().await.unwrap();

assert_eq!(genesis_data.genesis_time, 1590832934);
assert_eq!(
genesis_data.genesis_validators_root.to_string(),
"0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
);
assert_eq!(genesis_data.genesis_fork_version, [0; 4]);
}

pub async fn setup_server() -> mockito::ServerGuard {
let mut server = mockito::Server::new_async().await;
server
.mock("GET", "/eth/v1/beacon/genesis")
.with_body(r#"{
"data": {
"genesis_time": "1590832934",
"genesis_validators_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"genesis_fork_version": "0x00000000"
}
}"#)
.create();
server
.mock("GET", "/eth/v1/validator/duties/proposer/1")
.with_body(include_str!("lookahead_test_response.json"))
.create();
server
}
}
208 changes: 208 additions & 0 deletions Node/src/ethereum_l1/execution_layer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
use alloy::{
network::{Ethereum, EthereumWallet, NetworkWallet},
primitives::{Address, Bytes, FixedBytes, U256},
providers::ProviderBuilder,
signers::local::PrivateKeySigner,
sol,
sol_types::SolValue,
};
use anyhow::Error;
use beacon_api_client::ProposerDuty;
use std::str::FromStr;

pub struct ExecutionLayer {
rpc_url: reqwest::Url,
wallet: EthereumWallet,
taiko_preconfirming_address: Address,
genesis_timestamp_sec: u64,
slot_duration_sec: u64,
}

sol!(
#[allow(missing_docs)]
#[sol(rpc)]
PreconfTaskManager,
"src/ethereum_l1/abi/PreconfTaskManager.json"
);

sol! {
/// @dev Hook and it's data (currently used only during proposeBlock)
struct HookCall {
address hook;
bytes data;
}

/// @dev Represents proposeBlock's _data input parameter
struct BlockParams {
address assignedProver; // DEPRECATED, value ignored.
address coinbase;
bytes32 extraData;
bytes32 parentMetaHash;
HookCall[] hookCalls; // DEPRECATED, value ignored.
bytes signature;
uint32 l1StateBlockNumber;
uint64 timestamp;
}
}

impl ExecutionLayer {
pub fn new(
rpc_url: &str,
private_key: &str,
taiko_preconfirming_address: &str,
genesis_timestamp_sec: u64,
slot_duration_sec: u64,
) -> Result<Self, Error> {
let signer = PrivateKeySigner::from_str(private_key)?;
let wallet = EthereumWallet::from(signer);

Ok(Self {
rpc_url: rpc_url.parse()?,
wallet,
taiko_preconfirming_address: taiko_preconfirming_address.parse()?,
genesis_timestamp_sec,
slot_duration_sec,
})
}

pub async fn propose_new_block(
&self,
tx_list: Vec<u8>,
parent_meta_hash: [u8; 32],
lookahead_set: Vec<ProposerDuty>,
) -> Result<(), Error> {
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(self.wallet.clone())
.on_http(self.rpc_url.clone());

let contract = PreconfTaskManager::new(self.taiko_preconfirming_address, provider);

let block_params = BlockParams {
assignedProver: Address::ZERO,
coinbase: <EthereumWallet as NetworkWallet<Ethereum>>::default_signer_address(
&self.wallet,
),
extraData: FixedBytes::from(&[0u8; 32]),
parentMetaHash: FixedBytes::from(&parent_meta_hash),
hookCalls: vec![],
signature: Bytes::from(vec![0; 32]),
l1StateBlockNumber: 0,
timestamp: 0,
};

let encoded_block_params = Bytes::from(BlockParams::abi_encode_sequence(&block_params));

let tx_list = Bytes::from(tx_list);
let lookahead_set_param: Vec<PreconfTaskManager::LookaheadSetParam> = lookahead_set
.iter()
.map(|duty| PreconfTaskManager::LookaheadSetParam {
timestamp: U256::from(self.calculate_slot_timestamp(duty.slot)),
preconfer: Address::ZERO, //TODO: Replace it with a BLS key when the contract is ready.
})
.collect();

let builder = contract.newBlockProposal(
encoded_block_params,
tx_list,
U256::from(0), //TODO: Replace it with the proper lookaheadPointer when the contract is ready.
lookahead_set_param,
);

let tx_hash = builder.send().await?.watch().await?;
tracing::debug!("Proposed new block: {tx_hash}");

Ok(())
}

fn calculate_slot_timestamp(&self, slot: u64) -> u64 {
self.genesis_timestamp_sec + slot * self.slot_duration_sec
}

#[cfg(test)]
pub fn new_from_pk(
rpc_url: reqwest::Url,
private_key: elliptic_curve::SecretKey<k256::Secp256k1>,
) -> Result<Self, Error> {
let signer = PrivateKeySigner::from_signing_key(private_key.into());
let wallet = EthereumWallet::from(signer);

Ok(Self {
rpc_url,
wallet,
taiko_preconfirming_address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" // some random address for test
.parse()?,
genesis_timestamp_sec: 0,
slot_duration_sec: 12,
})
}

#[cfg(test)]
async fn call_test_contract(&self) -> Result<(), Error> {
sol! {
#[allow(missing_docs)]
#[sol(rpc, bytecode="6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033")]
contract Counter {
uint256 public number;

function setNumber(uint256 newNumber) public {
number = newNumber;
}

function increment() public {
number++;
}
}
}

let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(self.wallet.clone())
.on_http(self.rpc_url.clone());

let contract = Counter::deploy(&provider).await?;

let builder = contract.setNumber(U256::from(42));
let tx_hash = builder.send().await?.watch().await?;
println!("Set number to 42: {tx_hash}");

let builder = contract.increment();
let tx_hash = builder.send().await?.watch().await?;
println!("Incremented number: {tx_hash}");

let builder = contract.number();
let number = builder.call().await?.number.to_string();

assert_eq!(number, "43");

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use alloy::node_bindings::Anvil;

#[tokio::test]
async fn test_call_contract() {
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().try_spawn().unwrap();
let rpc_url: reqwest::Url = anvil.endpoint().parse().unwrap();
let private_key = anvil.keys()[0].clone();
let el = ExecutionLayer::new_from_pk(rpc_url, private_key).unwrap();
el.call_test_contract().await.unwrap();
}

#[tokio::test]
async fn test_propose_new_block() {
let anvil = Anvil::new().try_spawn().unwrap();
let rpc_url: reqwest::Url = anvil.endpoint().parse().unwrap();
let private_key = anvil.keys()[0].clone();
let el = ExecutionLayer::new_from_pk(rpc_url, private_key).unwrap();

el.propose_new_block(vec![0; 32], [0; 32], vec![])
.await
.unwrap();
}
}
Loading

0 comments on commit 6b21d85

Please sign in to comment.