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(l1): added support for forkchoiceUpdatedV1, getPayloadV1 and newPayloadV1 #1510

Merged
merged 10 commits into from
Dec 17, 2024
5 changes: 4 additions & 1 deletion .github/workflows/ci_l1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ jobs:
test_pattern: engine-(auth|exchange-capabilities)/
- name: "Cancun Engine tests"
simulation: ethereum/engine
test_pattern: "engine-cancun/Blob Transactions On Block 1|Blob Transaction Ordering, Single|Blob Transaction Ordering, Multiple Accounts|Replace Blob Transactions|Parallel Blob Transactions|ForkchoiceUpdated|GetPayload|NewPayloadV3 After Cancun|NewPayloadV3 Before Cancun|NewPayloadV3 Versioned Hashes|Incorrect BlobGasUsed|Bad Hash|ParentHash equals BlockHash|RPC:|in ForkchoiceState|Unknown|Invalid PayloadAttributes|Unique|Re-Execute Payload|In-Order Consecutive Payload|Multiple New Payloads|Valid NewPayload->|NewPayload with|Payload Build after|Build Payload with|Invalid Missing Ancestor ReOrg, StateRoot|Re-Org Back to|Re-org to Previously|Safe Re-Org to Side Chain|Transaction Re-Org, Re-Org Back In|Re-Org Back into Canonical Chain, Depth=5|Suggested Fee Recipient Test|PrevRandao Opcode|Invalid NewPayload|Fork ID: Genesis=0, Cancun=0|Fork ID: Genesis=0, Cancun=1|Fork ID: Genesis=0, Cancun=2 |Fork ID: Genesis=0, Cancun=2, BlocksBeforePeering=1|Fork ID: Genesis=0, Cancun=2, Shanghai=[^2]|Fork ID Genesis=1, Cancun=2, Shanghai=2"
test_pattern: "engine-cancun/Blob Transactions On Block 1|Blob Transaction Ordering, Single|Blob Transaction Ordering, Multiple Accounts|Replace Blob Transactions|Parallel Blob Transactions|ForkchoiceUpdated|GetPayload|NewPayloadV3 After Cancun|NewPayloadV3 Before Cancun|NewPayloadV3 Versioned Hashes|Incorrect BlobGasUsed|Bad Hash|ParentHash equals BlockHash|RPC:|in ForkchoiceState|Unknown|Invalid PayloadAttributes|Unique|Re-Execute Payload|In-Order Consecutive Payload|Multiple New Payloads|Valid NewPayload->|NewPayload with|Payload Build after|Build Payload with|Invalid Missing Ancestor ReOrg, StateRoot|Re-Org Back to|Re-org to Previously|Safe Re-Org to Side Chain|Transaction Re-Org|Re-Org Back into Canonical Chain, Depth=5|Suggested Fee Recipient Test|PrevRandao Opcode|Invalid NewPayload|Fork ID: Genesis=0|Fork ID: Genesis=1, Cancun=0|Fork ID: Genesis=1, Cancun=2 |Fork ID: Genesis=1, Cancun=2, BlocksBeforePeering=1|Fork ID: Genesis=1, Cancun=2, Shanghai=[^1]|Pre-Merge"
- name: "Paris Engine tests"
simulation: ethereum/engine
test_pattern: "engine-api/RPC|Re-Org Back to Canonical Chain From Syncing Chain|Re-org to Previously Validated Sidechain Payload|Re-Org Back into Canonical Chain, Depth=5|Safe Re-Org|Transaction Re-Org|Inconsistent|Suggested Fee|PrevRandao|Fork ID|Unknown|Invalid PayloadAttributes|Bad Hash|Unique Payload ID|Re-Execute Payload|In-Order|Multiple New Payloads|Valid NewPayload|NewPayload with|Invalid NewPayload|Payload Build|Invalid NewPayload, Transaction|ParentHash equals|Build Payload|Invalid Missing Ancestor ReOrg"
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
Expand Down
12 changes: 8 additions & 4 deletions crates/blockchain/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub struct BuildPayloadArgs {
pub timestamp: u64,
pub fee_recipient: Address,
pub random: H256,
pub withdrawals: Vec<Withdrawal>,
pub withdrawals: Option<Vec<Withdrawal>>,
pub beacon_root: Option<H256>,
pub version: u8,
}
Expand All @@ -49,7 +49,9 @@ impl BuildPayloadArgs {
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(withdrawals) = &self.withdrawals {
hasher.update(withdrawals.encode_to_vec());
}
if let Some(beacon_root) = self.beacon_root {
hasher.update(beacon_root);
}
Expand Down Expand Up @@ -95,7 +97,9 @@ pub fn create_payload(args: &BuildPayloadArgs, storage: &Store) -> Result<Block,
),
withdrawals_root: chain_config
.is_shanghai_activated(args.timestamp)
.then_some(compute_withdrawals_root(&args.withdrawals)),
.then_some(compute_withdrawals_root(
args.withdrawals.as_ref().unwrap_or(&Vec::new()),
)),
blob_gas_used: chain_config
.is_cancun_activated(args.timestamp)
.then_some(0),
Expand All @@ -111,7 +115,7 @@ pub fn create_payload(args: &BuildPayloadArgs, storage: &Store) -> Result<Block,
let body = BlockBody {
transactions: Vec::new(),
ommers: Vec::new(),
withdrawals: Some(args.withdrawals.clone()),
withdrawals: args.withdrawals.clone(),
};

