-
Notifications
You must be signed in to change notification settings - Fork 30
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): implement forkchoiceupdatedv2, newpayloadv2 and getpayloadv2 #1297
Changes from all commits
a969b82
b1e0ad1
4256146
945751a
9684401
7a6c827
85f1359
5a50dde
5e4030c
7c47aa5
5e881db
2ff0a85
18b6972
9f0e5d4
009cbfc
d1011f8
9ff70ff
9a9c43b
a821eeb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,72 +4,74 @@ use ethrex_blockchain::{ | |
latest_canonical_block_hash, | ||
payload::{create_payload, BuildPayloadArgs}, | ||
}; | ||
use ethrex_core::types::{BlockHeader, ChainConfig}; | ||
use serde_json::Value; | ||
use tracing::{info, warn}; | ||
|
||
use crate::{ | ||
types::{ | ||
fork_choice::{ForkChoiceResponse, ForkChoiceState, PayloadAttributesV3}, | ||
fork_choice::{ForkChoiceResponse, ForkChoiceState, PayloadAttributes}, | ||
payload::PayloadStatus, | ||
}, | ||
utils::RpcRequest, | ||
RpcApiContext, RpcErr, RpcHandler, | ||
}; | ||
|
||
#[derive(Debug)] | ||
pub struct ForkChoiceUpdatedV3 { | ||
pub struct ForkChoiceUpdated { | ||
pub fork_choice_state: ForkChoiceState, | ||
#[allow(unused)] | ||
pub payload_attributes: Result<Option<PayloadAttributesV3>, String>, | ||
pub payload_attributes: Result<Option<PayloadAttributes>, String>, | ||
} | ||
|
||
impl TryFrom<ForkChoiceUpdatedV3> for RpcRequest { | ||
type Error = String; | ||
|
||
fn try_from(val: ForkChoiceUpdatedV3) -> Result<Self, Self::Error> { | ||
match val.payload_attributes { | ||
Ok(attrs) => Ok(RpcRequest { | ||
method: "engine_forkchoiceUpdatedV3".to_string(), | ||
params: Some(vec![ | ||
serde_json::json!(val.fork_choice_state), | ||
serde_json::json!(attrs), | ||
]), | ||
..Default::default() | ||
}), | ||
Err(err) => Err(err), | ||
} | ||
} | ||
} | ||
|
||
impl RpcHandler for ForkChoiceUpdatedV3 { | ||
// TODO(#853): Allow fork choice to be executed even if fork choice updated v3 was not correctly parsed. | ||
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> { | ||
impl ForkChoiceUpdated { | ||
// TODO(#853): Allow fork choice to be executed even if fork choice updated was not correctly parsed. | ||
fn parse(params: &Option<Vec<Value>>) -> Result<ForkChoiceUpdated, RpcErr> { | ||
let params = params | ||
.as_ref() | ||
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?; | ||
if params.len() != 2 { | ||
return Err(RpcErr::BadParams("Expected 2 params".to_owned())); | ||
} | ||
Ok(ForkChoiceUpdatedV3 { | ||
Ok(ForkChoiceUpdated { | ||
fork_choice_state: serde_json::from_value(params[0].clone())?, | ||
payload_attributes: serde_json::from_value(params[1].clone()) | ||
.map_err(|e| e.to_string()), | ||
}) | ||
} | ||
|
||
fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> { | ||
fn try_from(fork_choice_updated: &dyn ForkChoiceUpdatedImpl) -> Result<RpcRequest, String> { | ||
let request = fork_choice_updated.request(); | ||
match &request.payload_attributes { | ||
Ok(attrs) => Ok(RpcRequest { | ||
method: fork_choice_updated.method(), | ||
params: Some(vec![ | ||
serde_json::json!(request.fork_choice_state), | ||
serde_json::json!(attrs), | ||
]), | ||
..Default::default() | ||
}), | ||
Err(err) => Err(err.to_string()), | ||
} | ||
} | ||
|
||
fn handle( | ||
fork_choice_updated: &dyn ForkChoiceUpdatedImpl, | ||
context: RpcApiContext, | ||
) -> Result<Value, RpcErr> { | ||
let request = fork_choice_updated.request(); | ||
let storage = &context.storage; | ||
info!( | ||
"New fork choice request with head: {}, safe: {}, finalized: {}.", | ||
self.fork_choice_state.head_block_hash, | ||
self.fork_choice_state.safe_block_hash, | ||
self.fork_choice_state.finalized_block_hash | ||
request.fork_choice_state.head_block_hash, | ||
request.fork_choice_state.safe_block_hash, | ||
request.fork_choice_state.finalized_block_hash | ||
); | ||
|
||
let head_block = match apply_fork_choice( | ||
&context.storage, | ||
self.fork_choice_state.head_block_hash, | ||
self.fork_choice_state.safe_block_hash, | ||
self.fork_choice_state.finalized_block_hash, | ||
storage, | ||
request.fork_choice_state.head_block_hash, | ||
request.fork_choice_state.safe_block_hash, | ||
request.fork_choice_state.finalized_block_hash, | ||
) { | ||
Ok(head) => head, | ||
Err(error) => { | ||
|
@@ -89,7 +91,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { | |
"Missing latest canonical block".to_owned(), | ||
)); | ||
}; | ||
let sync_head = self.fork_choice_state.head_block_hash; | ||
let sync_head = request.fork_choice_state.head_block_hash; | ||
tokio::spawn(async move { | ||
// If we can't get hold of the syncer, then it means that there is an active sync in process | ||
if let Ok(mut syncer) = context.syncer.try_lock() { | ||
|
@@ -112,34 +114,25 @@ impl RpcHandler for ForkChoiceUpdatedV3 { | |
|
||
// Build block from received payload. This step is skipped if applying the fork choice state failed | ||
let mut response = ForkChoiceResponse::from(PayloadStatus::valid_with_hash( | ||
self.fork_choice_state.head_block_hash, | ||
request.fork_choice_state.head_block_hash, | ||
)); | ||
|
||
match &self.payload_attributes { | ||
match &request.payload_attributes { | ||
// Payload may be invalid but we had to apply fork choice state nevertheless. | ||
Err(e) => return Err(RpcErr::InvalidPayloadAttributes(e.into())), | ||
Ok(None) => (), | ||
Ok(Some(attributes)) => { | ||
info!("Fork choice updated includes payload attributes. Creating a new payload."); | ||
let chain_config = context.storage.get_chain_config()?; | ||
if !chain_config.is_cancun_activated(attributes.timestamp) { | ||
return Err(RpcErr::UnsuportedFork( | ||
"forkChoiceV3 used to build pre-Cancun payload".to_string(), | ||
)); | ||
} | ||
if attributes.timestamp <= head_block.timestamp { | ||
return Err(RpcErr::InvalidPayloadAttributes( | ||
"invalid timestamp".to_string(), | ||
)); | ||
} | ||
let chain_config = storage.get_chain_config()?; | ||
fork_choice_updated.validate(attributes, chain_config, head_block)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this is the only thing that is different between the FCU calls? What if there is any actions besides validating? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For this call the only difference are the validations and the |
||
let args = BuildPayloadArgs { | ||
parent: self.fork_choice_state.head_block_hash, | ||
parent: request.fork_choice_state.head_block_hash, | ||
timestamp: attributes.timestamp, | ||
fee_recipient: attributes.suggested_fee_recipient, | ||
random: attributes.prev_randao, | ||
withdrawals: attributes.withdrawals.clone(), | ||
beacon_root: Some(attributes.parent_beacon_block_root), | ||
version: 3, | ||
beacon_root: attributes.parent_beacon_block_root, | ||
version: fork_choice_updated.version(), | ||
}; | ||
let payload_id = args.id(); | ||
response.set_id(payload_id); | ||
|
@@ -157,3 +150,138 @@ impl RpcHandler for ForkChoiceUpdatedV3 { | |
serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string())) | ||
} | ||
} | ||
|
||
trait ForkChoiceUpdatedImpl { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can we just call it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a struct already named like that |
||
fn method(&self) -> String; | ||
fn request(&self) -> &ForkChoiceUpdated; | ||
fn version(&self) -> u8; | ||
fn validate( | ||
&self, | ||
attributes: &PayloadAttributes, | ||
chain_config: ChainConfig, | ||
head_block: BlockHeader, | ||
) -> Result<(), RpcErr>; | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct ForkChoiceUpdatedV2(ForkChoiceUpdated); | ||
|
||
impl ForkChoiceUpdatedImpl for ForkChoiceUpdatedV2 { | ||
fn method(&self) -> String { | ||
"engine_forkchoiceUpdatedV2".to_string() | ||
} | ||
|
||
fn request(&self) -> &ForkChoiceUpdated { | ||
&self.0 | ||
} | ||
|
||
fn version(&self) -> u8 { | ||
2 | ||
} | ||
|
||
fn validate( | ||
&self, | ||
attributes: &PayloadAttributes, | ||
chain_config: ChainConfig, | ||
head_block: BlockHeader, | ||
) -> Result<(), RpcErr> { | ||
if attributes.parent_beacon_block_root.is_some() { | ||
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(), | ||
)); | ||
} | ||
if chain_config.is_cancun_activated(attributes.timestamp) { | ||
return Err(RpcErr::UnsuportedFork( | ||
"forkChoiceV2 used to build Cancun payload".to_string(), | ||
)); | ||
} | ||
if attributes.timestamp <= head_block.timestamp { | ||
return Err(RpcErr::InvalidPayloadAttributes( | ||
"invalid timestamp".to_string(), | ||
)); | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl TryFrom<ForkChoiceUpdatedV2> for RpcRequest { | ||
type Error = String; | ||
|
||
fn try_from(val: ForkChoiceUpdatedV2) -> Result<Self, Self::Error> { | ||
ForkChoiceUpdated::try_from(&val) | ||
} | ||
} | ||
|
||
impl RpcHandler for ForkChoiceUpdatedV2 { | ||
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> { | ||
Ok(Self(ForkChoiceUpdated::parse(params)?)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where do you validate that params is in fact v2? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only difference is the |
||
} | ||
|
||
fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> { | ||
ForkChoiceUpdated::handle(self, context) | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct ForkChoiceUpdatedV3(pub ForkChoiceUpdated); | ||
|
||
impl ForkChoiceUpdatedImpl for ForkChoiceUpdatedV3 { | ||
fn method(&self) -> String { | ||
"engine_forkchoiceUpdatedV3".to_string() | ||
} | ||
|
||
fn request(&self) -> &ForkChoiceUpdated { | ||
&self.0 | ||
} | ||
|
||
fn version(&self) -> u8 { | ||
3 | ||
} | ||
|
||
fn validate( | ||
&self, | ||
attributes: &PayloadAttributes, | ||
chain_config: ChainConfig, | ||
head_block: BlockHeader, | ||
) -> Result<(), RpcErr> { | ||
if attributes.parent_beacon_block_root.is_none() { | ||
return Err(RpcErr::InvalidPayloadAttributes( | ||
"Null Parent Beacon Root".to_string(), | ||
)); | ||
} | ||
if !chain_config.is_cancun_activated(attributes.timestamp) { | ||
return Err(RpcErr::UnsuportedFork( | ||
"forkChoiceV3 used to build pre-Cancun payload".to_string(), | ||
)); | ||
} | ||
if attributes.timestamp <= head_block.timestamp { | ||
return Err(RpcErr::InvalidPayloadAttributes( | ||
"invalid timestamp".to_string(), | ||
)); | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl TryFrom<ForkChoiceUpdatedV3> for RpcRequest { | ||
type Error = String; | ||
|
||
fn try_from(val: ForkChoiceUpdatedV3) -> Result<Self, Self::Error> { | ||
ForkChoiceUpdated::try_from(&val) | ||
} | ||
} | ||
|
||
impl RpcHandler for ForkChoiceUpdatedV3 { | ||
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> { | ||
Ok(Self(ForkChoiceUpdated::parse(params)?)) | ||
} | ||
|
||
fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> { | ||
ForkChoiceUpdated::handle(self, context) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I'd prefer if we used some explicit fields here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of prefer that too... but it's like repeating arbitrary code: