Skip to content

Commit

Permalink
Merge pull request #88 from NethermindEth/mu/prep-mev-boost
Browse files Browse the repository at this point in the history
Prepare transaction list to force push to MEV Boost
  • Loading branch information
mikhailUshakoff authored Aug 27, 2024
2 parents 88beae3 + bcc200d commit 996a92b
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 71 deletions.
105 changes: 82 additions & 23 deletions Node/src/ethereum_l1/execution_layer.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use super::slot_clock::SlotClock;
use crate::utils::{config, types::*};
use alloy::{
consensus::TypedTransaction,
contract::EventPoller,
network::{Ethereum, EthereumWallet, NetworkWallet},
primitives::{Address, Bytes, FixedBytes, B256, U256},
providers::ProviderBuilder,
providers::{Provider, ProviderBuilder},
signers::{
local::{LocalSigner, PrivateKeySigner},
Signature, SignerSync,
Expand All @@ -29,6 +30,7 @@ pub struct ExecutionLayer {
contract_addresses: ContractAddresses,
slot_clock: Arc<SlotClock>,
preconf_registry_expiry_sec: u64,
chain_id: u64,
}

pub struct ContractAddresses {
Expand Down Expand Up @@ -129,7 +131,7 @@ sol!(
);

impl ExecutionLayer {
pub fn new(
pub async fn new(
rpc_url: &str,
avs_node_ecdsa_private_key: &str,
contract_addresses: &config::ContractAddresses,
Expand All @@ -147,6 +149,9 @@ impl ExecutionLayer {
let contract_addresses = Self::parse_contract_addresses(contract_addresses)
.map_err(|e| Error::msg(format!("Failed to parse contract addresses: {}", e)))?;

let provider = ProviderBuilder::new().on_http(rpc_url.parse()?);
let chain_id = provider.get_chain_id().await?;

Ok(Self {
rpc_url: rpc_url.parse()?,
signer,
Expand All @@ -155,6 +160,7 @@ impl ExecutionLayer {
contract_addresses,
slot_clock,
preconf_registry_expiry_sec,
chain_id,
})
}

Expand Down Expand Up @@ -182,17 +188,16 @@ impl ExecutionLayer {

pub async fn propose_new_block(
&self,
nonce: u64,
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());
send_to_contract: bool,
) -> Result<Vec<u8>, Error> {
let provider = ProviderBuilder::new().on_http(self.rpc_url.clone());

let contract =
PreconfTaskManager::new(self.contract_addresses.avs.preconf_task_manager, provider);
PreconfTaskManager::new(self.contract_addresses.avs.preconf_task_manager, &provider);

let block_params = BlockParams {
assignedProver: Address::ZERO,
Expand All @@ -213,6 +218,8 @@ impl ExecutionLayer {
let encoded_block_params = Bytes::from(BlockParams::abi_encode_sequence(&block_params));

let tx_list = Bytes::from(tx_list);

// create lookahead set
let lookahead_set_param = lookahead_set
.iter()
.map(|duty| {
Expand All @@ -223,17 +230,50 @@ impl ExecutionLayer {
})
.collect::<Result<Vec<_>, Error>>()?;

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,
);
// TODO check gas parameters
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,
)
.chain_id(self.chain_id)
.nonce(nonce) //TODO how to get it?
.gas(50_000)
.max_fee_per_gas(20_000_000_000)
.max_priority_fee_per_gas(1_000_000_000);

// Build transaction
let tx = builder.as_ref().clone().build_typed_tx();
let Ok(TypedTransaction::Eip1559(mut tx)) = tx else {
// TODO fix
panic!("Not EIP1559 transaction");
};

let tx_hash = builder.send().await?.watch().await?;
tracing::debug!("Proposed new block: {tx_hash}");
// Sign transaction
let signature = self
.wallet
.default_signer()
.sign_transaction(&mut tx)
.await?;

Ok(())
// Encode transaction
let mut buf = vec![];
tx.encode_with_signature(&signature, &mut buf, false);

// Send transaction
if send_to_contract {
let pending = provider
.send_raw_transaction(&buf)
.await?
.register()
.await?;

tracing::debug!("Proposed new block, with hash {}", pending.tx_hash());
}

Ok(buf)
}

pub async fn register_preconfer(&self) -> Result<(), Error> {
Expand Down Expand Up @@ -325,6 +365,15 @@ impl ExecutionLayer {
Ok(address)
}

pub async fn get_preconfer_nonce(&self) -> Result<u64, Error> {
let provider = ProviderBuilder::new().on_http(self.rpc_url.clone());

let nonce = provider
.get_transaction_count(self.preconfer_address)
.await?;
Ok(nonce)
}

pub async fn prove_incorrect_preconfirmation(
&self,
block_id: u64,
Expand Down Expand Up @@ -416,7 +465,7 @@ impl ExecutionLayer {
let params = contract
.getLookaheadParamsForEpoch(
U256::from(epoch_begin_timestamp),
validator_bls_pub_keys.map(|key| Bytes::from(key)),
validator_bls_pub_keys.map(Bytes::from),
)
.call()
.await?
Expand All @@ -443,14 +492,17 @@ impl ExecutionLayer {
}

#[cfg(test)]
pub fn new_from_pk(
pub async 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.clone());
let clock = SlotClock::new(0u64, 0u64, 12u64, 32u64);

let provider = ProviderBuilder::new().on_http(rpc_url.clone());
let chain_id = provider.get_chain_id().await?;

Ok(Self {
rpc_url,
signer,
Expand All @@ -471,6 +523,7 @@ impl ExecutionLayer {
},
},
preconf_registry_expiry_sec: 120,
chain_id,
})
}

Expand Down Expand Up @@ -527,7 +580,9 @@ mod tests {
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();
let el = ExecutionLayer::new_from_pk(rpc_url, private_key)
.await
.unwrap();
el.call_test_contract().await.unwrap();
}

Expand All @@ -536,9 +591,11 @@ mod tests {
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();
let el = ExecutionLayer::new_from_pk(rpc_url, private_key)
.await
.unwrap();

el.propose_new_block(vec![0; 32], [0; 32], vec![])
el.propose_new_block(0, vec![0; 32], [0; 32], vec![], true)
.await
.unwrap();
}
Expand All @@ -547,7 +604,9 @@ mod tests {
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();
let el = ExecutionLayer::new_from_pk(rpc_url, private_key)
.await
.unwrap();

let result = el.register_preconfer().await;
assert!(result.is_ok(), "Register method failed: {:?}", result.err());
Expand Down
9 changes: 6 additions & 3 deletions Node/src/ethereum_l1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ impl EthereumL1 {
contract_addresses,
slot_clock.clone(),
preconf_registry_expiry_sec,
)?;
)
.await?;

Ok(Self {
slot_clock,
Expand All @@ -64,9 +65,11 @@ mod tests {
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();
let el = ExecutionLayer::new_from_pk(rpc_url, private_key)
.await
.unwrap();

el.propose_new_block(vec![0; 32], [0; 32], duties)
el.propose_new_block(0, vec![0; 32], [0; 32], duties, true)
.await
.unwrap();
}
Expand Down
3 changes: 2 additions & 1 deletion Node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ async fn main() -> Result<(), Error> {
config.taiko_chain_id,
));

let mev_boost = mev_boost::MevBoost::new(&config.mev_boost_url);
let mev_boost = mev_boost::MevBoost::new(&config.mev_boost_url, config.validator_index);
let block_proposed_event_checker =
BlockProposedEventReceiver::new(taiko.clone(), node_tx.clone());
BlockProposedEventReceiver::start(block_proposed_event_checker).await;
let ethereum_l1 = Arc::new(ethereum_l1);

let node = node::Node::new(
node_rx,
node_to_p2p_tx,
Expand Down
48 changes: 48 additions & 0 deletions Node/src/mev_boost/constraints.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone)]
pub struct Constraint {
tx: String,
index: Option<u64>,
}

impl Constraint {
pub fn new(tx: String, index: Option<u64>) -> Self {
Self { tx, index }
}
}

#[derive(Serialize, Deserialize, Clone)]
pub struct ConstraintsMessage {
validator_index: u64,
slot: u64,
constraints: Vec<Constraint>,
}

impl ConstraintsMessage {
pub fn new(validator_index: u64, slot: u64, constraints: Vec<Constraint>) -> Self {
Self {
validator_index,
slot,
constraints,
}
}
}

#[derive(Serialize, Deserialize, Clone)]
pub struct SignedConstraints {
message: ConstraintsMessage,
signature: String,
}

impl SignedConstraints {
pub fn new(message: ConstraintsMessage, signature: String) -> Self {
Self { message, signature }
}
}

impl From<ConstraintsMessage> for Vec<u8> {
fn from(val: ConstraintsMessage) -> Self {
bincode::serialize(&val).expect("MEV Boost message serialization failed")
}
}
51 changes: 44 additions & 7 deletions Node/src/mev_boost/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,57 @@
#![allow(unused)] //TODO remove after the EthereumL1 is used in release code

use crate::ethereum_l1::EthereumL1;
use crate::utils::rpc_client::RpcClient;
use anyhow::Error;
use std::sync::Arc;

pub mod constraints;
use constraints::{Constraint, ConstraintsMessage, SignedConstraints};

pub struct MevBoost {
_rpc_client: RpcClient,
rpc_client: RpcClient,
validator_index: u64,
}

impl MevBoost {
pub fn new(rpc_url: &str) -> Self {
pub fn new(rpc_url: &str, validator_index: u64) -> Self {
let rpc_client = RpcClient::new(rpc_url);
Self {
_rpc_client: rpc_client,
rpc_client,
validator_index,
}
}

pub fn send_transaction(&self, _tx: &[u8], _validator_index: u64, _slot: u64) {
//TODO: implement
pub async fn force_inclusion(
&self,
constraints: Vec<Constraint>,
ethereum_l1: Arc<EthereumL1>,
) -> Result<(), Error> {
// Prepare the message
// TODO check slot id value
let slot_id = ethereum_l1.slot_clock.get_current_slot()?;

let message = ConstraintsMessage::new(self.validator_index, slot_id, constraints);

let data_to_sign: Vec<u8> = message.clone().into();

// Sign the message
// TODO: Determine if the transaction data needs to be signed as a JSON string.
let signature = ethereum_l1
.execution_layer
.sign_message_with_private_ecdsa_key(&data_to_sign)?;

// Prepare data to send
let signed_constraints =
SignedConstraints::new(message, format!("0x{}", hex::encode(signature)));
let json_data = serde_json::to_value(&signed_constraints).unwrap();

// https://chainbound.github.io/bolt-docs/api/builder#ethv1builderconstraints
let method = "/eth/v1/builder/constraints";
// Make rpc request
self.rpc_client
.call_method(method, vec![json_data])
.await
.unwrap();

Ok(())
}
}
Loading

0 comments on commit 996a92b

Please sign in to comment.