From a078cc488bb8b6e774efc2e5f441572f5acc36e7 Mon Sep 17 00:00:00 2001 From: harry Date: Fri, 6 Jan 2023 09:28:29 +0800 Subject: [PATCH] simplify eip1559 support --- src/components/config/src/abci/mod.rs | 3 + .../contracts/primitives/rpc-core/Cargo.toml | 1 + .../primitives/rpc-core/src/types/block.rs | 6 +- .../rpc-core/src/types/call_request.rs | 4 + .../primitives/rpc-core/src/types/receipt.rs | 2 + .../rpc-core/src/types/transaction.rs | 16 +- .../rpc-core/src/types/transaction_request.rs | 13 +- src/components/contracts/rpc/src/eth.rs | 179 +++++++++++++++++- 8 files changed, 211 insertions(+), 13 deletions(-) diff --git a/src/components/config/src/abci/mod.rs b/src/components/config/src/abci/mod.rs index b97a41c6b7..f201934266 100644 --- a/src/components/config/src/abci/mod.rs +++ b/src/components/config/src/abci/mod.rs @@ -88,6 +88,7 @@ pub struct CheckPointConfig { // Fix the amount in the delegators that staking did not modify when it punished the validator. pub fix_delegators_am_height: u64, pub validators_limit_v2_height: u64, + pub enable_eip1559_height: u64, } impl CheckPointConfig { @@ -127,6 +128,7 @@ impl CheckPointConfig { proper_gas_set_height: 0, fix_delegators_am_height: 0, validators_limit_v2_height: 0, + enable_eip1559_height: 0, }; #[cfg(not(feature = "debug_env"))] let config = CheckPointConfig { @@ -156,6 +158,7 @@ impl CheckPointConfig { fix_undelegation_missing_reward_height: 3000000, fix_delegators_am_height: 30000000, validators_limit_v2_height: 30000000, + enable_eip1559_height: 40000000, }; let content = toml::to_string(&config).unwrap(); file.write_all(content.as_bytes()).unwrap(); diff --git a/src/components/contracts/primitives/rpc-core/Cargo.toml b/src/components/contracts/primitives/rpc-core/Cargo.toml index 81617b82fa..fe9bee55a6 100644 --- a/src/components/contracts/primitives/rpc-core/Cargo.toml +++ b/src/components/contracts/primitives/rpc-core/Cargo.toml @@ -9,6 +9,7 @@ description = "RPC traits of Ethereum." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] +ethereum = { version = "0.12.0", default-features = false, features = ["with-serde"] } ethereum-types = "0.13.1" futures = "0.3.16" jsonrpc-core = { git = "https://github.com/FindoraNetwork/jsonrpc.git", package = "jsonrpc-core" } diff --git a/src/components/contracts/primitives/rpc-core/src/types/block.rs b/src/components/contracts/primitives/rpc-core/src/types/block.rs index 5786136daf..bd1d762c6e 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/block.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/block.rs @@ -21,8 +21,7 @@ use std::ops::Deref; use crate::types::{Bytes, Transaction}; use ethereum_types::{Bloom as H2048, H160, H256, U256}; -use serde::ser::Error; -use serde::{Serialize, Serializer}; +use serde::{ser::Error, Serialize, Serializer}; /// Block Transactions #[derive(Debug)] @@ -90,6 +89,9 @@ pub struct Block { pub transactions: BlockTransactions, /// Size in bytes pub size: Option, + /// Base Fee for post-EIP1559 blocks. + #[serde(skip_serializing_if = "Option::is_none")] + pub base_fee_per_gas: Option, } /// Block header representation. diff --git a/src/components/contracts/primitives/rpc-core/src/types/call_request.rs b/src/components/contracts/primitives/rpc-core/src/types/call_request.rs index 0451dbaf65..2e965b9c99 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/call_request.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/call_request.rs @@ -31,6 +31,10 @@ pub struct CallRequest { pub to: Option, /// Gas Price pub gas_price: Option, + /// EIP-1559 Max base fee the caller is willing to pay + pub max_fee_per_gas: Option, + /// EIP-1559 Priority fee the caller is paying to the block author + pub max_priority_fee_per_gas: Option, /// Gas pub gas: Option, /// Value diff --git a/src/components/contracts/primitives/rpc-core/src/types/receipt.rs b/src/components/contracts/primitives/rpc-core/src/types/receipt.rs index 5d1ed929ed..af4542f75f 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/receipt.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/receipt.rs @@ -54,4 +54,6 @@ pub struct Receipt { // NOTE(niklasad1): Unknown after EIP98 rules, if it's missing then skip serializing it #[serde(skip_serializing_if = "Option::is_none", rename = "status")] pub status_code: Option, + /// Effective gas price. Pre-eip1559 this is just the gasprice. Post-eip1559 this is base fee + priority fee. + pub effective_gas_price: U256, } diff --git a/src/components/contracts/primitives/rpc-core/src/types/transaction.rs b/src/components/contracts/primitives/rpc-core/src/types/transaction.rs index 6d767f40c3..6bc36743a6 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/transaction.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/transaction.rs @@ -17,9 +17,9 @@ // along with this program. If not, see . use crate::types::Bytes; +use ethereum::AccessListItem; use ethereum_types::{H160, H256, H512, U256, U64}; -use serde::ser::SerializeStruct; -use serde::{Serialize, Serializer}; +use serde::{ser::SerializeStruct, Serialize, Serializer}; use std::{ collections::HashMap, sync::{Arc, Mutex}, @@ -46,7 +46,14 @@ pub struct Transaction { /// Transfered value pub value: U256, /// Gas Price - pub gas_price: U256, + #[serde(skip_serializing_if = "Option::is_none")] + pub gas_price: Option, + /// eip1559. Max BaseFeePerGas the user is willing to pay. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_fee_per_gas: Option, + /// eip1559.The miner's tip. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, /// Gas pub gas: U256, /// Data @@ -67,6 +74,9 @@ pub struct Transaction { pub r: U256, /// The S field of the signature. pub s: U256, + /// eip1559. Pre-pay to warm storage access. + #[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none"))] + pub access_list: Option>, } /// Local Transaction Status diff --git a/src/components/contracts/primitives/rpc-core/src/types/transaction_request.rs b/src/components/contracts/primitives/rpc-core/src/types/transaction_request.rs index bbddf8d5f4..c5ab601a6f 100644 --- a/src/components/contracts/primitives/rpc-core/src/types/transaction_request.rs +++ b/src/components/contracts/primitives/rpc-core/src/types/transaction_request.rs @@ -19,11 +19,12 @@ //! `TransactionRequest` type use crate::types::Bytes; +use ethereum::AccessListItem; use ethereum_types::{H160, U256}; use serde::{Deserialize, Serialize}; /// Transaction request coming from RPC -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] pub struct TransactionRequest { @@ -32,7 +33,14 @@ pub struct TransactionRequest { /// Recipient pub to: Option, /// Gas Price + #[serde(default)] pub gas_price: Option, + /// Max BaseFeePerGas the user is willing to pay. + #[serde(default)] + pub max_fee_per_gas: Option, + /// The miner's tip. + #[serde(default)] + pub max_priority_fee_per_gas: Option, /// Gas pub gas: Option, /// Value of transaction in wei @@ -41,4 +49,7 @@ pub struct TransactionRequest { pub data: Option, /// Transaction's nonce pub nonce: Option, + /// Pre-pay to warm storage access. + #[serde(default)] + pub access_list: Option>, } diff --git a/src/components/contracts/rpc/src/eth.rs b/src/components/contracts/rpc/src/eth.rs index 5f572cb1b3..fa5b362d0a 100644 --- a/src/components/contracts/rpc/src/eth.rs +++ b/src/components/contracts/rpc/src/eth.rs @@ -8,7 +8,7 @@ use baseapp::{extensions::SignedExtra, BaseApp}; use config::abci::global_cfg::CFG; use ethereum::{ BlockV0 as EthereumBlock, LegacyTransactionMessage as EthereumTransactionMessage, - TransactionV0 as EthereumTransaction, + TransactionSignature, TransactionV0 as EthereumTransaction, }; use ethereum_types::{BigEndianHash, Bloom, H160, H256, H512, H64, U256, U64}; use evm::{ExitError, ExitReason}; @@ -228,6 +228,32 @@ impl EthApi for EthApiImpl { fn send_transaction(&self, request: TransactionRequest) -> BoxFuture> { debug!(target: "eth_rpc", "send_transaction, request:{:?}", request); + let mut request = request; + let curr_height = match self.block_number() { + Ok(v) => v, + Err(e) => { + return Box::pin(async move { Err(e) }); + } + } + .as_u64(); + if request.gas_price.is_none() + && request.max_fee_per_gas.is_some() + && request.max_priority_fee_per_gas.is_some() + { + if CFG.checkpoint.enable_eip1559_height > curr_height { + return Box::pin(future::err(internal_err(format!( + "eip1559 not enabled at height: {:?}", + curr_height + )))); + } + + request.gas_price = Some( + ::FeeCalculator::min_gas_price( + curr_height, + ), + ); + } + let from = match request.from { Some(from) => from, None => { @@ -345,6 +371,32 @@ impl EthApi for EthApiImpl { ) -> BoxFuture> { debug!(target: "eth_rpc", "call, request:{:?}", request); + let mut request = request; + let curr_height = match self.block_number() { + Ok(v) => v, + Err(e) => { + return Box::pin(async move { Err(e) }); + } + } + .as_u64(); + if request.gas_price.is_none() + && request.max_fee_per_gas.is_some() + && request.max_priority_fee_per_gas.is_some() + { + if CFG.checkpoint.enable_eip1559_height > curr_height { + return Box::pin(future::err(internal_err(format!( + "eip1559 not enabled at height: {:?}", + curr_height + )))); + } + + request.gas_price = Some( + ::FeeCalculator::min_gas_price( + curr_height, + ), + ); + } + let account_base_app = self.account_base_app.clone(); let task = spawn_blocking(move || -> Result { @@ -352,6 +404,8 @@ impl EthApi for EthApiImpl { from, to, gas_price, + max_fee_per_gas: _max_fee_per_gas, + max_priority_fee_per_gas: _max_priority_fee_per_gas, gas, value, data, @@ -717,13 +771,88 @@ impl EthApi for EthApiImpl { } fn send_raw_transaction(&self, bytes: Bytes) -> BoxFuture> { - let transaction = match rlp::decode::(&bytes.0[..]) { - Ok(transaction) => transaction, - Err(_) => { - return Box::pin(future::err(internal_err("decode transaction failed"))); + let slice = &bytes.0[..]; + if slice.is_empty() { + return Box::pin(future::err(internal_err("transaction data is empty"))); + } + // let first = slice.get(0).unwrap(); + let first = slice.first().unwrap(); + let curr_height = match self.block_number() { + Ok(h) => h.as_u64(), + Err(e) => return Box::pin(future::err(e)), + }; + + let transaction = if first > &0x7f { + // Legacy transaction. Decode and wrap in envelope. + match rlp::decode::(slice) { + Ok(transaction) => ethereum::TransactionV2::Legacy(transaction), + Err(_) => { + return Box::pin(future::err(internal_err( + "decode transaction failed", + ))); + } + } + } else { + if CFG.checkpoint.enable_eip1559_height > curr_height { + return Box::pin(future::err(internal_err(format!( + "eip1559 not enabled at height: {:?}", + curr_height + )))); + } + // Typed Transaction. + // `ethereum` crate decode implementation for `TransactionV2` expects a valid rlp input, + // and EIP-1559 breaks that assumption by prepending a version byte. + // We re-encode the payload input to get a valid rlp, and the decode implementation will strip + // them to check the transaction version byte. + let extend = rlp::encode(&slice); + match rlp::decode::(&extend[..]) { + Ok(transaction) => transaction, + Err(_) => { + return Box::pin(future::err(internal_err( + "decode transaction failed", + ))) + } + } + }; + + let transaction: ethereum::TransactionV0 = match transaction { + ethereum::TransactionV2::Legacy(tx) => tx, + ethereum::TransactionV2::EIP1559(tx) => { + let chain_id: u64 = + ::ChainId::get(); + let v: u64 = if tx.odd_y_parity { + chain_id * 2 + 36 + } else { + chain_id * 2 + 35 + }; + let signature = match TransactionSignature::new(v, tx.r, tx.s) { + Some(sig) => sig, + None => { + return Box::pin(future::err(internal_err( + "eip1559 signature input error!!", + ))) + } + }; + + ethereum::TransactionV0 { + nonce: tx.nonce, + gas_price: + ::FeeCalculator::min_gas_price( + curr_height, + ), + gas_limit: tx.gas_limit, + action: tx.action, + value: tx.value, + input: tx.input, + signature, + } + } + _ => { + return Box::pin(future::err(internal_err( + "do't support other transaction format!!!", + ))) } }; - debug!(target: "eth_rpc", "send_raw_transaction :{:?}", transaction); let transaction_hash = H256::from_slice(Keccak256::digest(&rlp::encode(&transaction)).as_slice()); @@ -764,6 +893,32 @@ impl EthApi for EthApiImpl { ) -> BoxFuture> { debug!(target: "eth_rpc", "estimate_gas, block number {:?} request:{:?}", number, request); + let mut request = request; + let curr_height = match self.block_number() { + Ok(v) => v, + Err(e) => { + return Box::pin(async move { Err(e) }); + } + } + .as_u64(); + if request.gas_price.is_none() + && request.max_fee_per_gas.is_some() + && request.max_priority_fee_per_gas.is_some() + { + if CFG.checkpoint.enable_eip1559_height > curr_height { + return Box::pin(future::err(internal_err(format!( + "eip1559 not enabled at height: {:?}", + curr_height + )))); + } + + request.gas_price = Some( + ::FeeCalculator::min_gas_price( + curr_height, + ), + ); + } + let account_base_app = self.account_base_app.clone(); let task = spawn_blocking(move || { @@ -861,6 +1016,8 @@ impl EthApi for EthApiImpl { from, to, gas_price, + max_fee_per_gas: _max_fee_per_gas, + max_priority_fee_per_gas: _max_priority_fee_per_gas, gas, value, data, @@ -1139,6 +1296,8 @@ impl EthApi for EthApiImpl { ); let receipt = receipts[index].clone(); let status = statuses[index].clone(); + let transaction = block.transactions[index].clone(); + let mut cumulative_receipts = receipts; cumulative_receipts .truncate((status.transaction_index + 1) as usize); @@ -1196,6 +1355,7 @@ impl EthApi for EthApiImpl { status_code: Some(U64::from(receipt.state_root.to_low_u64_be())), logs_bloom: receipt.logs_bloom, state_root: None, + effective_gas_price: transaction.gas_price, })); } _ => Ok(None), @@ -1403,6 +1563,7 @@ fn rich_block_build( } }, size: Some(U256::from(rlp::encode(&block).len() as u32)), + base_fee_per_gas: None, }, extra_info: BTreeMap::new(), } @@ -1455,7 +1616,9 @@ fn transaction_build( |status| status.to, ), value: transaction.value, - gas_price: transaction.gas_price, + gas_price: Some(transaction.gas_price), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, gas: transaction.gas_limit, input: Bytes(transaction.clone().input), creates: status.as_ref().and_then(|status| status.contract_address), @@ -1466,6 +1629,7 @@ fn transaction_build( v: U256::from(transaction.signature.v()), r: U256::from(transaction.signature.r().as_bytes()), s: U256::from(transaction.signature.s().as_bytes()), + access_list: None, } } @@ -1661,6 +1825,7 @@ fn dummy_block(height: u64, full: bool) -> Rich { uncles: vec![], transactions, size: Some(U256::from(0x1_u32)), + base_fee_per_gas: None, }; Rich {