From 9145ff282eca71309eb656317c5cfb697085ac37 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:53:48 -0300 Subject: [PATCH] feat(L2): basic sequencer (#586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > [!WARNING] > This PR depends on #575 and must be merged after it. **Motivation** **Description** - Adds block producer module - Implements `Engine` to advance the blockchain --------- Co-authored-by: fmoletta Co-authored-by: Manuel Bilbao Co-authored-by: Manuel IƱaki Bilbao --- Cargo.toml | 1 + cmd/ethereum_rust/ethereum_rust.rs | 13 +- crates/common/serde_utils.rs | 45 ++++++ crates/common/types/blobs_bundle.rs | 4 +- crates/l2/Cargo.toml | 8 +- crates/l2/operator/block_producer.rs | 49 ++++++- crates/l2/operator/engine.rs | 145 ++++++++++++++++++++ crates/l2/operator/mod.rs | 13 +- crates/networking/rpc/Cargo.toml | 2 +- crates/networking/rpc/engine/fork_choice.rs | 14 ++ crates/networking/rpc/engine/mod.rs | 12 +- crates/networking/rpc/engine/payload.rs | 31 ++++- crates/networking/rpc/eth/block.rs | 2 +- crates/networking/rpc/eth/transaction.rs | 9 +- crates/networking/rpc/rpc.rs | 2 +- crates/networking/rpc/types/fork_choice.rs | 6 +- crates/networking/rpc/types/payload.rs | 2 +- crates/networking/rpc/utils.rs | 11 ++ test_data/genesis-execution-api.json | 3 + 19 files changed, 344 insertions(+), 28 deletions(-) create mode 100644 crates/l2/operator/engine.rs diff --git a/Cargo.toml b/Cargo.toml index 2884e3233..2f18efafd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,4 +49,5 @@ crc32fast = "1.4.2" lazy_static = "1.5.0" sha3 = "0.10.8" tokio-util = { version = "0.7.12", features = ["rt"] } +jsonwebtoken = "9.3.0" rand = "0.8.5" diff --git a/cmd/ethereum_rust/ethereum_rust.rs b/cmd/ethereum_rust/ethereum_rust.rs index c5ad2b78e..1333310ee 100644 --- a/cmd/ethereum_rust/ethereum_rust.rs +++ b/cmd/ethereum_rust/ethereum_rust.rs @@ -116,15 +116,18 @@ async fn main() { let size = blocks.len(); for block in blocks { let hash = block.header.compute_block_hash(); - info!("Adding block {} with hash {}.", block.header.number, hash); + info!( + "Adding block {} with hash {:#x}.", + block.header.number, hash + ); match add_block(&block, &store) { Ok(()) => store .set_canonical_block(block.header.number, hash) .unwrap(), - _ => { + Err(error) => { warn!( - "Failed to add block {} with hash {}.", - block.header.number, hash + "Failed to add block {} with hash {:#x}: {}", + block.header.number, hash, error ); } } @@ -159,7 +162,7 @@ async fn main() { // We do not want to start the networking module if the l2 feature is enabled. cfg_if::cfg_if! { if #[cfg(feature = "l2")] { - let l2_operator = ethereum_rust_l2::start_operator().into_future(); + let l2_operator = ethereum_rust_l2::start_operator(store.clone()).into_future(); tracker.spawn(l2_operator); let l2_prover = ethereum_rust_l2::start_prover().into_future(); tracker.spawn(l2_prover); diff --git a/crates/common/serde_utils.rs b/crates/common/serde_utils.rs index bba4d07ca..2237c98c8 100644 --- a/crates/common/serde_utils.rs +++ b/crates/common/serde_utils.rs @@ -290,6 +290,28 @@ pub mod bytes48 { { serialize_vec_of_hex_encodables(value, serializer) } + + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let value = Vec::::deserialize(d)?; + let mut output = Vec::new(); + for str in value { + let bytes = hex::decode(str.trim_start_matches("0x")) + .map_err(|e| D::Error::custom(e.to_string()))?; + if bytes.len() != 48 { + return Err(D::Error::custom(format!( + "Expected 48 bytes, got {}", + bytes.len() + ))); + } + let mut blob = [0u8; 48]; + blob.copy_from_slice(&bytes); + output.push(blob); + } + Ok(output) + } } } @@ -310,6 +332,29 @@ pub mod blob { { serialize_vec_of_hex_encodables(value, serializer) } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let value = Vec::::deserialize(deserializer)?; + let mut output = Vec::new(); + for str in value { + let bytes = hex::decode(str.trim_start_matches("0x")) + .map_err(|e| D::Error::custom(e.to_string()))?; + if bytes.len() != BYTES_PER_BLOB { + return Err(D::Error::custom(format!( + "Expected {} bytes, got {}", + BYTES_PER_BLOB, + bytes.len() + ))); + } + let mut blob = [0u8; BYTES_PER_BLOB]; + blob.copy_from_slice(&bytes); + output.push(blob); + } + Ok(output) + } } } diff --git a/crates/common/types/blobs_bundle.rs b/crates/common/types/blobs_bundle.rs index 71cf45bf6..9a6b28dab 100644 --- a/crates/common/types/blobs_bundle.rs +++ b/crates/common/types/blobs_bundle.rs @@ -5,7 +5,7 @@ use ethereum_rust_rlp::{ error::RLPDecodeError, structs::{Decoder, Encoder}, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use super::BYTES_PER_BLOB; @@ -14,7 +14,7 @@ pub type Blob = [u8; BYTES_PER_BLOB]; pub type Commitment = Bytes48; pub type Proof = Bytes48; -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] /// Struct containing all the blobs for a blob transaction, along with the corresponding commitments and proofs pub struct BlobsBundle { diff --git a/crates/l2/Cargo.toml b/crates/l2/Cargo.toml index a6d821a1e..452929718 100644 --- a/crates/l2/Cargo.toml +++ b/crates/l2/Cargo.toml @@ -12,16 +12,18 @@ tokio-util.workspace = true tracing.workspace = true serde.workspace = true serde_json.workspace = true -sp1-sdk = "2.0.0" ethereum-types.workspace = true ethereum_rust-core.workspace = true ethereum_rust-rlp.workspace = true ethereum_rust-rpc.workspace = true ethereum_rust-blockchain.workspace = true -libsecp256k1 = "0.7.1" +ethereum_rust-storage.workspace = true +hex.workspace = true bytes.workspace = true +jsonwebtoken.workspace = true +sp1-sdk = "2.0.0" +libsecp256k1 = "0.7.1" keccak-hash = "0.10.0" -hex.workspace = true [lib] path = "./l2.rs" diff --git a/crates/l2/operator/block_producer.rs b/crates/l2/operator/block_producer.rs index ffb4e8060..4ae9be491 100644 --- a/crates/l2/operator/block_producer.rs +++ b/crates/l2/operator/block_producer.rs @@ -1,3 +1,48 @@ -pub struct BlockProducer; +use crate::operator::engine::Engine; +use ethereum_rust_rpc::types::fork_choice::{ForkChoiceState, PayloadAttributesV3}; +use ethereum_types::H256; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tokio::time::sleep; -pub async fn start_block_producer() {} +pub async fn start_block_producer(current_block_hash: H256) { + let mut current_block_hash = current_block_hash; + loop { + let secret = std::fs::read("../../../jwt.hex").unwrap(); + let engine = Engine::new("http://localhost:8551", secret.into()); + + let fork_choice_state = ForkChoiceState { + head_block_hash: current_block_hash, + safe_block_hash: current_block_hash, + finalized_block_hash: current_block_hash, + }; + let payload_attributes = PayloadAttributesV3 { + timestamp: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + ..Default::default() + }; + + let fork_choice_response = engine + .engine_forkchoice_updated_v3(fork_choice_state, payload_attributes) + .await + .unwrap(); + + let payload_id = fork_choice_response.payload_id.unwrap(); + + let execution_payload_response = engine.engine_get_payload_v3(payload_id).await.unwrap(); + + let payload_status = engine + .engine_new_payload_v3( + execution_payload_response.execution_payload, + Default::default(), + Default::default(), + ) + .await + .unwrap(); + + current_block_hash = payload_status.latest_valid_hash.unwrap(); + + sleep(Duration::from_secs(5)).await; + } +} diff --git a/crates/l2/operator/engine.rs b/crates/l2/operator/engine.rs new file mode 100644 index 000000000..3fb9775c1 --- /dev/null +++ b/crates/l2/operator/engine.rs @@ -0,0 +1,145 @@ +use bytes::Bytes; +use ethereum_rust_rpc::{ + engine::{ + fork_choice::ForkChoiceUpdatedV3, + payload::{GetPayloadV3Request, NewPayloadV3Request}, + ExchangeCapabilitiesRequest, + }, + types::{ + fork_choice::{ForkChoiceResponse, ForkChoiceState, PayloadAttributesV3}, + payload::{ExecutionPayloadResponse, ExecutionPayloadV3, PayloadStatus}, + }, + utils::{RpcErrorResponse, RpcRequest, RpcSuccessResponse}, +}; +use ethereum_types::H256; +use reqwest::Client; +use serde::Deserialize; +use serde_json::json; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum RpcResponse { + Success(RpcSuccessResponse), + Error(RpcErrorResponse), +} + +pub struct Engine { + client: Client, + secret: Bytes, + execution_client_url: String, +} + +impl Engine { + pub fn new(execution_client_url: &str, secret: Bytes) -> Self { + Self { + client: Client::new(), + secret, + execution_client_url: execution_client_url.to_string(), + } + } + + async fn send_request(&self, request: RpcRequest) -> Result { + self.client + .post(&self.execution_client_url) + .bearer_auth(self.auth_token()) + .header("content-type", "application/json") + .body(serde_json::ser::to_string(&request).unwrap()) + .send() + .await? + .json::() + .await + } + + pub async fn engine_exchange_capabilities(&self) -> Result, String> { + let request = ExchangeCapabilitiesRequest::from(Self::capabilities()).into(); + + match self.send_request(request).await { + Ok(RpcResponse::Success(result)) => Ok(serde_json::from_value(result.result).unwrap()), + Ok(RpcResponse::Error(e)) => Err(e.error.message), + Err(e) => Err(e.to_string()), + } + } + + pub async fn engine_forkchoice_updated_v3( + &self, + state: ForkChoiceState, + payload_attributes: PayloadAttributesV3, + ) -> Result { + let request = ForkChoiceUpdatedV3 { + fork_choice_state: state, + payload_attributes: Some(payload_attributes), + } + .into(); + + match self.send_request(request).await { + Ok(RpcResponse::Success(s)) => match serde_json::from_value(s.result.clone()) { + Ok(parsed_value) => Ok(parsed_value), + Err(error) => { + dbg!(s.result); + Err(error.to_string()) + } + }, + Ok(RpcResponse::Error(e)) => Err(e.error.message), + Err(e) => Err(e.to_string()), + } + } + + pub async fn engine_get_payload_v3( + &self, + payload_id: u64, + ) -> Result { + let request = GetPayloadV3Request { payload_id }.into(); + + match self.send_request(request).await { + Ok(RpcResponse::Success(s)) => Ok(serde_json::from_value(s.result).unwrap()), + Ok(RpcResponse::Error(e)) => Err(e.error.message), + Err(e) => Err(e.to_string()), + } + } + + pub async fn engine_new_payload_v3( + &self, + execution_payload: ExecutionPayloadV3, + expected_blob_versioned_hashes: Vec, + parent_beacon_block_root: H256, + ) -> Result { + let request = NewPayloadV3Request { + payload: execution_payload, + expected_blob_versioned_hashes, + parent_beacon_block_root, + } + .into(); + + match self.send_request(request).await { + Ok(RpcResponse::Success(s)) => Ok(serde_json::from_value(s.result).unwrap()), + Ok(RpcResponse::Error(e)) => Err(e.error.message), + Err(e) => Err(e.to_string()), + } + } + + fn auth_token(&self) -> String { + // Header + let header = jsonwebtoken::Header::default(); + // Claims + let valid_iat = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as usize; + let claims = json!({"iat": valid_iat}); + // Encoding Key + let decoded_secret = hex::decode(self.secret.clone()).unwrap(); + let encoding_key = jsonwebtoken::EncodingKey::from_secret(decoded_secret.as_ref()); + // JWT Token + jsonwebtoken::encode(&header, &claims, &encoding_key).unwrap() + } + + fn capabilities() -> Vec { + vec![ + "engine_exchangeCapabilities".to_owned(), + "engine_forkchoiceUpdatedV3".to_owned(), + "engine_getPayloadV3".to_owned(), + "engine_newPayloadV3".to_owned(), + ] + } +} diff --git a/crates/l2/operator/mod.rs b/crates/l2/operator/mod.rs index 684b25f70..656691596 100644 --- a/crates/l2/operator/mod.rs +++ b/crates/l2/operator/mod.rs @@ -1,14 +1,23 @@ +use ethereum_rust_storage::Store; use std::net::{IpAddr, Ipv4Addr}; pub mod block_producer; +pub mod engine; pub mod l1_tx_sender; pub mod l1_watcher; pub mod proof_data_provider; -pub async fn start_operator() { +pub async fn start_operator(store: Store) { let l1_tx_sender = tokio::spawn(l1_tx_sender::start_l1_tx_sender()); let l1_watcher = tokio::spawn(l1_watcher::start_l1_watcher()); - let block_producer = tokio::spawn(block_producer::start_block_producer()); + let current_block_hash = { + let current_block_number = store.get_latest_block_number().unwrap().unwrap(); + store + .get_canonical_block_hash(current_block_number) + .unwrap() + .unwrap() + }; + let block_producer = tokio::spawn(block_producer::start_block_producer(current_block_hash)); let proof_data_provider = tokio::spawn(proof_data_provider::start_proof_data_provider( IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 3000, diff --git a/crates/networking/rpc/Cargo.toml b/crates/networking/rpc/Cargo.toml index c183f0a27..713c05763 100644 --- a/crates/networking/rpc/Cargo.toml +++ b/crates/networking/rpc/Cargo.toml @@ -21,7 +21,7 @@ ethereum_rust-net.workspace = true ethereum_rust-rlp.workspace = true hex.workspace = true axum-extra = {version = "0.9.3", features = ["typed-header"]} -jsonwebtoken = "9.3.0" +jsonwebtoken.workspace = true rand.workspace = true [dev-dependencies] diff --git a/crates/networking/rpc/engine/fork_choice.rs b/crates/networking/rpc/engine/fork_choice.rs index 11691eb2c..f0d4139bc 100644 --- a/crates/networking/rpc/engine/fork_choice.rs +++ b/crates/networking/rpc/engine/fork_choice.rs @@ -12,6 +12,7 @@ use crate::{ fork_choice::{ForkChoiceResponse, ForkChoiceState, PayloadAttributesV3}, payload::PayloadStatus, }, + utils::RpcRequest, RpcErr, RpcHandler, }; @@ -22,6 +23,19 @@ pub struct ForkChoiceUpdatedV3 { pub payload_attributes: Option, } +impl From for RpcRequest { + fn from(val: ForkChoiceUpdatedV3) -> Self { + RpcRequest { + method: "engine_forkchoiceUpdatedV3".to_string(), + params: Some(vec![ + serde_json::json!(val.fork_choice_state), + serde_json::json!(val.payload_attributes), + ]), + ..Default::default() + } + } +} + impl RpcHandler for ForkChoiceUpdatedV3 { fn parse(params: &Option>) -> Result { let params = params diff --git a/crates/networking/rpc/engine/mod.rs b/crates/networking/rpc/engine/mod.rs index 9649849e5..59e855a49 100644 --- a/crates/networking/rpc/engine/mod.rs +++ b/crates/networking/rpc/engine/mod.rs @@ -2,11 +2,21 @@ pub mod exchange_transition_config; pub mod fork_choice; pub mod payload; -use crate::{RpcErr, RpcHandler, Store}; +use crate::{utils::RpcRequest, RpcErr, RpcHandler, Store}; use serde_json::{json, Value}; pub type ExchangeCapabilitiesRequest = Vec; +impl From for RpcRequest { + fn from(val: ExchangeCapabilitiesRequest) -> Self { + RpcRequest { + method: "engine_exchangeCapabilities".to_string(), + params: Some(vec![serde_json::json!(val)]), + ..Default::default() + } + } +} + impl RpcHandler for ExchangeCapabilitiesRequest { fn parse(params: &Option>) -> Result { params diff --git a/crates/networking/rpc/engine/payload.rs b/crates/networking/rpc/engine/payload.rs index 36fa9d151..2560c59f7 100644 --- a/crates/networking/rpc/engine/payload.rs +++ b/crates/networking/rpc/engine/payload.rs @@ -2,12 +2,13 @@ use ethereum_rust_blockchain::error::ChainError; use ethereum_rust_blockchain::payload::build_payload; use ethereum_rust_blockchain::{add_block, latest_valid_hash}; use ethereum_rust_core::types::Fork; -use ethereum_rust_core::H256; +use ethereum_rust_core::{H256, U256}; use ethereum_rust_storage::Store; use serde_json::Value; use tracing::{info, warn}; use crate::types::payload::ExecutionPayloadResponse; +use crate::utils::RpcRequest; use crate::{ types::payload::{ExecutionPayloadV3, PayloadStatus}, RpcErr, RpcHandler, @@ -23,6 +24,20 @@ pub struct GetPayloadV3Request { pub payload_id: u64, } +impl From for RpcRequest { + fn from(val: NewPayloadV3Request) -> Self { + RpcRequest { + method: "engine_newPayloadV3".to_string(), + params: Some(vec![ + serde_json::json!(val.payload), + serde_json::json!(val.expected_blob_versioned_hashes), + serde_json::json!(val.parent_beacon_block_root), + ]), + ..Default::default() + } + } +} + impl RpcHandler for NewPayloadV3Request { fn parse(params: &Option>) -> Result { let params = params @@ -40,7 +55,7 @@ impl RpcHandler for NewPayloadV3Request { fn handle(&self, storage: Store) -> Result { let block_hash = self.payload.block_hash; - info!("Received new payload with block hash: {block_hash}"); + info!("Received new payload with block hash: {block_hash:#x}"); let block = match self .payload @@ -114,7 +129,7 @@ impl RpcHandler for NewPayloadV3Request { latest_valid_hash(&storage).map_err(|error| RpcErr::Internal(error.to_string()))?; // Execute and store the block - info!("Executing payload with block hash: {block_hash}"); + info!("Executing payload with block hash: {block_hash:#x}"); let payload_status = match add_block(&block, &storage) { Err(ChainError::NonCanonicalParent) => Ok(PayloadStatus::syncing()), Err(ChainError::ParentNotFound) => Ok(PayloadStatus::invalid_with_err( @@ -151,6 +166,16 @@ impl RpcHandler for NewPayloadV3Request { } } +impl From for RpcRequest { + fn from(val: GetPayloadV3Request) -> Self { + RpcRequest { + method: "engine_getPayloadV3".to_string(), + params: Some(vec![serde_json::json!(U256::from(val.payload_id))]), + ..Default::default() + } + } +} + impl RpcHandler for GetPayloadV3Request { fn parse(params: &Option>) -> Result { let params = params diff --git a/crates/networking/rpc/eth/block.rs b/crates/networking/rpc/eth/block.rs index 03119af4e..58f0dfa19 100644 --- a/crates/networking/rpc/eth/block.rs +++ b/crates/networking/rpc/eth/block.rs @@ -110,7 +110,7 @@ impl RpcHandler for GetBlockByHashRequest { }) } fn handle(&self, storage: Store) -> Result { - info!("Requested block with hash: {}", self.block); + info!("Requested block with hash: {:#x}", self.block); let block_number = match storage.get_block_number(self.block)? { Some(number) => number, _ => return Ok(Value::Null), diff --git a/crates/networking/rpc/eth/transaction.rs b/crates/networking/rpc/eth/transaction.rs index 13a41a56e..8b201cc0b 100644 --- a/crates/networking/rpc/eth/transaction.rs +++ b/crates/networking/rpc/eth/transaction.rs @@ -185,7 +185,7 @@ impl RpcHandler for GetTransactionByBlockHashAndIndexRequest { } fn handle(&self, storage: Store) -> Result { info!( - "Requested transaction at index: {} of block with hash: {}", + "Requested transaction at index: {} of block with hash: {:#x}", self.transaction_index, self.block, ); let block_number = match storage.get_block_number(self.block)? { @@ -222,7 +222,10 @@ impl RpcHandler for GetTransactionByHashRequest { }) } fn handle(&self, storage: Store) -> Result { - info!("Requested transaction with hash: {}", self.transaction_hash,); + info!( + "Requested transaction with hash: {:#x}", + self.transaction_hash, + ); let (block_number, block_hash, index) = match storage.get_transaction_location(self.transaction_hash)? { Some(location) => location, @@ -258,7 +261,7 @@ impl RpcHandler for GetTransactionReceiptRequest { } fn handle(&self, storage: Store) -> Result { info!( - "Requested receipt for transaction {}", + "Requested receipt for transaction {:#x}", self.transaction_hash, ); let (block_number, block_hash, index) = diff --git a/crates/networking/rpc/rpc.rs b/crates/networking/rpc/rpc.rs index c6b840170..f408b2513 100644 --- a/crates/networking/rpc/rpc.rs +++ b/crates/networking/rpc/rpc.rs @@ -48,7 +48,7 @@ use utils::{ }; mod admin; mod authentication; -mod engine; +pub mod engine; mod eth; pub mod types; pub mod utils; diff --git a/crates/networking/rpc/types/fork_choice.rs b/crates/networking/rpc/types/fork_choice.rs index 68d20f8b5..86189038a 100644 --- a/crates/networking/rpc/types/fork_choice.rs +++ b/crates/networking/rpc/types/fork_choice.rs @@ -2,7 +2,7 @@ use super::payload::PayloadStatus; use ethereum_rust_core::{serde_utils, types::Withdrawal, Address, H256}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ForkChoiceState { #[allow(unused)] @@ -11,7 +11,7 @@ pub struct ForkChoiceState { pub finalized_block_hash: H256, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Default, Serialize)] #[serde(rename_all = "camelCase")] #[allow(unused)] pub struct PayloadAttributesV3 { @@ -23,7 +23,7 @@ pub struct PayloadAttributesV3 { pub parent_beacon_block_root: H256, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ForkChoiceResponse { pub payload_status: PayloadStatus, diff --git a/crates/networking/rpc/types/payload.rs b/crates/networking/rpc/types/payload.rs index e8e39da00..6c0bcb5a1 100644 --- a/crates/networking/rpc/types/payload.rs +++ b/crates/networking/rpc/types/payload.rs @@ -221,7 +221,7 @@ impl PayloadStatus { } } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExecutionPayloadResponse { pub execution_payload: ExecutionPayloadV3, // We only handle v3 payloads diff --git a/crates/networking/rpc/utils.rs b/crates/networking/rpc/utils.rs index 18d724277..c06ae5883 100644 --- a/crates/networking/rpc/utils.rs +++ b/crates/networking/rpc/utils.rs @@ -170,6 +170,17 @@ impl RpcRequest { } } +impl Default for RpcRequest { + fn default() -> Self { + RpcRequest { + id: RpcRequestId::Number(1), + jsonrpc: "2.0".to_string(), + method: "".to_string(), + params: None, + } + } +} + #[derive(Serialize, Deserialize, Debug)] pub struct RpcErrorMetadata { pub code: i32, diff --git a/test_data/genesis-execution-api.json b/test_data/genesis-execution-api.json index 951e53525..81c4e768f 100644 --- a/test_data/genesis-execution-api.json +++ b/test_data/genesis-execution-api.json @@ -33,6 +33,9 @@ "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", "balance": "0x2a" }, + "0x3d1e15a1a55578f7c920884a9943b3b35d0d885b": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, "0c2c51a0990aee1d73c1228de158688341557508": { "balance": "0xc097ce7bc90715b34b9f1000000000" },