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

Prepare transaction list to force push to MEV Boost #88

Merged
merged 7 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
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 @@ -126,7 +128,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 @@ -144,6 +146,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 @@ -152,6 +157,7 @@ impl ExecutionLayer {
contract_addresses,
slot_clock,
preconf_registry_expiry_sec,
chain_id,
})
}

Expand Down Expand Up @@ -179,17 +185,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());
is_send: 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 @@ -207,6 +212,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 @@ -217,17 +224,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 is_send {
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 @@ -319,6 +359,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 @@ -410,7 +459,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 @@ -437,14 +486,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 @@ -465,6 +517,7 @@ impl ExecutionLayer {
},
},
preconf_registry_expiry_sec: 120,
chain_id,
})
}

Expand Down Expand Up @@ -521,7 +574,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 @@ -530,9 +585,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 @@ -541,7 +598,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