mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-11 04:31:51 +00:00
Implement engine API v1.0.0-alpha.4 (#2810)
* Added ForkchoiceUpdatedV1 & GetPayloadV1 * Added ExecutePayloadV1 * Added new geth test vectors * Separated Json Object/Serialization Code into file * Deleted code/tests for Requests Removed from spec * Finally fixed serialization of null '0x' * Made Naming of JSON Structs Consistent * Fix clippy lints * Remove u64 payload id * Remove unused serde impls * Swap to [u8; 8] for payload id * Tidy * Adjust some block gen return vals * Tidy * Add fallback when payload id is unknown * Remove comment Co-authored-by: Mark Mackey <mark@sigmaprime.io>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
use crate::engine_api::{
|
||||
http::JsonPreparePayloadRequest, ConsensusStatus, ExecutePayloadResponse, ExecutionBlock,
|
||||
ExecutePayloadResponse, ExecutePayloadResponseStatus, ExecutionBlock, PayloadAttributes,
|
||||
PayloadId,
|
||||
};
|
||||
use crate::engines::ForkChoiceState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use tree_hash::TreeHash;
|
||||
@@ -90,7 +92,7 @@ pub struct ExecutionBlockGenerator<T: EthSpec> {
|
||||
*/
|
||||
pub pending_payloads: HashMap<Hash256, ExecutionPayload<T>>,
|
||||
pub next_payload_id: u64,
|
||||
pub payload_ids: HashMap<u64, ExecutionPayload<T>>,
|
||||
pub payload_ids: HashMap<PayloadId, ExecutionPayload<T>>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
@@ -222,104 +224,128 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn prepare_payload(&mut self, payload: JsonPreparePayloadRequest) -> Result<u64, String> {
|
||||
if !self.blocks.iter().any(|(_, block)| {
|
||||
block.block_hash() == self.terminal_block_hash
|
||||
|| block.block_number() == self.terminal_block_number
|
||||
}) {
|
||||
return Err("refusing to create payload id before terminal block".to_string());
|
||||
}
|
||||
|
||||
let parent = self
|
||||
.blocks
|
||||
.get(&payload.parent_hash)
|
||||
.ok_or_else(|| format!("unknown parent block {:?}", payload.parent_hash))?;
|
||||
|
||||
let id = self.next_payload_id;
|
||||
self.next_payload_id += 1;
|
||||
|
||||
let mut execution_payload = ExecutionPayload {
|
||||
parent_hash: payload.parent_hash,
|
||||
coinbase: payload.fee_recipient,
|
||||
receipt_root: Hash256::repeat_byte(42),
|
||||
state_root: Hash256::repeat_byte(43),
|
||||
logs_bloom: vec![0; 256].into(),
|
||||
random: payload.random,
|
||||
block_number: parent.block_number() + 1,
|
||||
gas_limit: GAS_LIMIT,
|
||||
gas_used: GAS_USED,
|
||||
timestamp: payload.timestamp,
|
||||
extra_data: "block gen was here".as_bytes().to_vec().into(),
|
||||
base_fee_per_gas: Uint256::one(),
|
||||
block_hash: Hash256::zero(),
|
||||
transactions: vec![].into(),
|
||||
};
|
||||
|
||||
execution_payload.block_hash = execution_payload.tree_hash_root();
|
||||
|
||||
self.payload_ids.insert(id, execution_payload);
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn get_payload(&mut self, id: u64) -> Option<ExecutionPayload<T>> {
|
||||
self.payload_ids.remove(&id)
|
||||
pub fn get_payload(&mut self, id: &PayloadId) -> Option<ExecutionPayload<T>> {
|
||||
self.payload_ids.remove(id)
|
||||
}
|
||||
|
||||
pub fn execute_payload(&mut self, payload: ExecutionPayload<T>) -> ExecutePayloadResponse {
|
||||
let parent = if let Some(parent) = self.blocks.get(&payload.parent_hash) {
|
||||
parent
|
||||
} else {
|
||||
return ExecutePayloadResponse::Invalid;
|
||||
return ExecutePayloadResponse {
|
||||
status: ExecutePayloadResponseStatus::Syncing,
|
||||
latest_valid_hash: None,
|
||||
message: None,
|
||||
};
|
||||
};
|
||||
|
||||
if payload.block_number != parent.block_number() + 1 {
|
||||
return ExecutePayloadResponse::Invalid;
|
||||
return ExecutePayloadResponse {
|
||||
status: ExecutePayloadResponseStatus::Invalid,
|
||||
latest_valid_hash: Some(parent.block_hash()),
|
||||
message: Some("invalid block number".to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
let valid_hash = payload.block_hash;
|
||||
self.pending_payloads.insert(payload.block_hash, payload);
|
||||
|
||||
ExecutePayloadResponse::Valid
|
||||
ExecutePayloadResponse {
|
||||
status: ExecutePayloadResponseStatus::Valid,
|
||||
latest_valid_hash: Some(valid_hash),
|
||||
message: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consensus_validated(
|
||||
pub fn forkchoice_updated_v1(
|
||||
&mut self,
|
||||
block_hash: Hash256,
|
||||
status: ConsensusStatus,
|
||||
) -> Result<(), String> {
|
||||
let payload = self
|
||||
forkchoice_state: ForkChoiceState,
|
||||
payload_attributes: Option<PayloadAttributes>,
|
||||
) -> Result<Option<PayloadId>, String> {
|
||||
if let Some(payload) = self
|
||||
.pending_payloads
|
||||
.remove(&block_hash)
|
||||
.ok_or_else(|| format!("no pending payload for {:?}", block_hash))?;
|
||||
|
||||
match status {
|
||||
ConsensusStatus::Valid => self.insert_block(Block::PoS(payload)),
|
||||
ConsensusStatus::Invalid => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forkchoice_updated(
|
||||
&mut self,
|
||||
block_hash: Hash256,
|
||||
finalized_block_hash: Hash256,
|
||||
) -> Result<(), String> {
|
||||
if !self.blocks.contains_key(&block_hash) {
|
||||
return Err(format!("block hash {:?} unknown", block_hash));
|
||||
}
|
||||
|
||||
if finalized_block_hash != Hash256::zero()
|
||||
&& !self.blocks.contains_key(&finalized_block_hash)
|
||||
.remove(&forkchoice_state.head_block_hash)
|
||||
{
|
||||
self.insert_block(Block::PoS(payload))?;
|
||||
}
|
||||
if !self.blocks.contains_key(&forkchoice_state.head_block_hash) {
|
||||
return Err(format!(
|
||||
"finalized block hash {:?} is unknown",
|
||||
finalized_block_hash
|
||||
"block hash {:?} unknown",
|
||||
forkchoice_state.head_block_hash
|
||||
));
|
||||
}
|
||||
if !self.blocks.contains_key(&forkchoice_state.safe_block_hash) {
|
||||
return Err(format!(
|
||||
"block hash {:?} unknown",
|
||||
forkchoice_state.head_block_hash
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if forkchoice_state.finalized_block_hash != Hash256::zero()
|
||||
&& !self
|
||||
.blocks
|
||||
.contains_key(&forkchoice_state.finalized_block_hash)
|
||||
{
|
||||
return Err(format!(
|
||||
"finalized block hash {:?} is unknown",
|
||||
forkchoice_state.finalized_block_hash
|
||||
));
|
||||
}
|
||||
|
||||
match payload_attributes {
|
||||
None => Ok(None),
|
||||
Some(attributes) => {
|
||||
if !self.blocks.iter().any(|(_, block)| {
|
||||
block.block_hash() == self.terminal_block_hash
|
||||
|| block.block_number() == self.terminal_block_number
|
||||
}) {
|
||||
return Err("refusing to create payload id before terminal block".to_string());
|
||||
}
|
||||
|
||||
let parent = self
|
||||
.blocks
|
||||
.get(&forkchoice_state.head_block_hash)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"unknown parent block {:?}",
|
||||
forkchoice_state.head_block_hash
|
||||
)
|
||||
})?;
|
||||
|
||||
let id = payload_id_from_u64(self.next_payload_id);
|
||||
self.next_payload_id += 1;
|
||||
|
||||
let mut execution_payload = ExecutionPayload {
|
||||
parent_hash: forkchoice_state.head_block_hash,
|
||||
coinbase: attributes.fee_recipient,
|
||||
receipt_root: Hash256::repeat_byte(42),
|
||||
state_root: Hash256::repeat_byte(43),
|
||||
logs_bloom: vec![0; 256].into(),
|
||||
random: attributes.random,
|
||||
block_number: parent.block_number() + 1,
|
||||
gas_limit: GAS_LIMIT,
|
||||
gas_used: GAS_USED,
|
||||
timestamp: attributes.timestamp,
|
||||
extra_data: "block gen was here".as_bytes().to_vec().into(),
|
||||
base_fee_per_gas: Uint256::one(),
|
||||
block_hash: Hash256::zero(),
|
||||
transactions: vec![].into(),
|
||||
};
|
||||
|
||||
execution_payload.block_hash = execution_payload.tree_hash_root();
|
||||
|
||||
self.payload_ids.insert(id, execution_payload);
|
||||
|
||||
Ok(Some(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn payload_id_from_u64(n: u64) -> PayloadId {
|
||||
n.to_le_bytes()
|
||||
}
|
||||
|
||||
pub fn generate_pow_block(
|
||||
terminal_total_difficulty: Uint256,
|
||||
terminal_block_number: u64,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::Context;
|
||||
use crate::engine_api::http::*;
|
||||
use crate::engine_api::{http::*, ExecutePayloadResponse, ExecutePayloadResponseStatus};
|
||||
use crate::json_structures::*;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::sync::Arc;
|
||||
@@ -53,57 +54,59 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
)
|
||||
.unwrap())
|
||||
}
|
||||
ENGINE_PREPARE_PAYLOAD => {
|
||||
let request = get_param_0(params)?;
|
||||
let payload_id = ctx
|
||||
.execution_block_generator
|
||||
.write()
|
||||
.prepare_payload(request)?;
|
||||
ENGINE_EXECUTE_PAYLOAD_V1 => {
|
||||
let request: JsonExecutionPayloadV1<T> = get_param(params, 0)?;
|
||||
|
||||
Ok(serde_json::to_value(JsonPayloadIdResponse { payload_id }).unwrap())
|
||||
let response = if let Some(status) = *ctx.static_execute_payload_response.lock() {
|
||||
match status {
|
||||
ExecutePayloadResponseStatus::Valid => ExecutePayloadResponse {
|
||||
status,
|
||||
latest_valid_hash: Some(request.block_hash),
|
||||
message: None,
|
||||
},
|
||||
ExecutePayloadResponseStatus::Syncing => ExecutePayloadResponse {
|
||||
status,
|
||||
latest_valid_hash: None,
|
||||
message: None,
|
||||
},
|
||||
_ => unimplemented!("invalid static executePayloadResponse"),
|
||||
}
|
||||
} else {
|
||||
ctx.execution_block_generator
|
||||
.write()
|
||||
.execute_payload(request.into())
|
||||
};
|
||||
|
||||
Ok(serde_json::to_value(JsonExecutePayloadV1Response::from(response)).unwrap())
|
||||
}
|
||||
ENGINE_EXECUTE_PAYLOAD => {
|
||||
let request: JsonExecutionPayload<T> = get_param_0(params)?;
|
||||
|
||||
let status = ctx
|
||||
.static_execute_payload_response
|
||||
.lock()
|
||||
.unwrap_or_else(|| {
|
||||
ctx.execution_block_generator
|
||||
.write()
|
||||
.execute_payload(request.into())
|
||||
});
|
||||
|
||||
Ok(serde_json::to_value(ExecutePayloadResponseWrapper { status }).unwrap())
|
||||
}
|
||||
ENGINE_GET_PAYLOAD => {
|
||||
let request: JsonPayloadIdRequest = get_param_0(params)?;
|
||||
let id = request.payload_id;
|
||||
ENGINE_GET_PAYLOAD_V1 => {
|
||||
let request: JsonPayloadIdRequest = get_param(params, 0)?;
|
||||
let id = request.into();
|
||||
|
||||
let response = ctx
|
||||
.execution_block_generator
|
||||
.write()
|
||||
.get_payload(id)
|
||||
.ok_or_else(|| format!("no payload for id {}", id))?;
|
||||
.get_payload(&id)
|
||||
.ok_or_else(|| format!("no payload for id {:?}", id))?;
|
||||
|
||||
Ok(serde_json::to_value(JsonExecutionPayload::from(response)).unwrap())
|
||||
Ok(serde_json::to_value(JsonExecutionPayloadV1::from(response)).unwrap())
|
||||
}
|
||||
|
||||
ENGINE_CONSENSUS_VALIDATED => {
|
||||
let request: JsonConsensusValidatedRequest = get_param_0(params)?;
|
||||
ctx.execution_block_generator
|
||||
ENGINE_FORKCHOICE_UPDATED_V1 => {
|
||||
let forkchoice_state: JsonForkChoiceStateV1 = get_param(params, 0)?;
|
||||
let payload_attributes: Option<JsonPayloadAttributesV1> = get_param(params, 1)?;
|
||||
let id = ctx
|
||||
.execution_block_generator
|
||||
.write()
|
||||
.consensus_validated(request.block_hash, request.status)?;
|
||||
.forkchoice_updated_v1(
|
||||
forkchoice_state.into(),
|
||||
payload_attributes.map(|json| json.into()),
|
||||
)?;
|
||||
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
ENGINE_FORKCHOICE_UPDATED => {
|
||||
let request: JsonForkChoiceUpdatedRequest = get_param_0(params)?;
|
||||
ctx.execution_block_generator
|
||||
.write()
|
||||
.forkchoice_updated(request.head_block_hash, request.finalized_block_hash)?;
|
||||
|
||||
Ok(JsonValue::Null)
|
||||
Ok(serde_json::to_value(JsonForkchoiceUpdatedV1Response {
|
||||
status: JsonForkchoiceUpdatedV1ResponseStatus::Success,
|
||||
payload_id: id.map(Into::into),
|
||||
})
|
||||
.unwrap())
|
||||
}
|
||||
other => Err(format!(
|
||||
"The method {} does not exist/is not available",
|
||||
@@ -112,12 +115,12 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
}
|
||||
}
|
||||
|
||||
fn get_param_0<T: DeserializeOwned>(params: &JsonValue) -> Result<T, String> {
|
||||
fn get_param<T: DeserializeOwned>(params: &JsonValue, index: usize) -> Result<T, String> {
|
||||
params
|
||||
.get(0)
|
||||
.ok_or_else(|| "missing/invalid params[0] value".to_string())
|
||||
.get(index)
|
||||
.ok_or_else(|| format!("missing/invalid params[{}] value", index))
|
||||
.and_then(|param| {
|
||||
serde_json::from_value(param.clone())
|
||||
.map_err(|e| format!("failed to deserialize param[0]: {:?}", e))
|
||||
.map_err(|e| format!("failed to deserialize param[{}]: {:?}", index, e))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -105,16 +105,24 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
let block_number = latest_execution_block.block_number() + 1;
|
||||
let timestamp = block_number;
|
||||
let random = Hash256::from_low_u64_be(block_number);
|
||||
let finalized_block_hash = parent_hash;
|
||||
|
||||
let _payload_id = self
|
||||
.el
|
||||
.prepare_payload(parent_hash, timestamp, random)
|
||||
self.el
|
||||
.notify_forkchoice_updated(
|
||||
parent_hash,
|
||||
Hash256::zero(),
|
||||
Some(PayloadAttributes {
|
||||
timestamp,
|
||||
random,
|
||||
fee_recipient: Address::repeat_byte(42),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let payload = self
|
||||
.el
|
||||
.get_payload::<T>(parent_hash, timestamp, random)
|
||||
.get_payload::<T>(parent_hash, timestamp, random, finalized_block_hash)
|
||||
.await
|
||||
.unwrap();
|
||||
let block_hash = payload.block_hash;
|
||||
@@ -123,16 +131,13 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
assert_eq!(payload.timestamp, timestamp);
|
||||
assert_eq!(payload.random, random);
|
||||
|
||||
let (payload_response, payload_handle) = self.el.execute_payload(&payload).await.unwrap();
|
||||
assert_eq!(payload_response, ExecutePayloadResponse::Valid);
|
||||
|
||||
payload_handle
|
||||
.unwrap()
|
||||
.publish_async(ConsensusStatus::Valid)
|
||||
.await;
|
||||
let (payload_response, latest_valid_hash) =
|
||||
self.el.execute_payload(&payload).await.unwrap();
|
||||
assert_eq!(payload_response, ExecutePayloadResponseStatus::Valid);
|
||||
assert_eq!(latest_valid_hash, Some(payload.block_hash));
|
||||
|
||||
self.el
|
||||
.forkchoice_updated(block_hash, Hash256::zero())
|
||||
.notify_forkchoice_updated(block_hash, Hash256::zero(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Provides a mock execution engine HTTP JSON-RPC API for use in testing.
|
||||
|
||||
use crate::engine_api::http::JSONRPC_VERSION;
|
||||
use crate::engine_api::ExecutePayloadResponse;
|
||||
use crate::engine_api::ExecutePayloadResponseStatus;
|
||||
use bytes::Bytes;
|
||||
use environment::null_logger;
|
||||
use handle_rpc::handle_rpc;
|
||||
@@ -116,7 +116,7 @@ impl<T: EthSpec> MockServer<T> {
|
||||
}
|
||||
|
||||
pub fn all_payloads_valid(&self) {
|
||||
*self.ctx.static_execute_payload_response.lock() = Some(ExecutePayloadResponse::Valid)
|
||||
*self.ctx.static_execute_payload_response.lock() = Some(ExecutePayloadResponseStatus::Valid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ pub struct Context<T: EthSpec> {
|
||||
pub last_echo_request: Arc<RwLock<Option<Bytes>>>,
|
||||
pub execution_block_generator: RwLock<ExecutionBlockGenerator<T>>,
|
||||
pub preloaded_responses: Arc<Mutex<Vec<serde_json::Value>>>,
|
||||
pub static_execute_payload_response: Arc<Mutex<Option<ExecutePayloadResponse>>>,
|
||||
pub static_execute_payload_response: Arc<Mutex<Option<ExecutePayloadResponseStatus>>>,
|
||||
pub _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user