From 5b41e5c6b0e72b810f9322e56229ee4fa7f6ed1b Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 12 Jul 2024 09:54:26 +0200 Subject: [PATCH 1/6] feat(sidecar): use PooledTransactionsElement to support blob sidecars --- bolt-sidecar/src/primitives/commitment.rs | 19 ++++++++----------- bolt-sidecar/src/primitives/constraint.rs | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/bolt-sidecar/src/primitives/commitment.rs b/bolt-sidecar/src/primitives/commitment.rs index c220ad37..6eb83c43 100644 --- a/bolt-sidecar/src/primitives/commitment.rs +++ b/bolt-sidecar/src/primitives/commitment.rs @@ -1,8 +1,8 @@ +use serde::{de, Deserialize, Deserializer, Serialize}; use std::str::FromStr; use alloy_primitives::{keccak256, Signature, B256}; -use reth_primitives::TransactionSigned; -use serde::{de, Deserialize, Deserializer, Serialize}; +use reth_primitives::PooledTransactionsElement; /// Commitment requests sent by users or RPC proxies to the sidecar. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -18,11 +18,8 @@ pub struct InclusionRequest { /// The consensus slot number at which the transaction should be included. pub slot: u64, /// The transaction to be included. - #[serde( - deserialize_with = "deserialize_tx_signed", - serialize_with = "serialize_tx_signed" - )] - pub tx: TransactionSigned, + #[serde(deserialize_with = "deserialize_tx", serialize_with = "serialize_tx")] + pub tx: PooledTransactionsElement, /// The signature over the "slot" and "tx" fields by the user. /// A valid signature is the only proof that the user actually requested /// this specific commitment to be included at the given slot. @@ -44,16 +41,16 @@ impl InclusionRequest { } } -fn deserialize_tx_signed<'de, D>(deserializer: D) -> Result +fn deserialize_tx<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; let data = hex::decode(s.trim_start_matches("0x")).map_err(de::Error::custom)?; - TransactionSigned::decode_enveloped(&mut data.as_slice()).map_err(de::Error::custom) + PooledTransactionsElement::decode_enveloped(&mut data.as_slice()).map_err(de::Error::custom) } -fn serialize_tx_signed(tx: &TransactionSigned, serializer: S) -> Result +fn serialize_tx(tx: &PooledTransactionsElement, serializer: S) -> Result where S: serde::Serializer, { @@ -88,7 +85,7 @@ impl InclusionRequest { pub fn digest(&self) -> B256 { let mut data = Vec::new(); data.extend_from_slice(&self.slot.to_le_bytes()); - data.extend_from_slice(self.tx.hash.as_slice()); + data.extend_from_slice(self.tx.hash().as_slice()); keccak256(&data) } diff --git a/bolt-sidecar/src/primitives/constraint.rs b/bolt-sidecar/src/primitives/constraint.rs index 2ad6cbc6..5c4644c8 100644 --- a/bolt-sidecar/src/primitives/constraint.rs +++ b/bolt-sidecar/src/primitives/constraint.rs @@ -110,7 +110,7 @@ impl Constraint { Self { tx: format!("0x{}", hex::encode(encoded_tx)), index, - tx_decoded: req.tx, + tx_decoded: req.tx.into_transaction(), sender, } } From 4a86d6bc98f9c95c1bd43f18d8d65ee4ef00da88 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 12 Jul 2024 10:47:44 +0200 Subject: [PATCH 2/6] feat(sidecar): integrate PooledTransactionsElement everywhere --- bolt-sidecar/bin/sidecar.rs | 15 +++++-- bolt-sidecar/src/builder/state_root.rs | 2 +- bolt-sidecar/src/builder/template.rs | 52 +++++++++++++++-------- bolt-sidecar/src/common.rs | 11 +++-- bolt-sidecar/src/primitives/constraint.rs | 23 ++++------ bolt-sidecar/src/primitives/mod.rs | 38 +++++++++++++++++ bolt-sidecar/src/state/execution.rs | 8 ++-- bolt-sidecar/src/state/mod.rs | 22 +++++----- 8 files changed, 116 insertions(+), 55 deletions(-) diff --git a/bolt-sidecar/bin/sidecar.rs b/bolt-sidecar/bin/sidecar.rs index dca0f52e..5392c7bc 100644 --- a/bolt-sidecar/bin/sidecar.rs +++ b/bolt-sidecar/bin/sidecar.rs @@ -67,6 +67,7 @@ async fn main() -> eyre::Result<()> { loop { tokio::select! { Some(ApiEvent { request, response_tx }) = api_events_rx.recv() => { + let start = std::time::Instant::now(); tracing::info!("Received commitment request: {:?}", request); let validator_index = match consensus_state.validate_request(&request) { @@ -79,7 +80,7 @@ async fn main() -> eyre::Result<()> { }; let sender = match execution_state - .check_commitment_validity(&request) + .validate_commitment_request(&request) .await { Ok(sender) => { sender }, @@ -93,8 +94,9 @@ async fn main() -> eyre::Result<()> { // TODO: match when we have more request types let CommitmentRequest::Inclusion(request) = request; tracing::info!( + elapsed = ?start.elapsed(), tx_hash = %request.tx.hash(), - "Validation against execution state passed" + "Commitment request validated" ); // TODO: review all this `clone` usage @@ -106,8 +108,13 @@ async fn main() -> eyre::Result<()> { execution_state.add_constraint(request.slot, signed_constraints.clone()); - let res = serde_json::to_value(signed_constraints).map_err(Into::into); + + tracing::info!( + elapsed = ?start.elapsed(), + tx_hash = %request.tx.hash(), + "Processed commitment request" + ); let _ = response_tx.send(res).ok(); }, Ok(HeadEvent { slot, .. }) = head_tracker.next_head() => { @@ -147,7 +154,7 @@ async fn main() -> eyre::Result<()> { } - if let Err(e) = local_builder.build_new_local_payload(template.transactions()).await { + if let Err(e) = local_builder.build_new_local_payload(template.as_signed_transactions()).await { tracing::error!(err = ?e, "CRITICAL: Error while building local payload at slot deadline for {slot}"); }; }, diff --git a/bolt-sidecar/src/builder/state_root.rs b/bolt-sidecar/src/builder/state_root.rs index 73aa92f3..98a21662 100644 --- a/bolt-sidecar/src/builder/state_root.rs +++ b/bolt-sidecar/src/builder/state_root.rs @@ -15,7 +15,7 @@ mod tests { #[tokio::test] async fn test_trace_call() -> eyre::Result<()> { dotenvy::dotenv().ok(); - tracing_subscriber::fmt::init(); + let _ = tracing_subscriber::fmt::try_init(); tracing::info!("Starting test_trace_call"); diff --git a/bolt-sidecar/src/builder/template.rs b/bolt-sidecar/src/builder/template.rs index 356ddd8d..e42767c0 100644 --- a/bolt-sidecar/src/builder/template.rs +++ b/bolt-sidecar/src/builder/template.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use alloy_primitives::{Address, U256}; -use reth_primitives::{TransactionSigned, TxType}; +use reth_primitives::{PooledTransactionsElement, TransactionSigned}; use crate::{ common::max_transaction_cost, @@ -46,11 +46,11 @@ impl BlockTemplate { .and_modify(|state| { state.balance = state .balance - .saturating_add(max_transaction_cost(&c.tx_decoded)); + .saturating_add(max_transaction_cost(&c.transaction)); state.transaction_count += 1; }) .or_insert(AccountState { - balance: max_transaction_cost(&c.tx_decoded), + balance: max_transaction_cost(&c.transaction), transaction_count: 1, }); }); @@ -72,10 +72,25 @@ impl BlockTemplate { /// Returns all a clone of all transactions from the signed constraints list #[inline] - pub fn transactions(&self) -> Vec { + pub fn transactions(&self) -> Vec { self.signed_constraints_list .iter() - .flat_map(|sc| sc.message.constraints.iter().map(|c| c.tx_decoded.clone())) + .flat_map(|sc| sc.message.constraints.iter().map(|c| c.transaction.clone())) + .collect() + } + + /// Converts the list of signed constraints into a list of signed transactions. Use this when building + /// a local execution payload. + #[inline] + pub fn as_signed_transactions(&self) -> Vec { + self.signed_constraints_list + .iter() + .flat_map(|sc| { + sc.message + .constraints + .iter() + .map(|c| c.transaction.clone().into_transaction()) + }) .collect() } @@ -90,25 +105,28 @@ impl BlockTemplate { /// Returns the blob count of the block template. #[inline] pub fn blob_count(&self) -> usize { - self.signed_constraints_list.iter().fold(0, |acc, sc| { - acc + sc - .message - .constraints - .iter() - .filter(|c| c.tx_decoded.tx_type() == TxType::Eip4844) - .count() + self.signed_constraints_list.iter().fold(0, |mut acc, sc| { + acc += sc.message.constraints.iter().fold(0, |acc, c| { + acc + c + .transaction + .as_eip4844() + .map(|tx| tx.blob_versioned_hashes.len()) + .unwrap_or(0) + }); + + acc }) } /// Remove all signed constraints at the specified index and updates the state diff fn remove_constraints_at_index(&mut self, index: usize) { let sc = self.signed_constraints_list.remove(index); - let mut address_to_txs: HashMap> = HashMap::new(); + let mut address_to_txs: HashMap> = HashMap::new(); sc.message.constraints.iter().for_each(|c| { address_to_txs .entry(c.sender) - .and_modify(|txs| txs.push(&c.tx_decoded)) - .or_insert(vec![&c.tx_decoded]); + .and_modify(|txs| txs.push(&c.transaction)) + .or_insert(vec![&c.transaction]); }); // Collect the diff for each address and every transaction @@ -157,11 +175,11 @@ impl BlockTemplate { .iter() .flat_map(|c| c.1.clone()) .fold((U256::ZERO, u64::MAX), |mut acc, c| { - let nonce = c.tx_decoded.nonce(); + let nonce = c.transaction.nonce(); if nonce < acc.1 { acc.1 = nonce; } - acc.0 += max_transaction_cost(&c.tx_decoded); + acc.0 += max_transaction_cost(&c.transaction); acc }); diff --git a/bolt-sidecar/src/common.rs b/bolt-sidecar/src/common.rs index 4e429bcc..8caa8b7c 100644 --- a/bolt-sidecar/src/common.rs +++ b/bolt-sidecar/src/common.rs @@ -1,7 +1,10 @@ use alloy_primitives::U256; -use reth_primitives::TransactionSigned; +use reth_primitives::PooledTransactionsElement; -use crate::{primitives::AccountState, state::ValidationError}; +use crate::{ + primitives::{AccountState, TransactionExt}, + state::ValidationError, +}; /// Calculates the max_basefee `slot_diff` blocks in the future given a current basefee (in gwei). /// Returns None if an overflow would occur. @@ -26,7 +29,7 @@ pub fn calculate_max_basefee(current: u128, block_diff: u64) -> Option { } /// Calculates the max transaction cost (gas + value) in wei. -pub fn max_transaction_cost(transaction: &TransactionSigned) -> U256 { +pub fn max_transaction_cost(transaction: &PooledTransactionsElement) -> U256 { let gas_limit = transaction.gas_limit() as u128; let fee_cap = transaction.max_fee_per_gas(); @@ -40,7 +43,7 @@ pub fn max_transaction_cost(transaction: &TransactionSigned) -> U256 { /// 2. The balance of the account must be higher than the transaction's max cost. pub fn validate_transaction( account_state: &AccountState, - transaction: &TransactionSigned, + transaction: &PooledTransactionsElement, ) -> Result<(), ValidationError> { // Check if the nonce is correct (should be the same as the transaction count) if transaction.nonce() < account_state.transaction_count { diff --git a/bolt-sidecar/src/primitives/constraint.rs b/bolt-sidecar/src/primitives/constraint.rs index 5c4644c8..6cdfd285 100644 --- a/bolt-sidecar/src/primitives/constraint.rs +++ b/bolt-sidecar/src/primitives/constraint.rs @@ -1,5 +1,5 @@ use alloy_primitives::{keccak256, Address}; -use reth_primitives::TransactionSigned; +use reth_primitives::PooledTransactionsElement; use secp256k1::Message; use serde::{Deserialize, Serialize}; @@ -57,7 +57,7 @@ impl ConstraintsMessage { request: InclusionRequest, sender: Address, ) -> Self { - let constraints = vec![Constraint::from_inclusion_request(request, None, sender)]; + let constraints = vec![Constraint::from_transaction(request.tx, None, sender)]; Self { validator_index, slot, @@ -85,13 +85,10 @@ impl SignableBLS for ConstraintsMessage { /// A general constraint on block building. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct Constraint { - /// The raw transaction that needs to be included in the block - pub tx: String, /// The optional index at which the transaction needs to be included in the block pub index: Option, - /// The decoded transaction for internal use - #[serde(skip)] - pub(crate) tx_decoded: TransactionSigned, + /// The transaction to be included in the block + pub(crate) transaction: PooledTransactionsElement, /// The ec-recovered address of the transaction sender for internal use #[serde(skip)] pub(crate) sender: Address, @@ -99,18 +96,14 @@ pub struct Constraint { impl Constraint { /// Builds a constraint from an inclusion request and an optional index - pub fn from_inclusion_request( - req: InclusionRequest, + pub fn from_transaction( + transaction: PooledTransactionsElement, index: Option, sender: Address, ) -> Self { - let mut encoded_tx = Vec::new(); - req.tx.encode_enveloped(&mut encoded_tx); - Self { - tx: format!("0x{}", hex::encode(encoded_tx)), + transaction, index, - tx_decoded: req.tx.into_transaction(), sender, } } @@ -119,7 +112,7 @@ impl Constraint { /// TODO: remove if we go with SSZ pub fn as_bytes(&self) -> Vec { let mut data = Vec::new(); - data.extend_from_slice(self.tx.as_bytes()); + self.transaction.encode_enveloped(&mut data); data.extend_from_slice(&self.index.unwrap_or(0).to_le_bytes()); data } diff --git a/bolt-sidecar/src/primitives/mod.rs b/bolt-sidecar/src/primitives/mod.rs index c352131d..4dddf229 100644 --- a/bolt-sidecar/src/primitives/mod.rs +++ b/bolt-sidecar/src/primitives/mod.rs @@ -17,6 +17,7 @@ use ethereum_consensus::{ types::mainnet::ExecutionPayload, Fork, }; +use reth_primitives::{PooledTransactionsElement, TxType}; use tokio::sync::{mpsc, oneshot}; /// Commitment types, received by users wishing to receive preconfirmations. @@ -240,3 +241,40 @@ impl ChainHead { self.block.load(std::sync::atomic::Ordering::SeqCst) } } + +/// Trait that exposes additional information on transaction types that don't already do it +/// by themselves (e.g. [`PooledTransactionsElement`]). +pub trait TransactionExt { + fn gas_limit(&self) -> u64; + fn value(&self) -> U256; + fn tx_type(&self) -> TxType; +} + +impl TransactionExt for PooledTransactionsElement { + fn gas_limit(&self) -> u64 { + match self { + PooledTransactionsElement::Legacy { transaction, .. } => transaction.gas_limit, + PooledTransactionsElement::Eip2930 { transaction, .. } => transaction.gas_limit, + PooledTransactionsElement::Eip1559 { transaction, .. } => transaction.gas_limit, + PooledTransactionsElement::BlobTransaction(blob_tx) => blob_tx.transaction.gas_limit, + } + } + + fn value(&self) -> U256 { + match self { + PooledTransactionsElement::Legacy { transaction, .. } => transaction.value, + PooledTransactionsElement::Eip2930 { transaction, .. } => transaction.value, + PooledTransactionsElement::Eip1559 { transaction, .. } => transaction.value, + PooledTransactionsElement::BlobTransaction(blob_tx) => blob_tx.transaction.value, + } + } + + fn tx_type(&self) -> TxType { + match self { + PooledTransactionsElement::Legacy { .. } => TxType::Legacy, + PooledTransactionsElement::Eip2930 { .. } => TxType::Eip2930, + PooledTransactionsElement::Eip1559 { .. } => TxType::Eip1559, + PooledTransactionsElement::BlobTransaction(_) => TxType::Eip4844, + } + } +} diff --git a/bolt-sidecar/src/state/execution.rs b/bolt-sidecar/src/state/execution.rs index 53492178..96ccc045 100644 --- a/bolt-sidecar/src/state/execution.rs +++ b/bolt-sidecar/src/state/execution.rs @@ -8,7 +8,7 @@ use thiserror::Error; use crate::{ builder::BlockTemplate, common::{calculate_max_basefee, validate_transaction}, - primitives::{AccountState, CommitmentRequest, SignedConstraints, Slot}, + primitives::{AccountState, CommitmentRequest, SignedConstraints, Slot, TransactionExt}, }; use super::fetcher::StateFetcher; @@ -121,10 +121,10 @@ impl ExecutionState { /// timing or proposer slot targets. /// /// If the commitment is invalid because of nonce, basefee or balance errors, it will return an error. - /// If the commitment is valid, it will be added to the block template and its account state + /// If the commitment is valid, its account state /// will be cached. If this is succesful, any callers can be sure that the commitment is valid /// and SHOULD sign it and respond to the requester. - pub async fn check_commitment_validity( + pub async fn validate_commitment_request( &mut self, request: &CommitmentRequest, ) -> Result { @@ -157,6 +157,8 @@ impl ExecutionState { return Err(ValidationError::BaseFeeTooLow(max_basefee as u128)); } + // let transaction = req.tx.into_transaction(); + // If we have the account state, use it here if let Some(account_state) = self.account_state(&sender) { // Validate the transaction against the account state diff --git a/bolt-sidecar/src/state/mod.rs b/bolt-sidecar/src/state/mod.rs index 83cc2119..812368ab 100644 --- a/bolt-sidecar/src/state/mod.rs +++ b/bolt-sidecar/src/state/mod.rs @@ -80,7 +80,7 @@ mod tests { use execution::{ExecutionState, ValidationError}; use fetcher::StateClient; use reqwest::Url; - use reth_primitives::TransactionSigned; + use reth_primitives::PooledTransactionsElement; use tracing_subscriber::fmt; use crate::{ @@ -131,7 +131,7 @@ mod tests { // Trick to parse into the TransactionSigned type let tx_signed_bytes = signed.encoded_2718(); let tx_signed = - TransactionSigned::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); + PooledTransactionsElement::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); let request = CommitmentRequest::Inclusion(InclusionRequest { slot: 10, @@ -139,7 +139,7 @@ mod tests { signature: sig, }); - assert!(state.check_commitment_validity(&request).await.is_ok()); + assert!(state.validate_commitment_request(&request).await.is_ok()); } #[tokio::test] @@ -167,7 +167,7 @@ mod tests { // Trick to parse into the TransactionSigned type let tx_signed_bytes = signed.encoded_2718(); let tx_signed = - TransactionSigned::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); + PooledTransactionsElement::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); let request = CommitmentRequest::Inclusion(InclusionRequest { slot: 10, @@ -176,7 +176,7 @@ mod tests { }); assert!(matches!( - state.check_commitment_validity(&request).await, + state.validate_commitment_request(&request).await, Err(ValidationError::NonceTooHigh) )); } @@ -207,7 +207,7 @@ mod tests { // Trick to parse into the TransactionSigned type let tx_signed_bytes = signed.encoded_2718(); let tx_signed = - TransactionSigned::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); + PooledTransactionsElement::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); let request = CommitmentRequest::Inclusion(InclusionRequest { slot: 10, @@ -216,7 +216,7 @@ mod tests { }); assert!(matches!( - state.check_commitment_validity(&request).await, + state.validate_commitment_request(&request).await, Err(ValidationError::InsufficientBalance) )); } @@ -248,7 +248,7 @@ mod tests { // Trick to parse into the TransactionSigned type let tx_signed_bytes = signed.encoded_2718(); let tx_signed = - TransactionSigned::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); + PooledTransactionsElement::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); let request = CommitmentRequest::Inclusion(InclusionRequest { slot: 10, @@ -257,7 +257,7 @@ mod tests { }); assert!(matches!( - state.check_commitment_validity(&request).await, + state.validate_commitment_request(&request).await, Err(ValidationError::BaseFeeTooLow(_)) )); } @@ -287,7 +287,7 @@ mod tests { // Trick to parse into the TransactionSigned type let tx_signed_bytes = signed.encoded_2718(); let tx_signed = - TransactionSigned::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); + PooledTransactionsElement::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); let request = CommitmentRequest::Inclusion(InclusionRequest { slot: 10, @@ -295,7 +295,7 @@ mod tests { signature: sig, }); - assert!(state.check_commitment_validity(&request).await.is_ok()); + assert!(state.validate_commitment_request(&request).await.is_ok()); assert!(state.block_templates().get(&10).unwrap().transactions_len() == 1); let provider = ProviderBuilder::new().on_http(anvil.endpoint_url()); From c43445edb16eb8af4cd7db4cfffdc0a23c91882f Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 12 Jul 2024 15:02:35 +0200 Subject: [PATCH 3/6] fix(sidecar): integration + fix tests --- bolt-sidecar/bin/sidecar.rs | 13 +-- bolt-sidecar/src/builder/state_root.rs | 6 +- bolt-sidecar/src/builder/template.rs | 102 +++++++--------------- bolt-sidecar/src/client/rpc.rs | 12 +++ bolt-sidecar/src/crypto/bls.rs | 7 ++ bolt-sidecar/src/primitives/commitment.rs | 5 +- bolt-sidecar/src/state/execution.rs | 53 ++++++++--- bolt-sidecar/src/state/fetcher.rs | 11 ++- bolt-sidecar/src/state/mod.rs | 37 ++++++-- 9 files changed, 144 insertions(+), 102 deletions(-) diff --git a/bolt-sidecar/bin/sidecar.rs b/bolt-sidecar/bin/sidecar.rs index 5392c7bc..e2c0a55e 100644 --- a/bolt-sidecar/bin/sidecar.rs +++ b/bolt-sidecar/bin/sidecar.rs @@ -83,7 +83,7 @@ async fn main() -> eyre::Result<()> { .validate_commitment_request(&request) .await { - Ok(sender) => { sender }, + Ok(sender) => sender, Err(e) => { tracing::error!("Failed to commit request: {:?}", e); let _ = response_tx.send(Err(ApiError::Custom(e.to_string()))); @@ -96,7 +96,7 @@ async fn main() -> eyre::Result<()> { tracing::info!( elapsed = ?start.elapsed(), tx_hash = %request.tx.hash(), - "Commitment request validated" + "Validation against execution state passed" ); // TODO: review all this `clone` usage @@ -108,13 +108,8 @@ async fn main() -> eyre::Result<()> { execution_state.add_constraint(request.slot, signed_constraints.clone()); - let res = serde_json::to_value(signed_constraints).map_err(Into::into); - tracing::info!( - elapsed = ?start.elapsed(), - tx_hash = %request.tx.hash(), - "Processed commitment request" - ); + let res = serde_json::to_value(signed_constraints).map_err(Into::into); let _ = response_tx.send(res).ok(); }, Ok(HeadEvent { slot, .. }) = head_tracker.next_head() => { @@ -141,7 +136,7 @@ async fn main() -> eyre::Result<()> { let max_retries = 5; let mut i = 0; 'inner: while let Err(e) = mevboost_client - .submit_constraints(&template.signed_constraints_list) + .submit_constraints(&template.constraints) .await { tracing::error!(err = ?e, "Error submitting constraints, retrying..."); diff --git a/bolt-sidecar/src/builder/state_root.rs b/bolt-sidecar/src/builder/state_root.rs index 98a21662..a23d93f6 100644 --- a/bolt-sidecar/src/builder/state_root.rs +++ b/bolt-sidecar/src/builder/state_root.rs @@ -17,9 +17,13 @@ mod tests { dotenvy::dotenv().ok(); let _ = tracing_subscriber::fmt::try_init(); + let Some(rpc_url) = std::env::var("RPC_URL").ok() else { + tracing::warn!("RPC_URL not found in environment variables, skipping test"); + return Ok(()); + }; + tracing::info!("Starting test_trace_call"); - let rpc_url = std::env::var("RPC_URL").expect("RPC_URL must be set"); let rpc_url = Url::parse(&rpc_url).unwrap(); let client = RpcClient::new(rpc_url.clone()); diff --git a/bolt-sidecar/src/builder/template.rs b/bolt-sidecar/src/builder/template.rs index e42767c0..1973e84f 100644 --- a/bolt-sidecar/src/builder/template.rs +++ b/bolt-sidecar/src/builder/template.rs @@ -28,7 +28,7 @@ pub struct BlockTemplate { /// The state diffs per address given the list of commitments. state_diff: StateDiff, /// The signed constraints associated to the block - pub signed_constraints_list: Vec, + pub constraints: Vec, } impl BlockTemplate { @@ -37,43 +37,10 @@ impl BlockTemplate { &self.state_diff } - /// Adds a transaction to the block template and updates the state diff. - pub fn add_constraints(&mut self, signed_constraints: SignedConstraints) { - let mut address_to_state_diffs: HashMap = HashMap::new(); - signed_constraints.message.constraints.iter().for_each(|c| { - address_to_state_diffs - .entry(c.sender) - .and_modify(|state| { - state.balance = state - .balance - .saturating_add(max_transaction_cost(&c.transaction)); - state.transaction_count += 1; - }) - .or_insert(AccountState { - balance: max_transaction_cost(&c.transaction), - transaction_count: 1, - }); - }); - - // Now update intermediate state - address_to_state_diffs.iter().for_each(|(address, diff)| { - self.state_diff - .diffs - .entry(*address) - .and_modify(|(nonce, balance)| { - *nonce += diff.transaction_count; - *balance += diff.balance; - }) - .or_insert((diff.transaction_count, diff.balance)); - }); - - self.signed_constraints_list.push(signed_constraints); - } - - /// Returns all a clone of all transactions from the signed constraints list + /// Returns the cloned list of transactions from the constraints. #[inline] pub fn transactions(&self) -> Vec { - self.signed_constraints_list + self.constraints .iter() .flat_map(|sc| sc.message.constraints.iter().map(|c| c.transaction.clone())) .collect() @@ -83,7 +50,7 @@ impl BlockTemplate { /// a local execution payload. #[inline] pub fn as_signed_transactions(&self) -> Vec { - self.signed_constraints_list + self.constraints .iter() .flat_map(|sc| { sc.message @@ -97,7 +64,7 @@ impl BlockTemplate { /// Returns the length of the transactions in the block template. #[inline] pub fn transactions_len(&self) -> usize { - self.signed_constraints_list + self.constraints .iter() .fold(0, |acc, sc| acc + sc.message.constraints.len()) } @@ -105,7 +72,7 @@ impl BlockTemplate { /// Returns the blob count of the block template. #[inline] pub fn blob_count(&self) -> usize { - self.signed_constraints_list.iter().fold(0, |mut acc, sc| { + self.constraints.iter().fold(0, |mut acc, sc| { acc += sc.message.constraints.iter().fold(0, |acc, c| { acc + c .transaction @@ -118,38 +85,34 @@ impl BlockTemplate { }) } + /// Adds a list of constraints to the block template and updates the state diff. + pub fn add_constraints(&mut self, constraints: SignedConstraints) { + for constraint in constraints.message.constraints.iter() { + let max_cost = max_transaction_cost(&constraint.transaction); + self.state_diff + .diffs + .entry(constraint.sender) + .and_modify(|(nonce, balance)| { + *nonce += 1; + *balance += max_cost; + }) + .or_insert((1, max_cost)); + } + + self.constraints.push(constraints); + } + /// Remove all signed constraints at the specified index and updates the state diff fn remove_constraints_at_index(&mut self, index: usize) { - let sc = self.signed_constraints_list.remove(index); - let mut address_to_txs: HashMap> = HashMap::new(); - sc.message.constraints.iter().for_each(|c| { - address_to_txs - .entry(c.sender) - .and_modify(|txs| txs.push(&c.transaction)) - .or_insert(vec![&c.transaction]); - }); - - // Collect the diff for each address and every transaction - let address_to_diff: HashMap = address_to_txs - .iter() - .map(|(address, txs)| { - let mut state = AccountState::default(); - for tx in txs { - state.balance = state.balance.saturating_add(max_transaction_cost(tx)); - state.transaction_count = state.transaction_count.saturating_sub(1); - } - (*address, state) - }) - .collect(); + let constraints = self.constraints.remove(index); - // Update intermediate state - for (address, diff) in address_to_diff.iter() { + for constraint in constraints.message.constraints.iter() { self.state_diff .diffs - .entry(*address) + .entry(constraint.sender) .and_modify(|(nonce, balance)| { - *nonce = nonce.saturating_sub(diff.transaction_count); - *balance += diff.balance; + *nonce = nonce.saturating_sub(1); + *balance -= max_transaction_cost(&constraint.transaction); }); } } @@ -161,7 +124,7 @@ impl BlockTemplate { // The pre-confirmations made by such address, and the indexes of the signed constraints // in which they appear let constraints_with_address: Vec<(usize, Vec<&Constraint>)> = self - .signed_constraints_list + .constraints .iter() .enumerate() .map(|(idx, c)| (idx, &c.message.constraints)) @@ -184,13 +147,12 @@ impl BlockTemplate { }); if state.balance < max_total_cost || state.transaction_count > min_nonce { - // NOTE: We drop all the signed constraints containing such pre-confirmations - // since at least one of them has been invalidated. + // Remove invalidated constraints due to balance / nonce of chain state tracing::warn!( %address, - "Removing all signed constraints which contain such address pre-confirmations due to conflict with account state", + "Removing invalidated constraints for address" ); - indexes = constraints_with_address.iter().map(|c| c.0).collect(); + indexes = constraints_with_address.iter().map(|(i, _)| *i).collect(); } for index in indexes.into_iter().rev() { diff --git a/bolt-sidecar/src/client/rpc.rs b/bolt-sidecar/src/client/rpc.rs index ec796167..82116f16 100644 --- a/bolt-sidecar/src/client/rpc.rs +++ b/bolt-sidecar/src/client/rpc.rs @@ -45,6 +45,18 @@ impl RpcClient { Ok(fee_history.latest_block_base_fee().unwrap()) } + /// Get the blob basefee of the latest block. + pub async fn get_blob_basefee(&self, block_number: Option) -> TransportResult { + let tag = block_number.map_or(BlockNumberOrTag::Latest, BlockNumberOrTag::Number); + + let fee_history: FeeHistory = self + .0 + .request("eth_feeHistory", (U64::from(1), tag, &[] as &[f64])) + .await?; + + Ok(fee_history.latest_block_blob_base_fee().unwrap_or(0)) + } + /// Get the latest block number pub async fn get_head(&self) -> TransportResult { let result: U64 = self.0.request("eth_blockNumber", ()).await?; diff --git a/bolt-sidecar/src/crypto/bls.rs b/bolt-sidecar/src/crypto/bls.rs index 122690d7..e0625713 100644 --- a/bolt-sidecar/src/crypto/bls.rs +++ b/bolt-sidecar/src/crypto/bls.rs @@ -62,6 +62,13 @@ impl Signer { Self { key } } + /// Create a signer with a random BLS key. + pub fn random() -> Self { + Self { + key: random_bls_secret(), + } + } + /// Verify the signature of the object with the given public key. #[allow(dead_code)] pub fn verify( diff --git a/bolt-sidecar/src/primitives/commitment.rs b/bolt-sidecar/src/primitives/commitment.rs index 6eb83c43..416e6188 100644 --- a/bolt-sidecar/src/primitives/commitment.rs +++ b/bolt-sidecar/src/primitives/commitment.rs @@ -34,10 +34,7 @@ impl InclusionRequest { /// Validates the transaction fee against a minimum basefee. /// Returns true if the fee is greater than or equal to the min, false otherwise. pub fn validate_basefee(&self, min: u128) -> bool { - if self.tx.max_fee_per_gas() < min { - return false; - } - true + self.tx.max_fee_per_gas() >= min } } diff --git a/bolt-sidecar/src/state/execution.rs b/bolt-sidecar/src/state/execution.rs index 96ccc045..515496d2 100644 --- a/bolt-sidecar/src/state/execution.rs +++ b/bolt-sidecar/src/state/execution.rs @@ -1,14 +1,16 @@ use alloy_eips::eip4844::MAX_BLOBS_PER_BLOCK; use alloy_primitives::{Address, SignatureError}; use alloy_transport::TransportError; -use reth_primitives::transaction::TxType; +use reth_primitives::{ + revm_primitives::EnvKzgSettings, BlobTransactionValidationError, PooledTransactionsElement, +}; use std::{collections::HashMap, num::NonZero}; use thiserror::Error; use crate::{ builder::BlockTemplate, common::{calculate_max_basefee, validate_transaction}, - primitives::{AccountState, CommitmentRequest, SignedConstraints, Slot, TransactionExt}, + primitives::{AccountState, CommitmentRequest, SignedConstraints, Slot}, }; use super::fetcher::StateFetcher; @@ -17,8 +19,14 @@ use super::fetcher::StateFetcher; #[derive(Debug, Error)] pub enum ValidationError { /// The transaction fee is too low to cover the maximum base fee. - #[error("Transaction fee is too low, need {0} gwei to cover the maximum base fee")] + #[error("Transaction fee is too low, need {0} gwei to cover the maximum basefee")] BaseFeeTooLow(u128), + /// The transaction blob fee is too low to cover the maximum blob base fee. + #[error("Transaction blob fee is too low, need {0} gwei to cover the maximum blob basefee")] + BlobBaseFeeTooLow(u128), + /// The transaction blob is invalid. + #[error(transparent)] + BlobValidation(#[from] BlobTransactionValidationError), /// The transaction nonce is too low. #[error("Transaction nonce too low")] NonceTooLow, @@ -66,12 +74,12 @@ impl ValidationError { pub struct ExecutionState { /// The latest block number. block_number: u64, - /// The latest slot number. slot: u64, - - /// The base fee at the head block. + /// The basefee at the head block. basefee: u128, + /// The blob basefee at the head block. + blob_basefee: u128, /// The cached account states. This should never be read directly. /// These only contain the canonical account states at the head block, /// not the intermediate states. @@ -84,6 +92,9 @@ pub struct ExecutionState { max_commitments_per_slot: NonZero, + /// The KZG settings for validating blobs. + kzg_settings: EnvKzgSettings, + /// The state fetcher client. client: C, } @@ -95,12 +106,18 @@ impl ExecutionState { client: C, max_commitments_per_slot: NonZero, ) -> Result { + let (basefee, blob_basefee) = + tokio::try_join!(client.get_basefee(None), client.get_blob_basefee(None))?; + Ok(Self { - basefee: client.get_basefee(None).await?, + basefee, + blob_basefee, block_number: client.get_head().await?, slot: 0, account_states: HashMap::new(), block_templates: HashMap::new(), + // Load the default KZG settings + kzg_settings: EnvKzgSettings::default(), max_commitments_per_slot, client, }) @@ -124,6 +141,8 @@ impl ExecutionState { /// If the commitment is valid, its account state /// will be cached. If this is succesful, any callers can be sure that the commitment is valid /// and SHOULD sign it and respond to the requester. + /// + /// TODO: should also validate everything in https://github.com/paradigmxyz/reth/blob/9aa44e1a90b262c472b14cd4df53264c649befc2/crates/transaction-pool/src/validate/eth.rs#L153 pub async fn validate_commitment_request( &mut self, request: &CommitmentRequest, @@ -157,8 +176,6 @@ impl ExecutionState { return Err(ValidationError::BaseFeeTooLow(max_basefee as u128)); } - // let transaction = req.tx.into_transaction(); - // If we have the account state, use it here if let Some(account_state) = self.account_state(&sender) { // Validate the transaction against the account state @@ -183,14 +200,27 @@ impl ExecutionState { } // Check EIP-4844-specific limits - if req.tx.tx_type() == TxType::Eip4844 { + if let Some(transaction) = req.tx.as_eip4844() { if let Some(template) = self.block_templates.get(&req.slot) { if template.blob_count() >= MAX_BLOBS_PER_BLOCK { return Err(ValidationError::Eip4844Limit); } } - // TODO: check max_fee_per_blob_gas against the blob_base_fee + let PooledTransactionsElement::BlobTransaction(ref blob_transaction) = req.tx else { + unreachable!("EIP-4844 transaction should be a blob transaction") + }; + + // Calculate max possible increase in blob basefee + let max_blob_basefee = calculate_max_basefee(self.blob_basefee, slot_diff) + .ok_or(reject_internal("Overflow calculating max blob basefee"))?; + + if blob_transaction.transaction.max_fee_per_blob_gas < max_blob_basefee { + return Err(ValidationError::BlobBaseFeeTooLow(max_blob_basefee)); + } + + // Validate blob against KZG settings + transaction.validate_blob(&blob_transaction.sidecar, self.kzg_settings.get())?; } Ok(sender) @@ -294,6 +324,7 @@ impl ExecutionState { pub struct StateUpdate { pub account_states: HashMap, pub min_basefee: u128, + pub min_blob_basefee: u128, pub block_number: u64, } diff --git a/bolt-sidecar/src/state/fetcher.rs b/bolt-sidecar/src/state/fetcher.rs index 58957978..049aab94 100644 --- a/bolt-sidecar/src/state/fetcher.rs +++ b/bolt-sidecar/src/state/fetcher.rs @@ -33,6 +33,8 @@ pub trait StateFetcher { async fn get_basefee(&self, block_number: Option) -> Result; + async fn get_blob_basefee(&self, block_number: Option) -> Result; + async fn get_account_state( &self, address: &Address, @@ -103,12 +105,14 @@ impl StateFetcher for StateClient { batch.send().await?; let basefee = self.client.get_basefee(None); + let blob_basefee = self.client.get_blob_basefee(None); // Collect the results - let (nonce_vec, balance_vec, basefee) = tokio::join!( + let (nonce_vec, balance_vec, basefee, blob_basefee) = tokio::join!( nonce_futs.collect::>(), balance_futs.collect::>(), basefee, + blob_basefee, ); // Insert the results @@ -143,6 +147,7 @@ impl StateFetcher for StateClient { Ok(StateUpdate { account_states, min_basefee: basefee?, + min_blob_basefee: blob_basefee?, block_number, }) } @@ -155,6 +160,10 @@ impl StateFetcher for StateClient { self.client.get_basefee(block_number).await } + async fn get_blob_basefee(&self, block_number: Option) -> Result { + self.client.get_blob_basefee(block_number).await + } + async fn get_account_state( &self, address: &Address, diff --git a/bolt-sidecar/src/state/mod.rs b/bolt-sidecar/src/state/mod.rs index 812368ab..7b17c67b 100644 --- a/bolt-sidecar/src/state/mod.rs +++ b/bolt-sidecar/src/state/mod.rs @@ -84,7 +84,8 @@ mod tests { use tracing_subscriber::fmt; use crate::{ - primitives::{CommitmentRequest, InclusionRequest}, + crypto::{bls::Signer, SignableBLS, SignerBLS}, + primitives::{CommitmentRequest, ConstraintsMessage, InclusionRequest, SignedConstraints}, test_util::{default_test_transaction, launch_anvil}, }; @@ -266,6 +267,8 @@ mod tests { async fn test_invalidate_inclusion_request() { let _ = fmt::try_init(); + let target_slot = 10; + let anvil = launch_anvil(); let client = StateClient::new(Url::parse(&anvil.endpoint()).unwrap()); @@ -284,19 +287,37 @@ mod tests { let signer: EthereumWallet = wallet.into(); let signed = tx.build(&signer).await.unwrap(); + let bls_signer = Signer::random(); + // Trick to parse into the TransactionSigned type let tx_signed_bytes = signed.encoded_2718(); let tx_signed = PooledTransactionsElement::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); - let request = CommitmentRequest::Inclusion(InclusionRequest { - slot: 10, + let inclusion_request = InclusionRequest { + slot: target_slot, tx: tx_signed, signature: sig, - }); + }; + + let request = CommitmentRequest::Inclusion(inclusion_request.clone()); assert!(state.validate_commitment_request(&request).await.is_ok()); - assert!(state.block_templates().get(&10).unwrap().transactions_len() == 1); + + let message = ConstraintsMessage::build(0, target_slot, inclusion_request, sender); + let signature = bls_signer.sign(&message.digest()).unwrap().to_string(); + let signed_constraints = SignedConstraints { message, signature }; + + state.add_constraint(target_slot, signed_constraints.clone()); + + assert!( + state + .block_templates() + .get(&target_slot) + .unwrap() + .transactions_len() + == 1 + ); let provider = ProviderBuilder::new().on_http(anvil.endpoint_url()); @@ -314,7 +335,11 @@ mod tests { .await .unwrap(); - let transactions_len = state.block_templates().get(&10).unwrap().transactions_len(); + let transactions_len = state + .block_templates() + .get(&target_slot) + .unwrap() + .transactions_len(); assert!(transactions_len == 0); } } From e646fdc64c28b18536f23fa5d077affa826954e8 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 12 Jul 2024 15:10:08 +0200 Subject: [PATCH 4/6] fix(sidecar): remove stale templates + test --- bolt-sidecar/src/state/execution.rs | 3 ++ bolt-sidecar/src/state/mod.rs | 62 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/bolt-sidecar/src/state/execution.rs b/bolt-sidecar/src/state/execution.rs index 515496d2..668ee23f 100644 --- a/bolt-sidecar/src/state/execution.rs +++ b/bolt-sidecar/src/state/execution.rs @@ -257,6 +257,9 @@ impl ExecutionState { self.apply_state_update(update); + // Remove any block templates that are no longer valid + self.block_templates.remove(&slot); + Ok(()) } diff --git a/bolt-sidecar/src/state/mod.rs b/bolt-sidecar/src/state/mod.rs index 7b17c67b..d8a127cf 100644 --- a/bolt-sidecar/src/state/mod.rs +++ b/bolt-sidecar/src/state/mod.rs @@ -342,4 +342,66 @@ mod tests { .transactions_len(); assert!(transactions_len == 0); } + + #[tokio::test] + async fn test_invalidate_stale_template() { + let _ = fmt::try_init(); + + let target_slot = 10; + + let anvil = launch_anvil(); + let client = StateClient::new(Url::parse(&anvil.endpoint()).unwrap()); + + let mut state = ExecutionState::new(client, NonZero::new(1024).expect("valid non-zero")) + .await + .unwrap(); + + let wallet: PrivateKeySigner = anvil.keys()[0].clone().into(); + + let sender = anvil.addresses()[0]; + + let tx = default_test_transaction(sender, None); + + let sig = wallet.sign_message_sync(&hex!("abcd")).unwrap(); + + let signer: EthereumWallet = wallet.into(); + let signed = tx.build(&signer).await.unwrap(); + + let bls_signer = Signer::random(); + + // Trick to parse into the TransactionSigned type + let tx_signed_bytes = signed.encoded_2718(); + let tx_signed = + PooledTransactionsElement::decode_enveloped(&mut tx_signed_bytes.as_slice()).unwrap(); + + let inclusion_request = InclusionRequest { + slot: target_slot, + tx: tx_signed, + signature: sig, + }; + + let request = CommitmentRequest::Inclusion(inclusion_request.clone()); + + assert!(state.validate_commitment_request(&request).await.is_ok()); + + let message = ConstraintsMessage::build(0, target_slot, inclusion_request, sender); + let signature = bls_signer.sign(&message.digest()).unwrap().to_string(); + let signed_constraints = SignedConstraints { message, signature }; + + state.add_constraint(target_slot, signed_constraints.clone()); + + assert!( + state + .block_templates() + .get(&target_slot) + .unwrap() + .transactions_len() + == 1 + ); + + // Update the head, which should invalidate the transaction due to a nonce conflict + state.update_head(None, target_slot).await.unwrap(); + + assert!(state.block_templates().get(&target_slot).is_none()); + } } From 0d30bea004295918a858223aeccd1f48da0c9d50 Mon Sep 17 00:00:00 2001 From: thedevbirb Date: Mon, 15 Jul 2024 10:22:45 +0200 Subject: [PATCH 5/6] chore(sidecar): ignore trace call test since it's slow and we're using trace calls in prod --- bolt-sidecar/src/builder/state_root.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bolt-sidecar/src/builder/state_root.rs b/bolt-sidecar/src/builder/state_root.rs index a23d93f6..4d7b4841 100644 --- a/bolt-sidecar/src/builder/state_root.rs +++ b/bolt-sidecar/src/builder/state_root.rs @@ -12,6 +12,7 @@ mod tests { use crate::{builder::CallTraceManager, client::rpc::RpcClient}; + #[ignore] #[tokio::test] async fn test_trace_call() -> eyre::Result<()> { dotenvy::dotenv().ok(); From 381ff109ac0f2077c09d7385684858de4d640f0e Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Mon, 15 Jul 2024 10:24:19 +0200 Subject: [PATCH 6/6] chore: addressed review comments --- bolt-sidecar/bin/sidecar.rs | 2 +- bolt-sidecar/src/builder/state_root.rs | 10 ++++++---- bolt-sidecar/src/builder/template.rs | 16 ++++++++-------- bolt-sidecar/src/client/rpc.rs | 7 +++++-- bolt-sidecar/src/primitives/constraint.rs | 2 +- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/bolt-sidecar/bin/sidecar.rs b/bolt-sidecar/bin/sidecar.rs index e2c0a55e..d83a97e8 100644 --- a/bolt-sidecar/bin/sidecar.rs +++ b/bolt-sidecar/bin/sidecar.rs @@ -136,7 +136,7 @@ async fn main() -> eyre::Result<()> { let max_retries = 5; let mut i = 0; 'inner: while let Err(e) = mevboost_client - .submit_constraints(&template.constraints) + .submit_constraints(&template.signed_constraints_list) .await { tracing::error!(err = ?e, "Error submitting constraints, retrying..."); diff --git a/bolt-sidecar/src/builder/state_root.rs b/bolt-sidecar/src/builder/state_root.rs index 4d7b4841..d2d1b23e 100644 --- a/bolt-sidecar/src/builder/state_root.rs +++ b/bolt-sidecar/src/builder/state_root.rs @@ -10,7 +10,9 @@ mod tests { use partial_mpt::StateTrie; use reqwest::Url; - use crate::{builder::CallTraceManager, client::rpc::RpcClient}; + use crate::{ + builder::CallTraceManager, client::rpc::RpcClient, test_util::try_get_execution_api_url, + }; #[ignore] #[tokio::test] @@ -18,14 +20,14 @@ mod tests { dotenvy::dotenv().ok(); let _ = tracing_subscriber::fmt::try_init(); - let Some(rpc_url) = std::env::var("RPC_URL").ok() else { - tracing::warn!("RPC_URL not found in environment variables, skipping test"); + let Some(rpc_url) = try_get_execution_api_url().await else { + tracing::warn!("EL_RPC not reachable, skipping test"); return Ok(()); }; tracing::info!("Starting test_trace_call"); - let rpc_url = Url::parse(&rpc_url).unwrap(); + let rpc_url = Url::parse(rpc_url).unwrap(); let client = RpcClient::new(rpc_url.clone()); let (call_trace_manager, call_trace_handler) = CallTraceManager::new(rpc_url); diff --git a/bolt-sidecar/src/builder/template.rs b/bolt-sidecar/src/builder/template.rs index 1973e84f..4c12701f 100644 --- a/bolt-sidecar/src/builder/template.rs +++ b/bolt-sidecar/src/builder/template.rs @@ -28,7 +28,7 @@ pub struct BlockTemplate { /// The state diffs per address given the list of commitments. state_diff: StateDiff, /// The signed constraints associated to the block - pub constraints: Vec, + pub signed_constraints_list: Vec, } impl BlockTemplate { @@ -40,7 +40,7 @@ impl BlockTemplate { /// Returns the cloned list of transactions from the constraints. #[inline] pub fn transactions(&self) -> Vec { - self.constraints + self.signed_constraints_list .iter() .flat_map(|sc| sc.message.constraints.iter().map(|c| c.transaction.clone())) .collect() @@ -50,7 +50,7 @@ impl BlockTemplate { /// a local execution payload. #[inline] pub fn as_signed_transactions(&self) -> Vec { - self.constraints + self.signed_constraints_list .iter() .flat_map(|sc| { sc.message @@ -64,7 +64,7 @@ impl BlockTemplate { /// Returns the length of the transactions in the block template. #[inline] pub fn transactions_len(&self) -> usize { - self.constraints + self.signed_constraints_list .iter() .fold(0, |acc, sc| acc + sc.message.constraints.len()) } @@ -72,7 +72,7 @@ impl BlockTemplate { /// Returns the blob count of the block template. #[inline] pub fn blob_count(&self) -> usize { - self.constraints.iter().fold(0, |mut acc, sc| { + self.signed_constraints_list.iter().fold(0, |mut acc, sc| { acc += sc.message.constraints.iter().fold(0, |acc, c| { acc + c .transaction @@ -99,12 +99,12 @@ impl BlockTemplate { .or_insert((1, max_cost)); } - self.constraints.push(constraints); + self.signed_constraints_list.push(constraints); } /// Remove all signed constraints at the specified index and updates the state diff fn remove_constraints_at_index(&mut self, index: usize) { - let constraints = self.constraints.remove(index); + let constraints = self.signed_constraints_list.remove(index); for constraint in constraints.message.constraints.iter() { self.state_diff @@ -124,7 +124,7 @@ impl BlockTemplate { // The pre-confirmations made by such address, and the indexes of the signed constraints // in which they appear let constraints_with_address: Vec<(usize, Vec<&Constraint>)> = self - .constraints + .signed_constraints_list .iter() .enumerate() .map(|(idx, c)| (idx, &c.message.constraints)) diff --git a/bolt-sidecar/src/client/rpc.rs b/bolt-sidecar/src/client/rpc.rs index 82116f16..67c51014 100644 --- a/bolt-sidecar/src/client/rpc.rs +++ b/bolt-sidecar/src/client/rpc.rs @@ -46,12 +46,15 @@ impl RpcClient { } /// Get the blob basefee of the latest block. + /// + /// Reference: https://github.com/ethereum/execution-apis/blob/main/src/eth/fee_market.yaml pub async fn get_blob_basefee(&self, block_number: Option) -> TransportResult { + let block_count = U64::from(1); let tag = block_number.map_or(BlockNumberOrTag::Latest, BlockNumberOrTag::Number); - + let reward_percentiles: Vec = vec![]; let fee_history: FeeHistory = self .0 - .request("eth_feeHistory", (U64::from(1), tag, &[] as &[f64])) + .request("eth_feeHistory", (block_count, tag, &reward_percentiles)) .await?; Ok(fee_history.latest_block_blob_base_fee().unwrap_or(0)) diff --git a/bolt-sidecar/src/primitives/constraint.rs b/bolt-sidecar/src/primitives/constraint.rs index 6cdfd285..5543c891 100644 --- a/bolt-sidecar/src/primitives/constraint.rs +++ b/bolt-sidecar/src/primitives/constraint.rs @@ -95,7 +95,7 @@ pub struct Constraint { } impl Constraint { - /// Builds a constraint from an inclusion request and an optional index + /// Builds a constraint from a transaction, with an optional index pub fn from_transaction( transaction: PooledTransactionsElement, index: Option,