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(ethereum) <- add eip4844 block header verification #19

Merged
merged 9 commits into from
Feb 19, 2024
37 changes: 37 additions & 0 deletions common/ethereum/src/eip_4844.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use derive_more::Constructor;

use crate::EthBlock;

#[derive(Clone, Constructor)]
pub struct Eip4844 {}

impl Eip4844 {
pub fn is_active(&self, block: &EthBlock) -> bool {
block.blob_gas_used.is_some()
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::get_sample_eip4844_sepolia_submission_material;

#[test]
fn eip4844_should_be_active() {
let eip_4844 = Eip4844::new();
let block = get_sample_eip4844_sepolia_submission_material();
let result = eip_4844.is_active(&block.block.unwrap());
assert!(result);
}

#[test]
fn eip_4844_should_not_be_active() {
gskapka marked this conversation as resolved.
Show resolved Hide resolved
let eip_4844 = Eip4844::new();

let block = get_sample_eip4844_sepolia_submission_material();
let mut block = block.block.unwrap();
block.blob_gas_used = None;
let result = eip_4844.is_active(&block);
assert!(!result);
}
}
98 changes: 97 additions & 1 deletion common/ethereum/src/eth_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use serde_json::{json, Value as JsonValue};

use crate::{
eip_1559::Eip1559,
eip_4844::Eip4844,
eth_utils::{
convert_dec_str_to_u256,
convert_hex_strings_to_h256s,
Expand Down Expand Up @@ -44,6 +45,13 @@ pub struct EthBlock {
pub transactions_root: EthHash,
pub uncles: Vec<EthHash>,
pub base_fee_per_gas: Option<U256>,
// NOTE: The following new fields are EIP-4844 specific and hence they are
// optional. Non EIP-4844 blocks or layer 2s or other forks may not have
// these fields.
pub withdrawals_root: Option<EthHash>,
pub blob_gas_used: Option<U256>,
pub excess_blob_gas: Option<U256>,
pub parent_beacon_block_root: Option<EthHash>,
gskapka marked this conversation as resolved.
Show resolved Hide resolved
}

impl EthBlock {
Expand All @@ -56,6 +64,26 @@ impl EthBlock {
.ok_or(NoneError("Could not unwrap 'base_fee' from ETH block!"))
}

pub fn get_withdrawals_root(&self) -> Result<EthHash> {
self.withdrawals_root
.ok_or(NoneError("Could not unwrap 'withdrawals_root' from ETH block!"))
}

pub fn get_blob_gas_used(&self) -> Result<U256> {
self.blob_gas_used
.ok_or(NoneError("Could not unwrap 'blob_gas_used' from ETH block!"))
}

pub fn get_excess_blob_gas(&self) -> Result<U256> {
self.excess_blob_gas
.ok_or(NoneError("Could not unwrap 'excess_blob_gas' from ETH block!"))
}

pub fn get_parent_beacon_block_root(&self) -> Result<EthHash> {
self.parent_beacon_block_root
.ok_or(NoneError("Could not unwrap 'parent_beacon_block_root' from ETH block!"))
}

pub fn to_json(&self) -> Result<JsonValue> {
let encoded_transactions = self
.transactions
Expand Down Expand Up @@ -92,6 +120,8 @@ impl EthBlock {
}

pub fn from_json(json: &EthBlockJson) -> Result<Self> {
let radix = 16;

Ok(EthBlock {
size: U256::from(json.size),
number: U256::from(json.number),
Expand All @@ -114,6 +144,22 @@ impl EthBlock {
total_difficulty: convert_dec_str_to_u256(&json.total_difficulty)?,
base_fee_per_gas: Self::parse_base_fee_per_gas(&json.base_fee_per_gas)?,
logs_bloom: Bloom::from_slice(&convert_hex_to_bytes(&json.logs_bloom)?[..]),
withdrawals_root: match json.withdrawals_root.as_ref() {
None => None,
Some(hex) => Some(convert_hex_to_h256(hex)?),
},
blob_gas_used: match json.blob_gas_used.as_ref() {
None => None,
Some(hex) => Some(U256::from_str_radix(&strip_hex_prefix(hex), radix)?),
},
excess_blob_gas: match json.excess_blob_gas.as_ref() {
None => None,
Some(hex) => Some(U256::from_str_radix(&strip_hex_prefix(hex), radix)?),
},
parent_beacon_block_root: match json.parent_beacon_block_root.as_ref() {
None => None,
Some(hex) => Some(convert_hex_to_h256(hex)?),
},
})
}

Expand All @@ -133,9 +179,18 @@ impl EthBlock {

pub fn rlp_encode(&self, chain_id: &EthChainId) -> Result<Bytes> {
let mut rlp_stream = RlpStream::new();
let mut num_items = 15;
let eip_1559_is_active = Eip1559::new().is_active(chain_id, self.number)?;
let eip_4844_is_active = Eip4844::new().is_active(self);

if eip_1559_is_active {
num_items += 1;
}
if eip_4844_is_active {
num_items += 4;
}
gskapka marked this conversation as resolved.
Show resolved Hide resolved
rlp_stream
.begin_list(if eip_1559_is_active { 16 } else { 15 })
.begin_list(num_items)
.append(&self.parent_hash)
.append(&self.sha3_uncles)
.append(&self.miner)
Expand All @@ -154,6 +209,13 @@ impl EthBlock {
if eip_1559_is_active {
rlp_stream.append(&self.get_base_fee_per_gas()?);
};
if eip_4844_is_active {
rlp_stream.append(&self.get_withdrawals_root()?);
rlp_stream.append(&self.get_blob_gas_used()?);
rlp_stream.append(&self.get_excess_blob_gas()?);
rlp_stream.append(&self.get_parent_beacon_block_root()?);
}

Ok(rlp_stream.out().to_vec())
}

Expand Down Expand Up @@ -195,6 +257,10 @@ pub struct EthBlockJson {
pub transactions_root: String,
pub uncles: Vec<String>,
pub base_fee_per_gas: Option<JsonValue>,
pub withdrawals_root: Option<String>,
pub blob_gas_used: Option<String>,
pub excess_blob_gas: Option<String>,
pub parent_beacon_block_root: Option<String>,
}

#[cfg(test)]
Expand All @@ -204,6 +270,7 @@ mod tests {
get_expected_block,
get_sample_eip1559_mainnet_submission_material,
get_sample_eip1559_ropsten_submission_material,
get_sample_eip4844_sepolia_submission_material,
get_sample_eth_submission_material,
get_sample_eth_submission_material_json,
get_sample_invalid_block,
Expand Down Expand Up @@ -354,4 +421,33 @@ mod tests {
assert!(r.is_err() || matches!(r, Ok(false)))
});
}

#[test]
fn eip_4844_block_should_have_blob_fields() {
let block = get_sample_eip4844_sepolia_submission_material().block.unwrap();
let blob_gas = block.blob_gas_used.unwrap();
let excess_blob_gas = block.excess_blob_gas.unwrap();
let expected_blob_gas = U256::from(786432);
let expected_excess_blob_gas = U256::from(79953920);
assert_eq!(blob_gas, expected_blob_gas);
assert_eq!(excess_blob_gas, expected_excess_blob_gas);
}

#[test]
fn sepolia_eip4844_block_should_be_valid() {
let block = get_sample_eip4844_sepolia_submission_material().block.unwrap();
let chain_id = EthChainId::Sepolia;
let result = block.is_valid(&chain_id).unwrap();
assert!(result);
}

#[test]
fn invalid_sepolia_eip4844_block_should_not_be_valid() {
let mut block = get_sample_eip4844_sepolia_submission_material().block.unwrap();
// NOTE: Alter the new EIP4844 block header additional field to render the block invalid.
block.blob_gas_used = Some(block.blob_gas_used.unwrap() - 1);
let chain_id = EthChainId::Sepolia;
let result = block.is_valid(&chain_id).unwrap();
assert!(!result);
}
gskapka marked this conversation as resolved.
Show resolved Hide resolved
}
20 changes: 20 additions & 0 deletions common/ethereum/src/eth_block_from_json_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub struct EthBlockJsonFromRpc {
transactions_root: String,
transactions: Vec<String>,
base_fee_per_gas: Option<String>,
withdrawals_root: Option<String>,
blob_gas_used: Option<String>,
excess_blob_gas: Option<String>,
parent_beacon_block_root: Option<String>,
}

impl EthSubmissionMaterial {
Expand Down Expand Up @@ -91,6 +95,22 @@ impl EthBlock {
None => None,
Some(hex) => Some(U256::from_str_radix(&strip_hex_prefix(hex), radix)?),
},
withdrawals_root: match json.withdrawals_root.as_ref() {
None => None,
Some(hex) => Some(convert_hex_to_h256(hex)?),
},
blob_gas_used: match json.blob_gas_used.as_ref() {
None => None,
Some(hex) => Some(U256::from_str_radix(&strip_hex_prefix(hex), radix)?),
},
excess_blob_gas: match json.excess_blob_gas.as_ref() {
None => None,
Some(hex) => Some(U256::from_str_radix(&strip_hex_prefix(hex), radix)?),
},
parent_beacon_block_root: match json.parent_beacon_block_root.as_ref() {
None => None,
Some(hex) => Some(convert_hex_to_h256(hex)?),
},
})
}
}
Expand Down
1 change: 1 addition & 0 deletions common/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod check_parent_exists;
mod core_initialization;
mod default_block_parameter;
mod eip_1559;
mod eip_4844;
mod eth_block;
mod eth_block_from_json_rpc;
mod eth_constants;
Expand Down
7 changes: 7 additions & 0 deletions common/ethereum/src/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ pub const SAMPLE_BLOCK_AND_RECEIPT_JSON_18: &str = "src/test_utils/rpc-block-0xf

pub const SAMPLE_BLOCK_AND_RECEIPT_JSON_19: &str = "src/test_utils/host-sub-mat-num-16640614.json";

pub const SAMPLE_BLOCK_AND_RECEIPT_JSON_20: &str = "src/test_utils/sepolia-sub-mat-block-5301643-with-eip-4844.json";

pub fn get_sample_block_from_rpc() -> String {
get_sample_eth_submission_material_string(18).unwrap()
}
Expand Down Expand Up @@ -149,6 +151,7 @@ pub fn get_sample_eth_submission_material_string(num: usize) -> Result<String> {
17 => Ok(SAMPLE_BLOCK_AND_RECEIPT_JSON_17),
18 => Ok(SAMPLE_BLOCK_AND_RECEIPT_JSON_18),
19 => Ok(SAMPLE_BLOCK_AND_RECEIPT_JSON_19),
20 => Ok(SAMPLE_BLOCK_AND_RECEIPT_JSON_20),
_ => Err(AppError::Custom(format!("Cannot find sample block num: {}", num))),
}?;
match Path::new(&path).exists() {
Expand Down Expand Up @@ -181,6 +184,10 @@ pub fn get_sample_eip1559_mainnet_submission_material() -> EthSubmissionMaterial
get_sample_eth_submission_material_n(12).unwrap()
}

pub fn get_sample_eip4844_sepolia_submission_material() -> EthSubmissionMaterial {
get_sample_eth_submission_material_n(20).unwrap()
}

pub fn get_sample_receipt_n(sample_block_num: usize, receipt_index: usize) -> Result<EthReceipt> {
get_sample_eth_submission_material_n(sample_block_num).map(|block| block.receipts.0[receipt_index].clone())
}
Expand Down

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions common/sentinel/src/eth_rpc_calls/get_quicknode_sub_mat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ pub struct QuicknodeBlockFromRpc {
transactions_root: String,
base_fee_per_gas: Option<String>,
transactions: Vec<EthReceiptJsonFromRpc>,
withdrawals_root: Option<String>,
blob_gas_used: Option<String>,
excess_blob_gas: Option<String>,
parent_beacon_block_root: Option<String>,
}

impl TryFrom<QuicknodeBlockFromRpc> for EthBlock {
Expand Down Expand Up @@ -81,6 +85,22 @@ impl TryFrom<QuicknodeBlockFromRpc> for EthBlock {
None => None,
Some(hex) => Some(U256::from_str_radix(&strip_hex_prefix(hex), radix)?),
},
withdrawals_root: match json.withdrawals_root.as_ref() {
None => None,
Some(hex) => Some(convert_hex_to_h256(hex)?),
},
blob_gas_used: match json.blob_gas_used.as_ref() {
None => None,
Some(hex) => Some(U256::from_str_radix(&strip_hex_prefix(hex), radix)?),
},
excess_blob_gas: match json.excess_blob_gas.as_ref() {
None => None,
Some(hex) => Some(U256::from_str_radix(&strip_hex_prefix(hex), radix)?),
},
parent_beacon_block_root: match json.parent_beacon_block_root.as_ref() {
None => None,
Some(hex) => Some(convert_hex_to_h256(hex)?),
},
})
}
}
Expand Down
Loading