Files
lighthouse/beacon_node/execution_layer/src/test_utils/handle_rpc.rs
hopinheimer df764ffa9a Re-issue ForkchoiceUpdate based on updated PayloadStatus (#9102)
Co-Authored-By: hopinheimer <knmanas6@gmail.com>

Co-Authored-By: Michael Sproul <michael@sigmaprime.io>

Co-Authored-By: Michael Sproul <michaelsproul@users.noreply.github.com>
2026-04-25 08:04:09 +00:00

760 lines
33 KiB
Rust

use super::Context;
use crate::engine_api::{http::*, *};
use crate::json_structures::*;
use crate::test_utils::{DEFAULT_CLIENT_VERSION, DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI};
use serde::{Deserialize, de::DeserializeOwned};
use serde_json::Value as JsonValue;
use std::sync::Arc;
use tracing::debug;
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<E: EthSpec>(
body: JsonValue,
ctx: Arc<Context<E>>,
) -> 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))?;
debug!(method, "Mock execution engine");
match method {
ETH_SYNCING => ctx
.syncing_response
.lock()
.clone()
.map(JsonValue::Bool)
.map_err(|message| (message, GENERIC_ERROR_CODE)),
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()),
"0x0" => Ok(serde_json::to_value(
ctx.execution_block_generator
.read()
.genesis_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 {
Err((
"full_tx support has been removed".to_string(),
BAD_PARAMS_ERROR_CODE,
))
} 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
| ENGINE_NEW_PAYLOAD_V3
| ENGINE_NEW_PAYLOAD_V4
| ENGINE_NEW_PAYLOAD_V5 => {
let request = match method {
ENGINE_NEW_PAYLOAD_V1 => JsonExecutionPayload::Bellatrix(
get_param::<JsonExecutionPayloadBellatrix<E>>(params, 0)
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
),
ENGINE_NEW_PAYLOAD_V2 => get_param::<JsonExecutionPayloadCapella<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Capella(jep))
.or_else(|_| {
get_param::<JsonExecutionPayloadBellatrix<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Bellatrix(jep))
})
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
ENGINE_NEW_PAYLOAD_V3 => get_param::<JsonExecutionPayloadDeneb<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Deneb(jep))
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
ENGINE_NEW_PAYLOAD_V4 => get_param::<JsonExecutionPayloadFulu<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Fulu(jep))
.or_else(|_| {
get_param::<JsonExecutionPayloadElectra<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Electra(jep))
})
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
ENGINE_NEW_PAYLOAD_V5 => get_param::<JsonExecutionPayloadGloas<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Gloas(jep))
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
_ => unreachable!(),
};
let fork = ctx
.execution_block_generator
.read()
.get_fork_at_timestamp(*request.timestamp());
// validate method called correctly according to fork time
match fork {
ForkName::Bellatrix => {
if matches!(request, JsonExecutionPayload::Capella(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadCapella` 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::Bellatrix(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadBellatrix` after Capella fork!",
method
),
GENERIC_ERROR_CODE,
));
}
}
ForkName::Deneb => {
if method == ENGINE_NEW_PAYLOAD_V1 || method == ENGINE_NEW_PAYLOAD_V2 {
return Err((
format!("{} called after Deneb fork!", method),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::Bellatrix(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV1` after Deneb fork!",
method
),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::Capella(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV2` after Deneb fork!",
method
),
GENERIC_ERROR_CODE,
));
}
}
ForkName::Electra | ForkName::Fulu => {
if method == ENGINE_NEW_PAYLOAD_V1
|| method == ENGINE_NEW_PAYLOAD_V2
|| method == ENGINE_NEW_PAYLOAD_V3
{
return Err((
format!("{} called after Electra fork!", method),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::Bellatrix(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadBellatrix after Electra fork!",
method
),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::Capella(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadCapella` after Electra fork!",
method
),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::Deneb(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadDeneb` after Electra fork!",
method
),
GENERIC_ERROR_CODE,
));
}
}
ForkName::Gloas => {
if method != ENGINE_NEW_PAYLOAD_V5 {
return Err((
format!("{} called after Gloas fork!", method),
GENERIC_ERROR_CODE,
));
}
}
_ => unreachable!(),
};
// Canned responses set by block hash take priority.
if let Some(status) = ctx.get_new_payload_status(request.block_hash()) {
return status
.map(|status| serde_json::to_value(JsonPayloadStatusV1::from(status)).unwrap())
.map_err(|message| (message, GENERIC_ERROR_CODE));
}
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.try_into().unwrap()),
)
} 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
| ENGINE_GET_PAYLOAD_V3
| ENGINE_GET_PAYLOAD_V4
| ENGINE_GET_PAYLOAD_V5
| ENGINE_GET_PAYLOAD_V6 => {
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,
)
})?;
let maybe_blobs = ctx.execution_block_generator.write().get_blobs_bundle(&id);
let maybe_execution_requests = ctx
.execution_block_generator
.read()
.get_execution_requests(&id);
// 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,
));
}
// validate method called correctly according to cancun fork time
if ctx
.execution_block_generator
.read()
.get_fork_at_timestamp(response.timestamp())
== ForkName::Deneb
&& (method == ENGINE_GET_PAYLOAD_V1 || method == ENGINE_GET_PAYLOAD_V2)
{
return Err((
format!("{} called after Deneb fork!", method),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
// validate method called correctly according to prague fork time
if ctx
.execution_block_generator
.read()
.get_fork_at_timestamp(response.timestamp())
== ForkName::Electra
&& (method == ENGINE_GET_PAYLOAD_V1
|| method == ENGINE_GET_PAYLOAD_V2
|| method == ENGINE_GET_PAYLOAD_V3)
{
return Err((
format!("{} called after Electra fork!", method),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
// validate method called correctly according to osaka fork time
if ctx
.execution_block_generator
.read()
.get_fork_at_timestamp(response.timestamp())
== ForkName::Fulu
&& (method == ENGINE_GET_PAYLOAD_V1
|| method == ENGINE_GET_PAYLOAD_V2
|| method == ENGINE_GET_PAYLOAD_V3
|| method == ENGINE_GET_PAYLOAD_V4)
{
return Err((
format!("{} called after Fulu fork!", method),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
// validate method called correctly according to amsterdam fork time
if ctx
.execution_block_generator
.read()
.get_fork_at_timestamp(response.timestamp())
== ForkName::Gloas
&& (method == ENGINE_GET_PAYLOAD_V1
|| method == ENGINE_GET_PAYLOAD_V2
|| method == ENGINE_GET_PAYLOAD_V3
|| method == ENGINE_GET_PAYLOAD_V4
|| method == ENGINE_GET_PAYLOAD_V5)
{
return Err((
format!("{} called after Gloas fork!", method),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
match method {
ENGINE_GET_PAYLOAD_V1 => Ok(serde_json::to_value(
JsonExecutionPayload::try_from(response).unwrap(),
)
.unwrap()),
ENGINE_GET_PAYLOAD_V2 => {
Ok(match JsonExecutionPayload::try_from(response).unwrap() {
JsonExecutionPayload::Bellatrix(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseBellatrix {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
})
.unwrap()
}
JsonExecutionPayload::Capella(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseCapella {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
})
.unwrap()
}
_ => unreachable!(),
})
}
// From v3 onwards, we use the getPayload version only for the corresponding
// ExecutionPayload version. So we return an error if the ExecutionPayload version
// we get does not correspond to the getPayload version.
ENGINE_GET_PAYLOAD_V3 => {
Ok(match JsonExecutionPayload::try_from(response).unwrap() {
JsonExecutionPayload::Deneb(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseDeneb {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
blobs_bundle: maybe_blobs
.ok_or((
"No blobs returned despite V3 Payload".to_string(),
GENERIC_ERROR_CODE,
))?
.into(),
should_override_builder: false,
})
.unwrap()
}
_ => unreachable!(),
})
}
ENGINE_GET_PAYLOAD_V4 => {
Ok(match JsonExecutionPayload::try_from(response).unwrap() {
JsonExecutionPayload::Electra(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseElectra {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
blobs_bundle: maybe_blobs
.ok_or((
"No blobs returned despite V4 Payload".to_string(),
GENERIC_ERROR_CODE,
))?
.into(),
should_override_builder: false,
execution_requests: maybe_execution_requests
.clone()
.unwrap_or_default()
.into(),
})
.unwrap()
}
_ => unreachable!(),
})
}
ENGINE_GET_PAYLOAD_V5 => {
Ok(match JsonExecutionPayload::try_from(response).unwrap() {
JsonExecutionPayload::Fulu(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseFulu {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
blobs_bundle: maybe_blobs
.ok_or((
"No blobs returned despite V5 Payload".to_string(),
GENERIC_ERROR_CODE,
))?
.into(),
should_override_builder: false,
execution_requests: maybe_execution_requests
.clone()
.unwrap_or_default()
.into(),
})
.unwrap()
}
_ => unreachable!(),
})
}
ENGINE_GET_PAYLOAD_V6 => {
Ok(match JsonExecutionPayload::try_from(response).unwrap() {
JsonExecutionPayload::Gloas(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseGloas {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
blobs_bundle: maybe_blobs
.ok_or((
"No blobs returned despite V6 Payload".to_string(),
GENERIC_ERROR_CODE,
))?
.into(),
should_override_builder: false,
execution_requests: maybe_execution_requests
.unwrap_or_default()
.into(),
})
.unwrap()
}
_ => unreachable!(),
})
}
_ => unreachable!(),
}
}
ENGINE_GET_BLOBS_V1 => {
let versioned_hashes =
get_param::<Vec<Hash256>>(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
let generator = ctx.execution_block_generator.read();
// V1: per-element nullable array, positionally matching the request.
let response: Vec<Option<BlobAndProofV1<E>>> = versioned_hashes
.iter()
.map(|hash| match generator.get_blob_and_proof(hash) {
Some(BlobAndProof::V1(v1)) => Some(v1),
_ => None,
})
.collect();
Ok(serde_json::to_value(response).unwrap())
}
ENGINE_GET_BLOBS_V2 => {
let versioned_hashes =
get_param::<Vec<Hash256>>(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
let generator = ctx.execution_block_generator.read();
// V2: all-or-nothing — null if any blob is missing.
let results: Vec<Option<BlobAndProofV2<E>>> = versioned_hashes
.iter()
.map(|hash| match generator.get_blob_and_proof(hash) {
Some(BlobAndProof::V2(v2)) => Some(v2),
_ => None,
})
.collect();
let response: Option<Vec<BlobAndProofV2<E>>> = results.into_iter().collect();
Ok(serde_json::to_value(response).unwrap())
}
ENGINE_FORKCHOICE_UPDATED_V1
| ENGINE_FORKCHOICE_UPDATED_V2
| ENGINE_FORKCHOICE_UPDATED_V3
| ENGINE_FORKCHOICE_UPDATED_V4 => {
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::Bellatrix => {
get_param::<Option<JsonPayloadAttributesV1>>(params, 1)
.map(|opt| opt.map(JsonPayloadAttributes::V1))
.transpose()
}
ForkName::Capella
| ForkName::Deneb
| ForkName::Electra
| ForkName::Fulu
| ForkName::Gloas => {
get_param::<Option<JsonPayloadAttributesV2>>(params, 1)
.map(|opt| opt.map(JsonPayloadAttributes::V2))
.transpose()
}
_ => unreachable!(),
}
})
.transpose()
})
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
}
ENGINE_FORKCHOICE_UPDATED_V3 => {
get_param::<Option<JsonPayloadAttributesV3>>(params, 1)
.map(|opt| opt.map(JsonPayloadAttributes::V3))
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
}
ENGINE_FORKCHOICE_UPDATED_V4 => {
get_param::<Option<JsonPayloadAttributesV4>>(params, 1)
.map(|opt| opt.map(JsonPayloadAttributes::V4))
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
}
_ => unreachable!(),
};
debug!(
?payload_attributes,
?forkchoice_state,
"ENGINE_FORKCHOICE_UPDATED"
);
// validate method called correctly according to fork time
if let Some(pa) = payload_attributes.as_ref() {
match ctx
.execution_block_generator
.read()
.get_fork_at_timestamp(*pa.timestamp())
{
ForkName::Bellatrix => {
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 method == ENGINE_FORKCHOICE_UPDATED_V3 {
return Err((
format!(
"{} called with `JsonPayloadAttributesV3` before Deneb fork!",
method
),
GENERIC_ERROR_CODE,
));
}
if matches!(pa, JsonPayloadAttributes::V1(_)) {
return Err((
format!(
"{} called with `JsonPayloadAttributesV1` after Capella fork!",
method
),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
}
ForkName::Deneb | ForkName::Electra | ForkName::Fulu => {
if method == ENGINE_FORKCHOICE_UPDATED_V1 {
return Err((
format!("{} called after Deneb fork!", method),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
if method == ENGINE_FORKCHOICE_UPDATED_V2 {
return Err((
format!("{} called after Deneb fork!", method),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
}
ForkName::Gloas => {
if method != ENGINE_FORKCHOICE_UPDATED_V4 {
return Err((
format!("{} called after Gloas fork! Use V4.", method),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
}
_ => 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) {
return status
.map(|status| {
let response = JsonForkchoiceUpdatedV1Response {
payload_status: JsonPayloadStatusV1::from(status),
payload_id: None,
};
serde_json::to_value(response).unwrap()
})
.map_err(|message| (message, GENERIC_ERROR_CODE));
}
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_CAPABILITIES => {
let engine_capabilities = ctx.engine_capabilities.read();
Ok(serde_json::to_value(engine_capabilities.to_response()).unwrap())
}
ENGINE_GET_CLIENT_VERSION_V1 => {
Ok(serde_json::to_value([DEFAULT_CLIENT_VERSION.clone()]).unwrap())
}
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1 => {
#[derive(Deserialize)]
#[serde(transparent)]
struct Quantity(#[serde(with = "serde_utils::u64_hex_be")] pub u64);
let start = get_param::<Quantity>(params, 0)
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
.0;
let count = get_param::<Quantity>(params, 1)
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
.0;
let mut response = vec![];
for block_num in start..(start + count) {
let maybe_payload = ctx
.execution_block_generator
.read()
.execution_payload_by_number(block_num);
match maybe_payload {
Some(payload) => {
let payload_body: ExecutionPayloadBodyV1<E> = ExecutionPayloadBodyV1 {
transactions: payload.transactions().clone(),
withdrawals: payload.withdrawals().ok().cloned(),
};
let json_payload_body: JsonExecutionPayloadBodyV1<E> =
payload_body.try_into().unwrap();
response.push(Some(json_payload_body));
}
None => response.push(None),
}
}
Ok(serde_json::to_value(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))
})
}