Skip to content

Commit

Permalink
feat: expand forkChoiceUpdatedV3 with basic block building (#540)
Browse files Browse the repository at this point in the history
**Motivation**

Add basic block building to forkChoiceUpdatedV3

<!-- Why does this pull request exist? What are its goals? -->

**Description**

* Expand engine_forkChoiceUpdated:
   - Add validations
   - Fix fork state update conditions
   - Add basic block building

With these changes hive engine tests no longer fail after calling
engine_forkChoiceUpdatedV3. We still need to implement engine_getPayload
to progress further
NOTE: Some fixes over this code have been included in #571 (Computing
the state root for newly built payloads instead of using the parent
state root)
<!-- A clear and concise general description of the changes this PR
introduces -->

<!-- Link to issues: Resolves #111, Resolves #222 -->

Closes None, is part of #344
  • Loading branch information
fmoletta authored Sep 27, 2024
1 parent 21bff69 commit 0270aef
Show file tree
Hide file tree
Showing 15 changed files with 394 additions and 83 deletions.
2 changes: 2 additions & 0 deletions crates/blockchain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ edition = "2021"
[dependencies]
thiserror.workspace = true
ethereum_rust-core.workspace = true
ethereum_rust-rlp.workspace = true
ethereum_rust-storage.workspace = true
ethereum_rust-evm.workspace = true
sha3.workspace = true


[lib]
Expand Down
2 changes: 2 additions & 0 deletions crates/blockchain/blockchain.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod constants;
pub mod error;
pub mod mempool;
pub mod payload;