// Delay applying withdrawals until the payload is requested and built
Expand Down
2 changes: 1 addition & 1 deletion crates/blockchain/smoke_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ mod blockchain_integration_test {
timestamp: parent.timestamp + 12,
fee_recipient: H160::random(),
random: H256::random(),
withdrawals: Vec::new(),
withdrawals: Some(Vec::new()),
beacon_root: Some(H256::random()),
version: 1,
};
Expand Down
83 changes: 60 additions & 23 deletions crates/networking/rpc/engine/fork_choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,35 @@ use crate::{
RpcApiContext, RpcErr, RpcHandler,
};

#[derive(Debug)]
pub struct ForkChoiceUpdatedV1 {
pub fork_choice_state: ForkChoiceState,
pub payload_attributes: Option<PayloadAttributesV3>,
}

impl RpcHandler for ForkChoiceUpdatedV1 {
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
let (fork_choice_state, payload_attributes) = parse(params)?;

Ok(ForkChoiceUpdatedV1 {
fork_choice_state,
payload_attributes,
})
}

fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
let (head_block_opt, mut response) =
handle_forkchoice(&self.fork_choice_state, context.clone(), 1)?;
if let (Some(head_block), Some(attributes)) = (head_block_opt, &self.payload_attributes) {
validate_v1(attributes, head_block)?;
let payload_id = build_payload(attributes, context, &self.fork_choice_state, 1)?;
response.set_id(payload_id);
}

serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
}
}

