fix(rpc): support both input and data fields (#3911)

This commit is contained in:
Matthias Seitz
2023-07-25 18:33:13 +02:00
committed by GitHub
parent af604289bb
commit 4a24ae2555
5 changed files with 95 additions and 10 deletions

View File

@ -19,11 +19,9 @@ pub struct CallRequest {
pub gas: Option<U256>,
/// Value
pub value: Option<U256>,
/// Transaction data
///
/// This accepts both `input` and `data`
#[serde(alias = "input")]
pub data: Option<Bytes>,
/// Transaction input data
#[serde(default, flatten)]
pub input: CallInput,
/// Nonce
pub nonce: Option<U256>,
/// chain id
@ -44,6 +42,71 @@ impl CallRequest {
}
}
/// Helper type that supports both `data` and `input` fields that map to transaction input data.
///
/// This is done for compatibility reasons where older implementations used `data` instead of the
/// newer, recommended `input` field.
///
/// If both fields are set, it is expected that they contain the same value, otherwise an error is
/// returned.
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct CallInput {
/// Transaction data
pub input: Option<Bytes>,
/// Transaction data
///
/// This is the same as `input` but is used for backwards compatibility: <https://github.com/ethereum/go-ethereum/issues/15628>
pub data: Option<Bytes>,
}
impl CallInput {
/// Consumes the type and returns the optional input data.
///
/// Returns an error if both `data` and `input` fields are set and not equal.
pub fn try_into_unique_input(self) -> Result<Option<Bytes>, CallInputError> {
let Self { input, data } = self;
match (input, data) {
(Some(input), Some(data)) if input == data => Ok(Some(input)),
(Some(_), Some(_)) => Err(CallInputError::default()),
(Some(input), None) => Ok(Some(input)),
(None, Some(data)) => Ok(Some(data)),
(None, None) => Ok(None),
}
}
/// Consumes the type and returns the optional input data.
///
/// Returns an error if both `data` and `input` fields are set and not equal.
pub fn unique_input(&self) -> Result<Option<&Bytes>, CallInputError> {
let Self { input, data } = self;
match (input, data) {
(Some(input), Some(data)) if input == data => Ok(Some(input)),
(Some(_), Some(_)) => Err(CallInputError::default()),
(Some(input), None) => Ok(Some(input)),
(None, Some(data)) => Ok(Some(data)),
(None, None) => Ok(None),
}
}
}
impl From<Bytes> for CallInput {
fn from(input: Bytes) -> Self {
Self { input: Some(input), data: None }
}
}
impl From<Option<Bytes>> for CallInput {
fn from(input: Option<Bytes>) -> Self {
Self { input, data: None }
}
}
/// Error thrown when both `data` and `input` fields are set and not equal.
#[derive(Debug, Default, thiserror::Error)]
#[error("both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data")]
#[non_exhaustive]
pub struct CallInputError;
#[cfg(test)]
mod tests {
use super::*;
@ -53,4 +116,23 @@ mod tests {
let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let _req = serde_json::from_str::<CallRequest>(s).unwrap();
}
#[test]
fn serde_unique_call_input() {
let s = r#"{"accessList":[],"data":"0x0902f1ac", "input":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let req = serde_json::from_str::<CallRequest>(s).unwrap();
assert!(req.input.try_into_unique_input().unwrap().is_some());
let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let req = serde_json::from_str::<CallRequest>(s).unwrap();
assert!(req.input.try_into_unique_input().unwrap().is_some());
let s = r#"{"accessList":[],"input":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let req = serde_json::from_str::<CallRequest>(s).unwrap();
assert!(req.input.try_into_unique_input().unwrap().is_some());
let s = r#"{"accessList":[],"data":"0x0902f1ac", "input":"0x0902f1","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#;
let req = serde_json::from_str::<CallRequest>(s).unwrap();
assert!(req.input.try_into_unique_input().is_err());
}
}

View File

@ -19,7 +19,7 @@ mod work;
pub use account::*;
pub use block::*;
pub use call::CallRequest;
pub use call::{CallInput, CallInputError, CallRequest};
pub use fee::{FeeHistory, TxGasAndReward};
pub use filter::*;
pub use index::Index;

View File

@ -423,7 +423,7 @@ where
gas_price: Some(U256::from(gas_price)),
max_fee_per_gas: Some(U256::from(max_fee_per_gas)),
value: request.value,
data: request.data.clone(),
input: request.data.clone().into(),
nonce: request.nonce,
chain_id: Some(chain_id),
access_list: request.access_list.clone(),

View File

@ -7,7 +7,7 @@ use jsonrpsee::{
};
use reth_primitives::{abi::decode_revert_reason, Address, Bytes, U256};
use reth_revm::tracing::js::JsInspectorError;
use reth_rpc_types::{error::EthRpcErrorCode, BlockError};
use reth_rpc_types::{error::EthRpcErrorCode, BlockError, CallInputError};
use reth_transaction_pool::error::{InvalidPoolTransactionError, PoolError};
use revm::primitives::{EVMError, ExecutionResult, Halt, OutOfGasError};
use std::time::Duration;
@ -90,6 +90,8 @@ pub enum EthApiError {
/// Internal Error thrown by the javascript tracer
#[error("{0}")]
InternalJsTracerError(String),
#[error(transparent)]
CallInputError(#[from] CallInputError),
}
impl From<EthApiError> for ErrorObject<'static> {
@ -124,6 +126,7 @@ impl From<EthApiError> for ErrorObject<'static> {
}
err @ EthApiError::InternalTracingError => internal_rpc_err(err.to_string()),
err @ EthApiError::InternalEthError => internal_rpc_err(err.to_string()),
err @ EthApiError::CallInputError(_) => invalid_params_rpc_err(err.to_string()),
}
}
}

View File

@ -282,7 +282,7 @@ pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthR
max_priority_fee_per_gas,
gas,
value,
data,
input,
nonce,
access_list,
chain_id,
@ -308,7 +308,7 @@ pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthR
gas_priority_fee: max_priority_fee_per_gas,
transact_to: to.map(TransactTo::Call).unwrap_or_else(TransactTo::create),
value: value.unwrap_or_default(),
data: data.map(|data| data.0).unwrap_or_default(),
data: input.try_into_unique_input()?.map(|data| data.0).unwrap_or_default(),
chain_id: chain_id.map(|c| c.as_u64()),
access_list: access_list.map(AccessList::flattened).unwrap_or_default(),
};