* Undo Passing Spec to Engine API * Utilize engine_exchangeCapabilities * Add Logging to Indicate Capella Readiness * Add exchangeCapabilities to mock_execution_layer * Send Nested Array for engine_exchangeCapabilities * Use Mutex Instead of RwLock for EngineCapabilities * Improve Locking to Avoid Deadlock * Prettier logic for get_engine_capabilities * Improve Comments * Update beacon_node/beacon_chain/src/capella_readiness.rs Co-authored-by: Michael Sproul <micsproul@gmail.com> * Update beacon_node/beacon_chain/src/capella_readiness.rs Co-authored-by: Michael Sproul <micsproul@gmail.com> * Update beacon_node/beacon_chain/src/capella_readiness.rs Co-authored-by: Michael Sproul <micsproul@gmail.com> * Update beacon_node/beacon_chain/src/capella_readiness.rs Co-authored-by: Michael Sproul <micsproul@gmail.com> * Update beacon_node/beacon_chain/src/capella_readiness.rs Co-authored-by: Michael Sproul <micsproul@gmail.com> * Update beacon_node/client/src/notifier.rs Co-authored-by: Michael Sproul <micsproul@gmail.com> * Update beacon_node/execution_layer/src/engine_api/http.rs Co-authored-by: Michael Sproul <micsproul@gmail.com> * Addressed Michael's Comments --------- Co-authored-by: Michael Sproul <micsproul@gmail.com>
378 lines
15 KiB
Rust
378 lines
15 KiB
Rust
use super::Context;
|
|
use crate::engine_api::{http::*, *};
|
|
use crate::json_structures::*;
|
|
use serde::de::DeserializeOwned;
|
|
use serde_json::Value as JsonValue;
|
|
use std::sync::Arc;
|
|
use types::{EthSpec, ForkName};
|
|
|
|
pub const GENERIC_ERROR_CODE: i64 = -1234;
|
|
pub const BAD_PARAMS_ERROR_CODE: i64 = -32602;
|
|
pub const UNKNOWN_PAYLOAD_ERROR_CODE: i64 = -38001;
|
|
pub const FORK_REQUEST_MISMATCH_ERROR_CODE: i64 = -32000;
|
|
|
|
pub async fn handle_rpc<T: EthSpec>(
|
|
body: JsonValue,
|
|
ctx: Arc<Context<T>>,
|
|
) -> Result<JsonValue, (String, i64)> {
|
|
*ctx.previous_request.lock() = Some(body.clone());
|
|
|
|
let method = body
|
|
.get("method")
|
|
.and_then(JsonValue::as_str)
|
|
.ok_or_else(|| "missing/invalid method field".to_string())
|
|
.map_err(|s| (s, GENERIC_ERROR_CODE))?;
|
|
|
|
let params = body
|
|
.get("params")
|
|
.ok_or_else(|| "missing/invalid params field".to_string())
|
|
.map_err(|s| (s, GENERIC_ERROR_CODE))?;
|
|
|
|
match method {
|
|
ETH_SYNCING => Ok(JsonValue::Bool(false)),
|
|
ETH_GET_BLOCK_BY_NUMBER => {
|
|
let tag = params
|
|
.get(0)
|
|
.and_then(JsonValue::as_str)
|
|
.ok_or_else(|| "missing/invalid params[0] value".to_string())
|
|
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
|
|
|
match tag {
|
|
"latest" => Ok(serde_json::to_value(
|
|
ctx.execution_block_generator
|
|
.read()
|
|
.latest_execution_block(),
|
|
)
|
|
.unwrap()),
|
|
other => Err((
|
|
format!("The tag {} is not supported", other),
|
|
BAD_PARAMS_ERROR_CODE,
|
|
)),
|
|
}
|
|
}
|
|
ETH_GET_BLOCK_BY_HASH => {
|
|
let hash = params
|
|
.get(0)
|
|
.and_then(JsonValue::as_str)
|
|
.ok_or_else(|| "missing/invalid params[0] value".to_string())
|
|
.and_then(|s| {
|
|
s.parse()
|
|
.map_err(|e| format!("unable to parse hash: {:?}", e))
|
|
})
|
|
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
|
|
|
// If we have a static response set, just return that.
|
|
if let Some(response) = *ctx.static_get_block_by_hash_response.lock() {
|
|
return Ok(serde_json::to_value(response).unwrap());
|
|
}
|
|
|
|
let full_tx = params
|
|
.get(1)
|
|
.and_then(JsonValue::as_bool)
|
|
.ok_or_else(|| "missing/invalid params[1] value".to_string())
|
|
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
|
if full_tx {
|
|
Ok(serde_json::to_value(
|
|
ctx.execution_block_generator
|
|
.read()
|
|
.execution_block_with_txs_by_hash(hash),
|
|
)
|
|
.unwrap())
|
|
} else {
|
|
Ok(serde_json::to_value(
|
|
ctx.execution_block_generator
|
|
.read()
|
|
.execution_block_by_hash(hash),
|
|
)
|
|
.unwrap())
|
|
}
|
|
}
|
|
ENGINE_NEW_PAYLOAD_V1 | ENGINE_NEW_PAYLOAD_V2 => {
|
|
let request = match method {
|
|
ENGINE_NEW_PAYLOAD_V1 => JsonExecutionPayload::V1(
|
|
get_param::<JsonExecutionPayloadV1<T>>(params, 0)
|
|
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
|
|
),
|
|
ENGINE_NEW_PAYLOAD_V2 => get_param::<JsonExecutionPayloadV2<T>>(params, 0)
|
|
.map(|jep| JsonExecutionPayload::V2(jep))
|
|
.or_else(|_| {
|
|
get_param::<JsonExecutionPayloadV1<T>>(params, 0)
|
|
.map(|jep| JsonExecutionPayload::V1(jep))
|
|
})
|
|
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
|
|
// TODO(4844) add that here..
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let fork = ctx
|
|
.execution_block_generator
|
|
.read()
|
|
.get_fork_at_timestamp(*request.timestamp());
|
|
// validate method called correctly according to shanghai fork time
|
|
match fork {
|
|
ForkName::Merge => {
|
|
if matches!(request, JsonExecutionPayload::V2(_)) {
|
|
return Err((
|
|
format!(
|
|
"{} called with `ExecutionPayloadV2` before Capella fork!",
|
|
method
|
|
),
|
|
GENERIC_ERROR_CODE,
|
|
));
|
|
}
|
|
}
|
|
ForkName::Capella => {
|
|
if method == ENGINE_NEW_PAYLOAD_V1 {
|
|
return Err((
|
|
format!("{} called after Capella fork!", method),
|
|
GENERIC_ERROR_CODE,
|
|
));
|
|
}
|
|
if matches!(request, JsonExecutionPayload::V1(_)) {
|
|
return Err((
|
|
format!(
|
|
"{} called with `ExecutionPayloadV1` after Capella fork!",
|
|
method
|
|
),
|
|
GENERIC_ERROR_CODE,
|
|
));
|
|
}
|
|
}
|
|
// TODO(4844) add 4844 error checking here
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
// Canned responses set by block hash take priority.
|
|
if let Some(status) = ctx.get_new_payload_status(request.block_hash()) {
|
|
return Ok(serde_json::to_value(JsonPayloadStatusV1::from(status)).unwrap());
|
|
}
|
|
|
|
let (static_response, should_import) =
|
|
if let Some(mut response) = ctx.static_new_payload_response.lock().clone() {
|
|
if response.status.status == PayloadStatusV1Status::Valid {
|
|
response.status.latest_valid_hash = Some(*request.block_hash())
|
|
}
|
|
|
|
(Some(response.status), response.should_import)
|
|
} else {
|
|
(None, true)
|
|
};
|
|
|
|
let dynamic_response = if should_import {
|
|
Some(
|
|
ctx.execution_block_generator
|
|
.write()
|
|
.new_payload(request.into()),
|
|
)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let response = static_response.or(dynamic_response).unwrap();
|
|
|
|
Ok(serde_json::to_value(JsonPayloadStatusV1::from(response)).unwrap())
|
|
}
|
|
ENGINE_GET_PAYLOAD_V1 | ENGINE_GET_PAYLOAD_V2 => {
|
|
let request: JsonPayloadIdRequest =
|
|
get_param(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
|
let id = request.into();
|
|
|
|
let response = ctx
|
|
.execution_block_generator
|
|
.write()
|
|
.get_payload(&id)
|
|
.ok_or_else(|| {
|
|
(
|
|
format!("no payload for id {:?}", id),
|
|
UNKNOWN_PAYLOAD_ERROR_CODE,
|
|
)
|
|
})?;
|
|
|
|
// validate method called correctly according to shanghai fork time
|
|
if ctx
|
|
.execution_block_generator
|
|
.read()
|
|
.get_fork_at_timestamp(response.timestamp())
|
|
== ForkName::Capella
|
|
&& method == ENGINE_GET_PAYLOAD_V1
|
|
{
|
|
return Err((
|
|
format!("{} called after Capella fork!", method),
|
|
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
|
));
|
|
}
|
|
// TODO(4844) add 4844 error checking here
|
|
|
|
match method {
|
|
ENGINE_GET_PAYLOAD_V1 => {
|
|
Ok(serde_json::to_value(JsonExecutionPayload::from(response)).unwrap())
|
|
}
|
|
ENGINE_GET_PAYLOAD_V2 => Ok(match JsonExecutionPayload::from(response) {
|
|
JsonExecutionPayload::V1(execution_payload) => {
|
|
serde_json::to_value(JsonGetPayloadResponseV1 {
|
|
execution_payload,
|
|
block_value: 0.into(),
|
|
})
|
|
.unwrap()
|
|
}
|
|
JsonExecutionPayload::V2(execution_payload) => {
|
|
serde_json::to_value(JsonGetPayloadResponseV2 {
|
|
execution_payload,
|
|
block_value: 0.into(),
|
|
})
|
|
.unwrap()
|
|
}
|
|
_ => unreachable!(),
|
|
}),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
ENGINE_FORKCHOICE_UPDATED_V1 | ENGINE_FORKCHOICE_UPDATED_V2 => {
|
|
let forkchoice_state: JsonForkchoiceStateV1 =
|
|
get_param(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
|
let payload_attributes = match method {
|
|
ENGINE_FORKCHOICE_UPDATED_V1 => {
|
|
let jpa1: Option<JsonPayloadAttributesV1> =
|
|
get_param(params, 1).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
|
jpa1.map(JsonPayloadAttributes::V1)
|
|
}
|
|
ENGINE_FORKCHOICE_UPDATED_V2 => {
|
|
// we can't use `deny_unknown_fields` without breaking compatibility with some
|
|
// clients that haven't updated to the latest engine_api spec. So instead we'll
|
|
// need to deserialize based on timestamp
|
|
get_param::<Option<JsonPayloadAttributes>>(params, 1)
|
|
.and_then(|pa| {
|
|
pa.and_then(|pa| {
|
|
match ctx
|
|
.execution_block_generator
|
|
.read()
|
|
.get_fork_at_timestamp(*pa.timestamp())
|
|
{
|
|
ForkName::Merge => {
|
|
get_param::<Option<JsonPayloadAttributesV1>>(params, 1)
|
|
.map(|opt| opt.map(JsonPayloadAttributes::V1))
|
|
.transpose()
|
|
}
|
|
ForkName::Capella => {
|
|
get_param::<Option<JsonPayloadAttributesV2>>(params, 1)
|
|
.map(|opt| opt.map(JsonPayloadAttributes::V2))
|
|
.transpose()
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
})
|
|
.transpose()
|
|
})
|
|
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
// validate method called correctly according to shanghai fork time
|
|
if let Some(pa) = payload_attributes.as_ref() {
|
|
match ctx
|
|
.execution_block_generator
|
|
.read()
|
|
.get_fork_at_timestamp(*pa.timestamp())
|
|
{
|
|
ForkName::Merge => {
|
|
if matches!(pa, JsonPayloadAttributes::V2(_)) {
|
|
return Err((
|
|
format!(
|
|
"{} called with `JsonPayloadAttributesV2` before Capella fork!",
|
|
method
|
|
),
|
|
GENERIC_ERROR_CODE,
|
|
));
|
|
}
|
|
}
|
|
ForkName::Capella => {
|
|
if method == ENGINE_FORKCHOICE_UPDATED_V1 {
|
|
return Err((
|
|
format!("{} called after Capella fork!", method),
|
|
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
|
));
|
|
}
|
|
if matches!(pa, JsonPayloadAttributes::V1(_)) {
|
|
return Err((
|
|
format!(
|
|
"{} called with `JsonPayloadAttributesV1` after Capella fork!",
|
|
method
|
|
),
|
|
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
|
));
|
|
}
|
|
}
|
|
// TODO(4844) add 4844 error checking here
|
|
_ => unreachable!(),
|
|
};
|
|
}
|
|
|
|
if let Some(hook_response) = ctx
|
|
.hook
|
|
.lock()
|
|
.on_forkchoice_updated(forkchoice_state.clone(), payload_attributes.clone())
|
|
{
|
|
return Ok(serde_json::to_value(hook_response).unwrap());
|
|
}
|
|
|
|
let head_block_hash = forkchoice_state.head_block_hash;
|
|
|
|
// Canned responses set by block hash take priority.
|
|
if let Some(status) = ctx.get_fcu_payload_status(&head_block_hash) {
|
|
let response = JsonForkchoiceUpdatedV1Response {
|
|
payload_status: JsonPayloadStatusV1::from(status),
|
|
payload_id: None,
|
|
};
|
|
return Ok(serde_json::to_value(response).unwrap());
|
|
}
|
|
|
|
let mut response = ctx
|
|
.execution_block_generator
|
|
.write()
|
|
.forkchoice_updated(
|
|
forkchoice_state.into(),
|
|
payload_attributes.map(|json| json.into()),
|
|
)
|
|
.map_err(|s| (s, GENERIC_ERROR_CODE))?;
|
|
|
|
if let Some(mut status) = ctx.static_forkchoice_updated_response.lock().clone() {
|
|
if status.status == PayloadStatusV1Status::Valid {
|
|
status.latest_valid_hash = Some(head_block_hash)
|
|
}
|
|
|
|
response.payload_status = status.into();
|
|
}
|
|
|
|
Ok(serde_json::to_value(response).unwrap())
|
|
}
|
|
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1 => {
|
|
let block_generator = ctx.execution_block_generator.read();
|
|
let transition_config: TransitionConfigurationV1 = TransitionConfigurationV1 {
|
|
terminal_total_difficulty: block_generator.terminal_total_difficulty,
|
|
terminal_block_hash: block_generator.terminal_block_hash,
|
|
terminal_block_number: block_generator.terminal_block_number,
|
|
};
|
|
Ok(serde_json::to_value(transition_config).unwrap())
|
|
}
|
|
ENGINE_EXCHANGE_CAPABILITIES => {
|
|
let engine_capabilities = ctx.engine_capabilities.read();
|
|
Ok(serde_json::to_value(engine_capabilities.to_response()).unwrap())
|
|
}
|
|
other => Err((
|
|
format!("The method {} does not exist/is not available", other),
|
|
METHOD_NOT_FOUND_CODE,
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn get_param<T: DeserializeOwned>(params: &JsonValue, index: usize) -> Result<T, String> {
|
|
params
|
|
.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[{}]: {:?}", index, e))
|
|
})
|
|
}
|