-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add cast command to convert beacon payload to execution payload b2e-payload
#11629
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
base: master
Are you sure you want to change the base?
Changes from all commits
ca2a51d
a9de85e
14342de
65b924e
920096b
a5fa112
ade3eda
7a94011
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
use alloy_rpc_types_beacon::payload::execution_payload_from_beacon_str; | ||
use alloy_rpc_types_engine::ExecutionPayload; | ||
use clap::Parser; | ||
use eyre::{Result, eyre}; | ||
use foundry_common::{fs, sh_print}; | ||
use std::path::PathBuf; | ||
|
||
/// CLI arguments for `cast b2e-payload`, convert Beacon block's execution payload to Execution | ||
/// JSON-RPC format. | ||
#[derive(Parser)] | ||
pub struct B2EPayloadArgs { | ||
/// Input data provided through JSON file path. | ||
#[arg( | ||
long = "json-file", | ||
value_name = "FILE", | ||
help = "Path to the JSON file containing the beacon block" | ||
)] | ||
pub json_file: PathBuf, | ||
} | ||
|
||
impl B2EPayloadArgs { | ||
pub async fn run(self) -> Result<()> { | ||
// Get input beacon block data | ||
let beacon_block = fs::read_to_string(&self.json_file) | ||
.map_err(|e| eyre!("Failed to read JSON file '{}': {}", self.json_file.display(), e))?; | ||
|
||
// Extract and convert execution payload | ||
let execution_payload = Self::extract_and_convert_execution_payload(&beacon_block)?; | ||
|
||
let json_rpc_output = format_as_json_rpc(execution_payload)?; | ||
sh_print!("{}", json_rpc_output)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
// Extracts the execution payload from a beacon block JSON string and converts it to | ||
// `ExecutionPayload` It matches `beaconcha.in` json format | ||
fn extract_and_convert_execution_payload(beacon_block: &str) -> Result<ExecutionPayload> { | ||
let beacon_json: serde_json::Value = serde_json::from_str(beacon_block) | ||
.map_err(|e| eyre!("Failed to parse beacon block JSON: {}", e))?; | ||
|
||
// early detection if the format is not correct | ||
if beacon_json | ||
.get("message") | ||
.and_then(|m| m.get("body")) | ||
.and_then(|b| b.get("execution_payload")) | ||
.is_none() | ||
{ | ||
return Err(eyre!("Invalid beacon block format: missing 'message' field")); | ||
Comment on lines
+39
to
+49
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. could we add this to alloy as well, so that we dont need to do this manually? |
||
} | ||
// Extract the "message.body.execution_payload" field from the beacon block JSON | ||
// TODO: check if we extract from beacon api it works but not sure it will work with all API | ||
// interfaces | ||
let execution_payload_beacon_block = beacon_json | ||
.get("message") | ||
.and_then(|m| m.get("body")) | ||
.and_then(|b| b.get("execution_payload")) | ||
.ok_or_else(|| eyre!("Could not find execution_payload in beacon block"))?; | ||
|
||
let execution_payload_str = serde_json::to_string(execution_payload_beacon_block) | ||
.map_err(|e| eyre!("Failed to serialize execution payload: {}", e))?; | ||
|
||
// Convert beacon block's execution payload to json rpc execution payload | ||
let execution_payload = execution_payload_from_beacon_str(&execution_payload_str)?; | ||
|
||
Ok(execution_payload) | ||
} | ||
} | ||
|
||
// Helper to format the execution payload as JSON-RPC response | ||
fn format_as_json_rpc(execution_payload: ExecutionPayload) -> Result<String> { | ||
// TODO: check if we used this format and this method engine version | ||
let json_rpc_request = serde_json::json!({ | ||
"jsonrpc": "2.0", | ||
"method": "engine_newPayloadV3", | ||
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 wee need some --version argument as well and we need to do some additional work here, because these endpoint take additional args: 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. let's either not do this conversion to Engine API -compatible request format at all, or do it optionally with an argument. The motivation to just have the execution payload in output is to be able to pipe this into |
||
"params": [execution_payload], | ||
"id": 1 | ||
}); | ||
Comment on lines
+71
to
+78
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. we def need some serde tests for this as well because converting payload variants totally sucks 👍 |
||
|
||
serde_json::to_string_pretty(&json_rpc_request) | ||
.map_err(|e| eyre!("Failed to serialize JSON-RPC response: {}", e)) | ||
} |
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 think we also want this to support piped inputs or maybe even fetch the beacon block directly
but def piped input so you can do curl ... | cast b2e