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

feat: expand forkChoiceUpdatedV3 with basic block building #540

Merged
merged 24 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is what the spec says? not against it, just curious

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I took it from geth. The spec says 8 Bytes - identifier of the payload build process

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
Loading