use constants::{GAS_PER_BLOB, MAX_BLOB_GAS_PER_BLOCK, MAX_BLOB_NUMBER_PER_BLOCK};
use error::{ChainError, InvalidBlockError};
use ethereum_rust_core::types::{
Expand Down
4 changes: 4 additions & 0 deletions crates/blockchain/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,7 @@ pub const MAX_BLOB_GAS_PER_BLOCK: u64 = MAX_BLOB_NUMBER_PER_BLOCK * GAS_PER_BLOB

// Minimum base fee per blob
pub const MIN_BASE_FEE_PER_BLOB_GAS: u64 = 1;

pub const GAS_LIMIT_BOUND_DIVISOR: u64 = 1024;

pub const MIN_GAS_LIMIT: u64 = 5000;
129 changes: 129 additions & 0 deletions crates/blockchain/payload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use std::cmp::min;

use ethereum_rust_core::{
types::{
calculate_base_fee_per_gas, compute_receipts_root, compute_transactions_root,
compute_withdrawals_root, Block, BlockBody, BlockHash, BlockHeader, Withdrawal,
DEFAULT_OMMERS_HASH,
},
Address, Bloom, Bytes, H256, U256,
};
use ethereum_rust_rlp::encode::RLPEncode;
use ethereum_rust_storage::{error::StoreError, Store};
use sha3::{Digest, Keccak256};

use crate::constants::{GAS_LIMIT_BOUND_DIVISOR, MIN_GAS_LIMIT, TARGET_BLOB_GAS_PER_BLOCK};

pub struct BuildPayloadArgs {
pub parent: BlockHash,
pub timestamp: u64,
pub fee_recipient: Address,
pub random: H256,
pub withdrawals: Vec<Withdrawal>,
pub beacon_root: Option<H256>,
pub version: u8,
}

impl BuildPayloadArgs {
/// Computes an 8-byte identifier by hashing the components of the payload arguments.
pub fn id(&self) -> u64 {
let mut hasher = Keccak256::new();
hasher.update(self.parent);
hasher.update(self.timestamp.to_be_bytes());
hasher.update(self.random);
hasher.update(self.fee_recipient);
hasher.update(self.withdrawals.encode_to_vec());
if let Some(beacon_root) = self.beacon_root {
hasher.update(beacon_root);
}
let res = &mut hasher.finalize()[..8];
res[0] = self.version;
u64::from_be_bytes(res.try_into().unwrap())
}
}

/// Builds a new payload based on the payload arguments
// Basic payload block building, can and should be improved
pub fn build_payload(args: &BuildPayloadArgs, storage: &Store) -> Result<Block, StoreError> {
// TODO: check where we should get builder values from
const DEFAULT_BUILDER_GAS_CEIL: u64 = 30_000_000;
// Presence of a parent block should have been checked or guaranteed before calling this function
// So we can treat a missing parent block as an internal storage error
let parent_block = storage
.get_block_header_by_hash(args.parent)?
.ok_or_else(|| StoreError::Custom("unexpected missing parent block".to_string()))?;
let chain_config = storage.get_chain_config()?;
let gas_limit = calc_gas_limit(parent_block.gas_limit, DEFAULT_BUILDER_GAS_CEIL);
Ok(Block {
header: BlockHeader {
parent_hash: args.parent,
ommers_hash: *DEFAULT_OMMERS_HASH,
coinbase: args.fee_recipient,
state_root: parent_block.state_root,
transactions_root: compute_transactions_root(&[]),
receipts_root: compute_receipts_root(&[]),
logs_bloom: Bloom::default(),
difficulty: U256::zero(),
number: parent_block.number.saturating_add(1),
gas_limit,
gas_used: 0,
timestamp: args.timestamp,
// TODO: should use builder config's extra_data
extra_data: Bytes::new(),
prev_randao: args.random,
nonce: 0,
base_fee_per_gas: calculate_base_fee_per_gas(
gas_limit,
parent_block.gas_limit,
parent_block.gas_used,
parent_block.base_fee_per_gas.unwrap_or_default(),
),
withdrawals_root: chain_config
.is_shanghai_activated(args.timestamp)
.then_some(compute_withdrawals_root(&args.withdrawals)),
blob_gas_used: Some(0),
excess_blob_gas: chain_config.is_cancun_activated(args.timestamp).then_some(
calc_excess_blob_gas(
parent_block.excess_blob_gas.unwrap_or_default(),
parent_block.blob_gas_used.unwrap_or_default(),
),
),
parent_beacon_block_root: args.beacon_root,
},
// Empty body as we just created this payload
body: BlockBody {
transactions: Vec::new(),
ommers: Vec::new(),
withdrawals: Some(args.withdrawals.clone()),
},
})
}

fn calc_gas_limit(parent_gas_limit: u64, desired_limit: u64) -> u64 {
let delta = parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR - 1;
let mut limit = parent_gas_limit;
let desired_limit = min(desired_limit, MIN_GAS_LIMIT);
if limit < desired_limit {
limit = parent_gas_limit + delta;
if limit > desired_limit {
limit = desired_limit
}
return limit;
}
if limit > desired_limit {
limit = parent_gas_limit - delta;
if limit < desired_limit {
limit = desired_limit
}
}
limit
}

fn calc_excess_blob_gas(parent_excess_blob_gas: u64, parent_blob_gas_used: u64) -> u64 {
let excess_blob_gas = parent_excess_blob_gas + parent_blob_gas_used;
if excess_blob_gas < TARGET_BLOB_GAS_PER_BLOCK {
0
} else {
excess_blob_gas - TARGET_BLOB_GAS_PER_BLOCK
}
}
24 changes: 20 additions & 4 deletions crates/core/serde_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,7 @@ pub mod u64 {
where
D: Deserializer<'de>,
{
let value = String::deserialize(d)?;
let res = u64::from_str_radix(value.trim_start_matches("0x"), 16)
.map_err(|_| D::Error::custom("Failed to deserialize u64 value"));
res
super::hex_str::deserialize(d)
}

pub fn serialize<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
Expand Down Expand Up @@ -139,6 +136,25 @@ pub mod u64 {
}
}

pub mod hex_str_opt_padded {
use serde::Serialize;

use super::*;
pub fn serialize<S>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Option::<String>::serialize(&value.map(|v| format!("{:#018x}", v)), serializer)
}

pub fn deserialize<'de, D>(d: D) -> Result<Option<u64>, D::Error>
where
D: Deserializer<'de>,
{
super::hex_str_opt::deserialize(d)
}
}

pub fn deser_dec_str<'de, D>(d: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
Expand Down
2 changes: 1 addition & 1 deletion crates/core/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ fn fake_exponential(factor: u64, numerator: u64, denominator: u64) -> u64 {

// Calculates the base fee for the current block based on its gas_limit and parent's gas and fee
// Returns None if the block gas limit is not valid in relation to its parent's gas limit
fn calculate_base_fee_per_gas(
pub fn calculate_base_fee_per_gas(
block_gas_limit: u64,
parent_gas_limit: u64,
parent_gas_used: u64,
Expand Down
Loading

0 comments on commit 0270aef

Please sign in to comment.