Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix eth RPC call context and fix gas estimate pipeline #2633

Merged
merged 16 commits into from
Oct 31, 2023
Merged
1 change: 1 addition & 0 deletions lib/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 5 additions & 13 deletions lib/ain-evm/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,16 +127,14 @@ pub struct EVMCoreService {
tx_validation_cache: TxValidationCache,
}
pub struct EthCallArgs<'a> {
pub caller: Option<H160>,
pub caller: H160,
pub to: Option<H160>,
pub value: U256,
pub data: &'a [u8],
pub gas_limit: u64,
pub gas_price: Option<U256>,
pub max_fee_per_gas: Option<U256>,
pub gas_price: U256,
pub access_list: AccessList,
pub block_number: U256,
pub transaction_type: Option<U256>,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -234,10 +232,8 @@ impl EVMCoreService {
data,
gas_limit,
gas_price,
max_fee_per_gas,
access_list,
block_number,
transaction_type,
} = arguments;

let (
Expand Down Expand Up @@ -269,12 +265,8 @@ impl EVMCoreService {
);
debug!("[call] caller: {:?}", caller);
let vicinity = Vicinity {
gas_price: if transaction_type == Some(U256::from(2)) {
max_fee_per_gas.unwrap_or_default()
} else {
gas_price.unwrap_or_default()
},
origin: caller.unwrap_or_default(),
gas_price,
origin: caller,
beneficiary,
block_number,
timestamp: U256::from(timestamp),
Expand All @@ -295,7 +287,7 @@ impl EVMCoreService {
.map_err(|e| format_err!("------ Could not restore backend {}", e))?;

Ok(AinExecutor::new(&mut backend).call(ExecutorContext {
caller: caller.unwrap_or_default(),
caller,
to,
value,
data,
Expand Down
1 change: 1 addition & 0 deletions lib/ain-grpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ain-evm = { path = "../ain-evm" }
ain-cpp-imports = { path = "../ain-cpp-imports" }
cxx.workspace = true
env_logger.workspace = true
evm = { workspace = true, default-features = false, features = ["with-serde"] }
jsonrpsee = { workspace = true, features = ["server", "macros", "http-client"] }
jsonrpsee-server.workspace = true
lazy_static.workspace = true
Expand Down
68 changes: 68 additions & 0 deletions lib/ain-grpc/src/call_request.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use ain_evm::bytes::Bytes;
use ethereum::AccessListItem;
use ethereum_types::{H160, U256};
use jsonrpsee::core::Error;
use serde::Deserialize;

use crate::errors::RPCError;

/// Call request
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
Expand Down Expand Up @@ -34,3 +37,68 @@ pub struct CallRequest {
#[serde(rename = "type")]
pub transaction_type: Option<U256>,
}

impl CallRequest {
pub fn get_effective_gas_price(&self, block_base_fee: U256) -> Result<U256, Error> {
if self.gas_price.is_some()
&& (self.max_fee_per_gas.is_some() || self.max_priority_fee_per_gas.is_some())
{
return Err(RPCError::InvalidGasPrice.into());
}

match self.transaction_type {
// Legacy
Some(tx_type) if tx_type == U256::zero() => {
if let Some(gas_price) = self.gas_price {
return Ok(gas_price);
} else {
return Ok(block_base_fee);
}
}
// EIP2930
Some(tx_type) if tx_type == U256::one() => {
if let Some(gas_price) = self.gas_price {
return Ok(gas_price);
} else {
return Ok(block_base_fee);
}
}
// EIP1559
Some(tx_type) if tx_type == U256::from(2) => {
if let Some(max_fee_per_gas) = self.max_fee_per_gas {
return Ok(max_fee_per_gas);
} else {
return Ok(block_base_fee);
}
}
None => (),
_ => return Err(RPCError::InvalidTransactionType.into()),
}

if let Some(gas_price) = self.gas_price {
Ok(gas_price)
} else if let Some(gas_price) = self.max_fee_per_gas {
Ok(gas_price)
} else {
Ok(block_base_fee)
}
}

// https://github.com/ethereum/go-ethereum/blob/281e8cd5abaac86ed3f37f98250ff147b3c9fe62/internal/ethapi/transaction_args.go#L67
// We accept "data" and "input" for backwards-compatibility reasons.
// "input" is the newer name and should be preferred by clients.
// Issue detail: https://github.com/ethereum/go-ethereum/issues/15628
pub fn get_data(&self) -> Result<Bytes, Error> {
if self.data.is_some() && self.input.is_some() {
return Err(RPCError::InvalidDataInput.into());
}

if let Some(data) = self.data.clone() {
Ok(data)
} else if let Some(data) = self.input.clone() {
Ok(data)
} else {
Ok(Default::default())
}
}
}
62 changes: 62 additions & 0 deletions lib/ain-grpc/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use ain_evm::EVMError;
use jsonrpsee::core::Error;

pub enum RPCError {
AccountError,
BlockNotFound,
Error(Box<dyn std::error::Error>),
EvmError(EVMError),
FromBlockGreaterThanToBlock,
GasCapTooLow(u64),
InsufficientFunds,
InvalidBlockInput,
InvalidDataInput,
InvalidLogFilter,
InvalidGasPrice,
InvalidTransactionMessage,
InvalidTransactionType,
NonceCacheError,
StateRootNotFound,
TxExecutionFailed,
ValueOverflow,
}

impl From<RPCError> for Error {
fn from(e: RPCError) -> Self {
match e {
RPCError::AccountError => to_custom_err("error getting account"),
RPCError::BlockNotFound => to_custom_err("header not found"),
RPCError::Error(e) => Error::Custom(format!("{e:?}")),
RPCError::EvmError(e) => Error::Custom(format!("error calling EVM : {e:?}")),
RPCError::FromBlockGreaterThanToBlock => {
to_custom_err("fromBlock is greater than toBlock")
}
RPCError::GasCapTooLow(cap) => {
Error::Custom(format!("gas required exceeds allowance {:#?}", cap))
}
RPCError::InsufficientFunds => to_custom_err("insufficient funds for transfer"),
RPCError::InvalidBlockInput => {
to_custom_err("both blockHash and fromBlock/toBlock specified")
}
RPCError::InvalidDataInput => {
to_custom_err("data and input fields are mutually exclusive")
}
RPCError::InvalidGasPrice => {
to_custom_err("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
}
RPCError::InvalidLogFilter => to_custom_err("invalid log filter"),
RPCError::InvalidTransactionMessage => {
to_custom_err("invalid transaction message parameters")
}
RPCError::InvalidTransactionType => to_custom_err("invalid transaction type specified"),
RPCError::NonceCacheError => to_custom_err("could not cache account nonce"),
RPCError::StateRootNotFound => to_custom_err("state root not found"),
RPCError::TxExecutionFailed => to_custom_err("transaction execution failed"),
RPCError::ValueOverflow => to_custom_err("value overflow"),
}
}
}

pub fn to_custom_err<T: ToString>(e: T) -> Error {
Error::Custom(e.to_string())
}
1 change: 1 addition & 0 deletions lib/ain-grpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ extern crate serde_json;
pub mod block;
pub mod call_request;
pub mod codegen;
mod errors;
mod filters;
mod impls;
pub mod logging;
Expand Down
123 changes: 42 additions & 81 deletions lib/ain-grpc/src/rpc/debug.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{cmp, sync::Arc};
use std::sync::Arc;

use ain_evm::{
core::EthCallArgs, evm::EVMServices, executor::TxResponse, storage::block_store::DumpArg,
Expand All @@ -12,8 +12,10 @@ use jsonrpsee::{
use log::debug;
use rlp::{Decodable, Rlp};

use super::to_jsonrpsee_custom_error;
use crate::call_request::CallRequest;
use crate::{
call_request::CallRequest,
errors::{to_custom_err, RPCError},
};

#[derive(Serialize, Deserialize)]
pub struct FeeEstimate {
Expand Down Expand Up @@ -47,7 +49,7 @@ pub trait MetachainDebugRPC {

// Get transaction fee estimate
#[method(name = "feeEstimate")]
fn fee_estimate(&self, input: CallRequest) -> RpcResult<FeeEstimate>;
fn fee_estimate(&self, call: CallRequest) -> RpcResult<FeeEstimate>;
}

pub struct MetachainDebugRPCModule {
Expand Down Expand Up @@ -80,7 +82,7 @@ impl MetachainDebugRPCServer for MetachainDebugRPCModule {
self.handler
.storage
.dump_db(arg.unwrap_or(DumpArg::All), start, limit)
.map_err(to_jsonrpsee_custom_error)
.map_err(to_custom_err)
}

fn log_account_states(&self) -> RpcResult<()> {
Expand All @@ -107,104 +109,63 @@ impl MetachainDebugRPCServer for MetachainDebugRPCModule {
Ok(())
}

fn fee_estimate(&self, input: CallRequest) -> RpcResult<FeeEstimate> {
let CallRequest {
from,
to,
gas,
value,
data,
access_list,
transaction_type,
gas_price,
max_fee_per_gas,
max_priority_fee_per_gas,
..
} = input;
fn fee_estimate(&self, call: CallRequest) -> RpcResult<FeeEstimate> {
debug!(target:"rpc", "Fee estimate");
let caller = call.from.unwrap_or_default();
let byte_data = call.get_data()?;
let data = byte_data.0.as_slice();

// Get latest block information
let (block_hash, block_number) = self
.handler
.block
.get_latest_block_hash_and_number()
.map_err(to_jsonrpsee_custom_error)?
.ok_or(Error::Custom(
"Error fetching latest block hash and number".to_string(),
))?;
let base_fee = self
.map_err(to_custom_err)?
.unwrap_or_default();

// Get gas
let block_gas_limit = self
.handler
.storage
.get_attributes_or_default()
.map_err(to_custom_err)?
.block_gas_limit;
let gas_limit = u64::try_from(call.gas.unwrap_or(U256::from(block_gas_limit)))
.map_err(to_custom_err)?;

// Get gas price
let block_base_fee = self
.handler
.block
.calculate_base_fee(block_hash)
.map_err(to_jsonrpsee_custom_error)?;
let Ok(gas_limit) = u64::try_from(gas.ok_or(Error::Custom(
"Cannot get fee estimate without specifying gas limit".to_string(),
))?) else {
return Err(Error::Custom(
"Cannot get fee estimate, gas value overflow".to_string(),
));
};
.map_err(to_custom_err)?;
let gas_price = call.get_effective_gas_price(block_base_fee)?;

let TxResponse { used_gas, .. } = self
.handler
.core
.call(EthCallArgs {
caller: from,
to,
value: value.unwrap_or_default(),
data: &data.map(|d| d.0).unwrap_or_default(),
caller,
to: call.to,
value: call.value.unwrap_or_default(),
data,
gas_limit,
gas_price,
max_fee_per_gas,
access_list: access_list.unwrap_or_default(),
access_list: call.access_list.unwrap_or_default(),
block_number,
transaction_type,
})
.map_err(|e| Error::Custom(format!("Error calling EVM : {e:?}")))?;
.map_err(RPCError::EvmError)?;

let used_gas = U256::from(used_gas);
let gas_fee = match transaction_type {
// Legacy
None => {
let Some(gas_price) = gas_price else {
return Err(Error::Custom(
"Cannot get Legacy TX fee estimate without gas price".to_string()
));
};
used_gas.checked_mul(gas_price)
}
// EIP2930
Some(typ) if typ == U256::one() => {
let Some(gas_price) = gas_price else {
return Err(Error::Custom(
"Cannot get EIP2930 TX fee estimate without gas price".to_string()
));
};
used_gas.checked_mul(gas_price)
}
// EIP1559
Some(typ) if typ == U256::from(2) => {
let (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) =
(max_fee_per_gas, max_priority_fee_per_gas)
else {
return Err(Error::Custom("Cannot get EIP1559 TX fee estimate without max_fee_per_gas and max_priority_fee_per_gas".to_string()));
};
let gas_fee = cmp::min(max_fee_per_gas, max_priority_fee_per_gas.checked_add(base_fee).ok_or_else(|| Error::Custom("max_priority_fee_per_gas overflow".to_string()))?);
used_gas.checked_mul(gas_fee)
}
_ => {
return Err(Error::Custom(
"Wrong transaction type. Should be either None, 1 or 2".to_string()
))
}
}.ok_or(Error::Custom(
"Cannot get fee estimate, fee value overflow".to_string()
))?;

let gas_fee = used_gas
.checked_mul(gas_price)
.ok_or(RPCError::ValueOverflow)?;
let burnt_fee = used_gas
.checked_mul(base_fee)
.ok_or_else(|| Error::Custom("burnt_fee overflow".to_string()))?;
.checked_mul(block_base_fee)
.ok_or(RPCError::ValueOverflow)?;
let priority_fee = gas_fee
.checked_sub(burnt_fee)
.ok_or_else(|| Error::Custom("priority_fee underflow".to_string()))?;
.ok_or(RPCError::ValueOverflow)?;

Ok(FeeEstimate {
used_gas,
Expand Down
Loading