#[derive(Debug)]
pub struct ForkChoiceUpdatedV2 {
pub fork_choice_state: ForkChoiceState,
Expand Down Expand Up @@ -101,10 +130,12 @@ fn parse(
let forkchoice_state: ForkChoiceState = serde_json::from_value(params[0].clone())?;
// if there is an error when parsing, set to None
let payload_attributes: Option<PayloadAttributesV3> =
if let Ok(attributes) = serde_json::from_value::<PayloadAttributesV3>(params[1].clone()) {
Some(attributes)
} else {
None
match serde_json::from_value::<PayloadAttributesV3>(params[1].clone()) {
Ok(attributes) => Some(attributes),
Err(error) => {
info!("Could not parse params {}", error);
None
}
};

Ok((forkchoice_state, payload_attributes))
Expand Down Expand Up @@ -173,51 +204,57 @@ fn handle_forkchoice(
}
}

fn validate_v3(
fn validate_v1(attributes: &PayloadAttributesV3, head_block: BlockHeader) -> Result<(), RpcErr> {
validate_timestamp(attributes, head_block)
}

fn validate_v2(
attributes: &PayloadAttributesV3,
head_block: BlockHeader,
context: &RpcApiContext,
) -> Result<(), RpcErr> {
let chain_config = context.storage.get_chain_config()?;
if attributes.parent_beacon_block_root.is_none() {
if attributes.parent_beacon_block_root.is_some() {
return Err(RpcErr::InvalidPayloadAttributes(
"Null Parent Beacon Root".to_string(),
"forkChoiceV2 with Beacon Root".to_string(),
));
}
if !chain_config.is_cancun_activated(attributes.timestamp) {
if !chain_config.is_shanghai_activated(attributes.timestamp) {
return Err(RpcErr::UnsuportedFork(
"forkChoiceV3 used to build pre-Cancun payload".to_string(),
"forkChoiceV2 used to build pre-Shanghai payload".to_string(),
));
}
if attributes.timestamp <= head_block.timestamp {
return Err(RpcErr::InvalidPayloadAttributes(
"invalid timestamp".to_string(),
if chain_config.is_cancun_activated(attributes.timestamp) {
return Err(RpcErr::UnsuportedFork(
"forkChoiceV2 used to build Cancun payload".to_string(),
));
}
Ok(())
validate_timestamp(attributes, head_block)
}

fn validate_v2(
fn validate_v3(
attributes: &PayloadAttributesV3,
head_block: BlockHeader,
context: &RpcApiContext,
) -> Result<(), RpcErr> {
let chain_config = context.storage.get_chain_config()?;
if attributes.parent_beacon_block_root.is_some() {
if attributes.parent_beacon_block_root.is_none() {
return Err(RpcErr::InvalidPayloadAttributes(
"forkChoiceV2 with Beacon Root".to_string(),
));
}
if !chain_config.is_shanghai_activated(attributes.timestamp) {
return Err(RpcErr::UnsuportedFork(
"forkChoiceV2 used to build pre-Shanghai payload".to_string(),
"Null Parent Beacon Root".to_string(),
));
}
if chain_config.is_cancun_activated(attributes.timestamp) {
if !chain_config.is_cancun_activated(attributes.timestamp) {
return Err(RpcErr::UnsuportedFork(
"forkChoiceV2 used to build Cancun payload".to_string(),
"forkChoiceV3 used to build pre-Cancun payload".to_string(),
));
}
validate_timestamp(attributes, head_block)
}

fn validate_timestamp(
attributes: &PayloadAttributesV3,
head_block: BlockHeader,
) -> Result<(), RpcErr> {
if attributes.timestamp <= head_block.timestamp {
return Err(RpcErr::InvalidPayloadAttributes(
"invalid timestamp".to_string(),
Expand Down
84 changes: 66 additions & 18 deletions crates/networking/rpc/engine/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,36 @@ use crate::types::payload::{ExecutionPayload, ExecutionPayloadResponse, PayloadS
use crate::utils::RpcRequest;
use crate::{RpcApiContext, RpcErr, RpcHandler};

// NewPayload V1-V2-V3 implementations
pub struct NewPayloadV1Request {
pub payload: ExecutionPayload,
}

impl RpcHandler for NewPayloadV1Request {
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
Ok(NewPayloadV1Request {
payload: parse_execution_payload(params)?,
})
}

fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
handle_new_payload_v1_v2(&self.payload, Fork::Paris, context)
}
}

pub struct NewPayloadV2Request {
pub payload: ExecutionPayload,
}

impl RpcHandler for NewPayloadV2Request {
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
let params = params
.as_ref()
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
if params.len() != 1 {
return Err(RpcErr::BadParams("Expected 1 params".to_owned()));
}
Ok(NewPayloadV2Request {
payload: serde_json::from_value(params[0].clone())
.map_err(|_| RpcErr::WrongParam("payload".to_string()))?,
payload: parse_execution_payload(params)?,
})
}

fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
let block = get_block_from_payload(&self.payload, None)?;
validate_fork(&block, Fork::Shanghai, &context)?;
let payload_status = {
if let Err(RpcErr::Internal(error_msg)) = validate_block_hash(&self.payload, &block) {
PayloadStatus::invalid_with_err(&error_msg)
} else {
execute_payload(&block, &context)?
}
};
serde_json::to_value(payload_status).map_err(|error| RpcErr::Internal(error.to_string()))
handle_new_payload_v1_v2(&self.payload, Fork::Shanghai, context)
}
}

Expand Down Expand Up @@ -106,6 +107,25 @@ impl RpcHandler for NewPayloadV3Request {
}
}

// GetPayload V1-V2-V3 implementations
pub struct GetPayloadV1Request {
pub payload_id: u64,
}

impl RpcHandler for GetPayloadV1Request {
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
let payload_id = parse_get_payload_request(params)?;
Ok(Self { payload_id })
}

fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
let execution_payload_response =
build_execution_payload_response(self.payload_id, Fork::Paris, None, context)?;
serde_json::to_value(execution_payload_response.execution_payload)
.map_err(|error| RpcErr::Internal(error.to_string()))
}
}

pub struct GetPayloadV2Request {
pub payload_id: u64,
}
Expand Down Expand Up @@ -152,6 +172,33 @@ impl RpcHandler for GetPayloadV3Request {
}
}

fn parse_execution_payload(params: &Option<Vec<Value>>) -> Result<ExecutionPayload, RpcErr> {
let params = params
.as_ref()
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
if params.len() != 1 {
return Err(RpcErr::BadParams("Expected 1 params".to_owned()));
}
serde_json::from_value(params[0].clone()).map_err(|_| RpcErr::WrongParam("payload".to_string()))
}

fn handle_new_payload_v1_v2(
payload: &ExecutionPayload,
fork: Fork,
context: RpcApiContext,
) -> Result<Value, RpcErr> {
let block = get_block_from_payload(payload, None)?;
validate_fork(&block, fork, &context)?;
let payload_status = {
if let Err(RpcErr::Internal(error_msg)) = validate_block_hash(payload, &block) {
PayloadStatus::invalid_with_err(&error_msg)
} else {
execute_payload(&block, &context)?
}
};
serde_json::to_value(payload_status).map_err(|error| RpcErr::Internal(error.to_string()))
}

fn validate_execution_payload_v3(payload: &ExecutionPayload) -> Result<(), RpcErr> {
if payload.excess_blob_gas.is_none() {
return Err(RpcErr::WrongParam("excess_blob_gas".to_string()));
Expand Down Expand Up @@ -291,6 +338,7 @@ fn build_execution_payload_response(
) -> Result<ExecutionPayloadResponse, RpcErr> {
let (mut payload_block, block_value, blobs_bundle, completed) =
get_payload(payload_id, &context)?;

validate_fork(&payload_block, fork, &context)?;

if completed {
Expand Down
10 changes: 8 additions & 2 deletions crates/networking/rpc/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ use axum_extra::{
use bytes::Bytes;
use engine::{
exchange_transition_config::ExchangeTransitionConfigV1Req,
fork_choice::{ForkChoiceUpdatedV2, ForkChoiceUpdatedV3},
payload::{GetPayloadV2Request, GetPayloadV3Request, NewPayloadV2Request, NewPayloadV3Request},
fork_choice::{ForkChoiceUpdatedV1, ForkChoiceUpdatedV2, ForkChoiceUpdatedV3},
payload::{
GetPayloadV1Request, GetPayloadV2Request, GetPayloadV3Request, NewPayloadV1Request,
NewPayloadV2Request, NewPayloadV3Request,
},
ExchangeCapabilitiesRequest,
};
use eth::{
Expand Down Expand Up @@ -255,15 +258,18 @@ pub fn map_debug_requests(req: &RpcRequest, context: RpcApiContext) -> Result<Va
pub fn map_engine_requests(req: &RpcRequest, context: RpcApiContext) -> Result<Value, RpcErr> {
match req.method.as_str() {
"engine_exchangeCapabilities" => ExchangeCapabilitiesRequest::call(req, context),
"engine_forkchoiceUpdatedV1" => ForkChoiceUpdatedV1::call(req, context),
"engine_forkchoiceUpdatedV2" => ForkChoiceUpdatedV2::call(req, context),
"engine_forkchoiceUpdatedV3" => ForkChoiceUpdatedV3::call(req, context),
"engine_newPayloadV3" => NewPayloadV3Request::call(req, context),
"engine_newPayloadV2" => NewPayloadV2Request::call(req, context),
"engine_newPayloadV1" => NewPayloadV1Request::call(req, context),
"engine_exchangeTransitionConfigurationV1" => {
ExchangeTransitionConfigV1Req::call(req, context)
}
"engine_getPayloadV3" => GetPayloadV3Request::call(req, context),
"engine_getPayloadV2" => GetPayloadV2Request::call(req, context),
"engine_getPayloadV1" => GetPayloadV1Request::call(req, context),
unknown_engine_method => Err(RpcErr::MethodNotFound(unknown_engine_method.to_owned())),
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/networking/rpc/types/fork_choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct PayloadAttributesV3 {
pub timestamp: u64,
pub prev_randao: H256,
pub suggested_fee_recipient: Address,
pub withdrawals: Vec<Withdrawal>,
pub withdrawals: Option<Vec<Withdrawal>>,
pub parent_beacon_block_root: Option<H256>,
}

Expand Down
14 changes: 8 additions & 6 deletions crates/networking/rpc/types/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ pub struct ExecutionPayload {
base_fee_per_gas: u64,
pub block_hash: H256,
transactions: Vec<EncodedTransaction>,
withdrawals: Vec<Withdrawal>,
#[serde(skip_serializing_if = "Option::is_none", default)]
withdrawals: Option<Vec<Withdrawal>>,
// ExecutionPayloadV3 fields. Optional since we support V2 too
#[serde(
skip_serializing_if = "Option::is_none",
Expand Down Expand Up @@ -101,7 +102,7 @@ impl ExecutionPayload {
.map(|encoded_tx| encoded_tx.decode())
.collect::<Result<Vec<_>, RLPDecodeError>>()?,
ommers: vec![],
withdrawals: Some(self.withdrawals),
withdrawals: self.withdrawals,
};

let header = BlockHeader {
Expand All @@ -121,9 +122,10 @@ impl ExecutionPayload {
prev_randao: self.prev_randao,
nonce: 0,
base_fee_per_gas: Some(self.base_fee_per_gas),
withdrawals_root: Some(compute_withdrawals_root(
&body.withdrawals.clone().unwrap_or_default(),
)),
withdrawals_root: body
.withdrawals
.clone()
.map(|w| compute_withdrawals_root(&w)),
blob_gas_used: self.blob_gas_used,
excess_blob_gas: self.excess_blob_gas,
parent_beacon_block_root,
Expand Down Expand Up @@ -153,7 +155,7 @@ impl ExecutionPayload {
.iter()
.map(EncodedTransaction::encode)
.collect(),
withdrawals: block.body.withdrawals.unwrap_or_default(),
withdrawals: block.body.withdrawals,
blob_gas_used: block.header.blob_gas_used,
excess_blob_gas: block.header.excess_blob_gas,
}
Expand Down
Loading