From 9e9c31b53416542c2a2742b9c1ffd90d1075d078 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Thu, 2 Nov 2023 11:21:04 +0800 Subject: [PATCH] fix: update the Transaction RPC type and fix response of txpool_content RPC (#1234) * fix: update the Transaction type and fix response of txpool_content RPC - fix the response of txpool_content RPC - update the `Transaction` response type of some RPC response * remove Option wrapper of transaction type --- client/rpc-core/src/txpool.rs | 11 ++- client/rpc-core/src/types/mod.rs | 9 +- client/rpc-core/src/types/transaction.rs | 116 ++++++++++++----------- client/rpc-core/src/types/txpool.rs | 107 ++------------------- client/rpc/src/eth/mod.rs | 73 +++++--------- client/rpc/src/eth/transaction.rs | 20 ++-- client/rpc/src/txpool.rs | 33 +++---- ts-tests/tests/test-pending-pool.ts | 4 - 8 files changed, 134 insertions(+), 239 deletions(-) diff --git a/client/rpc-core/src/txpool.rs b/client/rpc-core/src/txpool.rs index af758db15d..402e7a1b8a 100644 --- a/client/rpc-core/src/txpool.rs +++ b/client/rpc-core/src/txpool.rs @@ -34,9 +34,10 @@ pub trait TxPoolApi { /// associative arrays, in which each entry maps an origin-address to a batch of scheduled /// transactions. These batches themselves are maps associating nonces with actual transactions. /// - /// For details, see [txpool_content](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-content) + /// For details, see [txpool_content (geth)](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-content) + /// or [txpool_content (nethermind)](https://docs.nethermind.io/nethermind/ethereum-client/json-rpc/txpool#txpool_content). #[method(name = "txpool_content")] - fn content(&self) -> RpcResult>>; + fn content(&self) -> RpcResult>>; /// The inspect inspection property can be queried to list a textual summary of all the /// transactions currently pending for inclusion in the next block(s), as well as the ones that @@ -48,7 +49,8 @@ pub trait TxPoolApi { /// transactions. These batches themselves are maps associating nonces with transactions /// summary strings. /// - /// For details, see [txpool_inspect](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-content) + /// For details, see [txpool_inspect (geth)](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-inspect) + /// or [txpool_inspect (nethermind)](https://docs.nethermind.io/nethermind/ethereum-client/json-rpc/txpool#txpool_inspect). #[method(name = "txpool_inspect")] fn inspect(&self) -> RpcResult>>; @@ -59,7 +61,8 @@ pub trait TxPoolApi { /// The result is an object with two fields pending and queued, each of which is a counter /// representing the number of transactions in that particular state. /// - /// For details, see [txpool_status](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-status) + /// For details, see [txpool_status (geth)](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-txpool#txpool-status) + /// or [txpool_status (nethermind)](https://docs.nethermind.io/nethermind/ethereum-client/json-rpc/txpool#txpool_status). #[method(name = "txpool_status")] fn status(&self) -> RpcResult>; } diff --git a/client/rpc-core/src/types/mod.rs b/client/rpc-core/src/types/mod.rs index 70b3223959..0fd9828bd1 100644 --- a/client/rpc-core/src/types/mod.rs +++ b/client/rpc-core/src/types/mod.rs @@ -37,10 +37,12 @@ mod work; pub mod pubsub; +use ethereum::TransactionV2 as EthereumTransaction; +use ethereum_types::H160; use serde::{de::Error, Deserialize, Deserializer}; #[cfg(feature = "txpool")] -pub use self::txpool::{Get, Summary, TransactionMap, TxPoolResult, TxPoolTransaction}; +pub use self::txpool::{Summary, TransactionMap, TxPoolResult}; pub use self::{ account_info::{AccountInfo, EthAccount, ExtAccountInfo, RecoveredAccount, StorageProof}, block::{Block, BlockTransactions, Header, Rich, RichBlock, RichHeader}, @@ -89,3 +91,8 @@ pub(crate) fn deserialize_data_or_input<'d, D: Deserializer<'d>>( (_, _) => Ok(data.or(input)), } } + +/// The trait that used to build types from the `from` address and ethereum `transaction`. +pub trait BuildFrom { + fn build_from(from: H160, transaction: &EthereumTransaction) -> Self; +} diff --git a/client/rpc-core/src/types/transaction.rs b/client/rpc-core/src/types/transaction.rs index 0ba042d4f5..e1532f7e11 100644 --- a/client/rpc-core/src/types/transaction.rs +++ b/client/rpc-core/src/types/transaction.rs @@ -16,16 +16,19 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use ethereum::{AccessListItem, TransactionV2}; -use ethereum_types::{H160, H256, H512, U256, U64}; +use ethereum::{AccessListItem, TransactionAction, TransactionV2 as EthereumTransaction}; +use ethereum_types::{H160, H256, U256, U64}; use serde::{ser::SerializeStruct, Serialize, Serializer}; -use crate::types::Bytes; +use crate::types::{BuildFrom, Bytes}; /// Transaction #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Transaction { + /// EIP-2718 transaction type + #[serde(rename = "type")] + pub transaction_type: U256, /// Hash pub hash: H256, /// Nonce @@ -42,6 +45,8 @@ pub struct Transaction { pub to: Option, /// Transferred value pub value: U256, + /// Gas + pub gas: U256, /// Gas Price #[serde(skip_serializing_if = "Option::is_none")] pub gas_price: Option, @@ -51,114 +56,113 @@ pub struct Transaction { /// The miner's tip. #[serde(skip_serializing_if = "Option::is_none")] pub max_priority_fee_per_gas: Option, - /// Gas - pub gas: U256, /// Data pub input: Bytes, /// Creates contract pub creates: Option, - /// Raw transaction data - pub raw: Bytes, - /// Public key of the signer. - pub public_key: Option, /// The network id of the transaction, if any. + #[serde(skip_serializing_if = "Option::is_none")] pub chain_id: Option, - /// The standardised V field of the signature (0 or 1). - pub standard_v: U256, + /// Pre-pay to warm storage access. + #[serde(skip_serializing_if = "Option::is_none")] + pub access_list: Option>, + /// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature. + #[serde(skip_serializing_if = "Option::is_none")] + pub y_parity: Option, /// The standardised V field of the signature. - pub v: U256, + /// + /// For backwards compatibility, `v` is optionally provided as an alternative to `yParity`. + /// This field is DEPRECATED and all use of it should migrate to `yParity`. + #[serde(skip_serializing_if = "Option::is_none")] + pub v: Option, /// The R field of the signature. pub r: U256, /// The S field of the signature. pub s: U256, - /// Pre-pay to warm storage access. - #[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none"))] - pub access_list: Option>, - /// EIP-2718 type - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub transaction_type: Option, } -impl From for Transaction { - fn from(transaction: TransactionV2) -> Self { - let serialized = ethereum::EnvelopedEncodable::encode(&transaction); +impl BuildFrom for Transaction { + fn build_from(from: H160, transaction: &EthereumTransaction) -> Self { let hash = transaction.hash(); - let raw = Bytes(serialized.to_vec()); match transaction { - TransactionV2::Legacy(t) => Transaction { + EthereumTransaction::Legacy(t) => Self { + transaction_type: U256::from(0), hash, nonce: t.nonce, block_hash: None, block_number: None, transaction_index: None, - from: H160::default(), - to: None, + from, + to: match t.action { + TransactionAction::Call(to) => Some(to), + TransactionAction::Create => None, + }, value: t.value, + gas: t.gas_limit, gas_price: Some(t.gas_price), max_fee_per_gas: None, max_priority_fee_per_gas: None, - gas: t.gas_limit, - input: Bytes(t.clone().input), + input: Bytes(t.input.clone()), creates: None, - raw, - public_key: None, chain_id: t.signature.chain_id().map(U64::from), - standard_v: U256::from(t.signature.standard_v()), - v: U256::from(t.signature.v()), + access_list: None, + y_parity: None, + v: Some(U256::from(t.signature.v())), r: U256::from(t.signature.r().as_bytes()), s: U256::from(t.signature.s().as_bytes()), - access_list: None, - transaction_type: Some(U256::from(0)), }, - TransactionV2::EIP2930(t) => Transaction { + EthereumTransaction::EIP2930(t) => Self { + transaction_type: U256::from(1), hash, nonce: t.nonce, block_hash: None, block_number: None, transaction_index: None, - from: H160::default(), - to: None, + from, + to: match t.action { + TransactionAction::Call(to) => Some(to), + TransactionAction::Create => None, + }, value: t.value, + gas: t.gas_limit, gas_price: Some(t.gas_price), max_fee_per_gas: None, max_priority_fee_per_gas: None, - gas: t.gas_limit, - input: Bytes(t.clone().input), + input: Bytes(t.input.clone()), creates: None, - raw, - public_key: None, chain_id: Some(U64::from(t.chain_id)), - standard_v: U256::from(t.odd_y_parity as u8), - v: U256::from(t.odd_y_parity as u8), + access_list: Some(t.access_list.clone()), + y_parity: Some(U256::from(t.odd_y_parity as u8)), + v: Some(U256::from(t.odd_y_parity as u8)), r: U256::from(t.r.as_bytes()), s: U256::from(t.s.as_bytes()), - access_list: Some(t.access_list), - transaction_type: Some(U256::from(1)), }, - TransactionV2::EIP1559(t) => Transaction { + EthereumTransaction::EIP1559(t) => Self { + transaction_type: U256::from(2), hash, nonce: t.nonce, block_hash: None, block_number: None, transaction_index: None, - from: H160::default(), - to: None, + from, + to: match t.action { + TransactionAction::Call(to) => Some(to), + TransactionAction::Create => None, + }, value: t.value, - gas_price: None, + gas: t.gas_limit, + // If transaction is not mined yet, gas price is considered just max fee per gas. + gas_price: Some(t.max_fee_per_gas), max_fee_per_gas: Some(t.max_fee_per_gas), max_priority_fee_per_gas: Some(t.max_priority_fee_per_gas), - gas: t.gas_limit, - input: Bytes(t.clone().input), + input: Bytes(t.input.clone()), creates: None, - raw, - public_key: None, chain_id: Some(U64::from(t.chain_id)), - standard_v: U256::from(t.odd_y_parity as u8), - v: U256::from(t.odd_y_parity as u8), + access_list: Some(t.access_list.clone()), + y_parity: Some(U256::from(t.odd_y_parity as u8)), + v: Some(U256::from(t.odd_y_parity as u8)), r: U256::from(t.r.as_bytes()), s: U256::from(t.s.as_bytes()), - access_list: Some(t.access_list), - transaction_type: Some(U256::from(2)), }, } } diff --git a/client/rpc-core/src/types/txpool.rs b/client/rpc-core/src/types/txpool.rs index 3b193c58c7..5c953bc678 100644 --- a/client/rpc-core/src/types/txpool.rs +++ b/client/rpc-core/src/types/txpool.rs @@ -19,19 +19,15 @@ use std::collections::HashMap; use ethereum::{TransactionAction, TransactionV2 as EthereumTransaction}; -use ethereum_types::{H160, H256, U256}; +use ethereum_types::{H160, U256}; use serde::{Serialize, Serializer}; -use crate::types::Bytes; +use crate::types::BuildFrom; /// The entry maps an origin-address to a batch of scheduled transactions. /// These batches themselves are maps associating nonces with actual transactions. pub type TransactionMap = HashMap>; -pub trait Get { - fn get(hash: H256, from_address: H160, txn: &EthereumTransaction) -> Self; -} - /// The result type of `txpool` API. #[derive(Debug, Serialize)] pub struct TxPoolResult { @@ -68,9 +64,9 @@ impl Serialize for Summary { } } -impl Get for Summary { - fn get(_hash: H256, _from_address: H160, txn: &EthereumTransaction) -> Self { - let (action, value, gas_price, gas_limit) = match txn { +impl BuildFrom for Summary { + fn build_from(_from: H160, transaction: &EthereumTransaction) -> Self { + let (action, value, gas_price, gas) = match transaction { EthereumTransaction::Legacy(t) => (t.action, t.value, t.gas_price, t.gas_limit), EthereumTransaction::EIP2930(t) => (t.action, t.value, t.gas_price, t.gas_limit), EthereumTransaction::EIP1559(t) => (t.action, t.value, t.max_fee_per_gas, t.gas_limit), @@ -82,98 +78,7 @@ impl Get for Summary { }, value, gas_price, - gas: gas_limit, - } - } -} - -/// The exact details of all the transactions currently pending for inclusion in the next block(s) -#[derive(Debug, Default, Clone, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct TxPoolTransaction { - /// Hash - pub hash: H256, - /// Nonce - pub nonce: U256, - /// Block hash - #[serde(serialize_with = "block_hash_serialize")] - pub block_hash: Option, - /// Block number - pub block_number: Option, - /// Sender - pub from: H160, - /// Recipient - #[serde(serialize_with = "to_serialize")] - pub to: Option, - /// Transferred value - pub value: U256, - /// Gas Price - pub gas_price: U256, - /// Gas - pub gas: U256, - /// Data - pub input: Bytes, - /// Transaction Index - pub transaction_index: Option, -} - -fn block_hash_serialize(hash: &Option, serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_str(&format!("0x{:x}", hash.unwrap_or_default())) -} - -fn to_serialize(hash: &Option, serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_str(&format!("0x{:x}", hash.unwrap_or_default())) -} - -impl Get for TxPoolTransaction { - fn get(hash: H256, from_address: H160, txn: &EthereumTransaction) -> Self { - let (nonce, action, value, gas_price, gas_limit, input) = match txn { - EthereumTransaction::Legacy(t) => ( - t.nonce, - t.action, - t.value, - t.gas_price, - t.gas_limit, - t.input.clone(), - ), - EthereumTransaction::EIP2930(t) => ( - t.nonce, - t.action, - t.value, - t.gas_price, - t.gas_limit, - t.input.clone(), - ), - EthereumTransaction::EIP1559(t) => ( - t.nonce, - t.action, - t.value, - t.max_fee_per_gas, - t.gas_limit, - t.input.clone(), - ), - }; - Self { - hash, - nonce, - block_hash: None, - block_number: None, - from: from_address, - to: match action { - TransactionAction::Call(to) => Some(to), - _ => None, - }, - value, - gas_price, - gas: gas_limit, - input: Bytes(input), - transaction_index: None, + gas, } } } diff --git a/client/rpc/src/eth/mod.rs b/client/rpc/src/eth/mod.rs index 1c7bb62ebc..16961fb7dd 100644 --- a/client/rpc/src/eth/mod.rs +++ b/client/rpc/src/eth/mod.rs @@ -32,7 +32,7 @@ mod transaction; use std::{collections::BTreeMap, marker::PhantomData, sync::Arc}; use ethereum::{BlockV2 as EthereumBlock, TransactionV2 as EthereumTransaction}; -use ethereum_types::{H160, H256, H512, H64, U256, U64}; +use ethereum_types::{H160, H256, H64, U256, U64}; use jsonrpsee::core::{async_trait, RpcResult}; // Substrate use sc_client_api::backend::{Backend, StorageProvider}; @@ -606,9 +606,9 @@ fn rich_block_build( .enumerate() .map(|(index, transaction)| { transaction_build( - transaction.clone(), - Some(block.clone()), - Some(statuses[index].clone().unwrap_or_default()), + transaction, + Some(&block), + statuses[index].as_ref(), base_fee, ) }) @@ -632,17 +632,30 @@ fn rich_block_build( } fn transaction_build( - ethereum_transaction: EthereumTransaction, - block: Option, - status: Option, + ethereum_transaction: &EthereumTransaction, + block: Option<&EthereumBlock>, + status: Option<&TransactionStatus>, base_fee: Option, ) -> Transaction { - let mut transaction: Transaction = ethereum_transaction.clone().into(); + let pubkey = match public_key(ethereum_transaction) { + Ok(p) => Some(p), + Err(_) => None, + }; + let from = status.map_or( + { + match pubkey { + Some(pk) => H160::from(H256::from(keccak_256(&pk))), + _ => H160::default(), + } + }, + |status| status.from, + ); + + let mut transaction: Transaction = Transaction::build_from(from, ethereum_transaction); if let EthereumTransaction::EIP1559(_) = ethereum_transaction { if block.is_none() && status.is_none() { // If transaction is not mined yet, gas price is considered just max fee per gas. - transaction.gas_price = transaction.max_fee_per_gas; } else { let base_fee = base_fee.unwrap_or_default(); let max_priority_fee_per_gas = transaction.max_priority_fee_per_gas.unwrap_or_default(); @@ -657,52 +670,18 @@ fn transaction_build( } } - let pubkey = match public_key(ðereum_transaction) { - Ok(p) => Some(p), - Err(_e) => None, - }; - // Block hash. - transaction.block_hash = block - .as_ref() - .map(|block| H256::from(keccak_256(&rlp::encode(&block.header)))); + transaction.block_hash = block.map(|block| block.header.hash()); // Block number. - transaction.block_number = block.as_ref().map(|block| block.header.number); + transaction.block_number = block.map(|block| block.header.number); // Transaction index. - transaction.transaction_index = status.as_ref().map(|status| { + transaction.transaction_index = status.map(|status| { U256::from(UniqueSaturatedInto::::unique_saturated_into( status.transaction_index, )) }); - // From. - transaction.from = status.as_ref().map_or( - { - match pubkey { - Some(pk) => H160::from(H256::from(keccak_256(&pk))), - _ => H160::default(), - } - }, - |status| status.from, - ); - // To. - transaction.to = status.as_ref().map_or( - { - let action = match ethereum_transaction { - EthereumTransaction::Legacy(t) => t.action, - EthereumTransaction::EIP2930(t) => t.action, - EthereumTransaction::EIP1559(t) => t.action, - }; - match action { - ethereum::TransactionAction::Call(to) => Some(to), - _ => None, - } - }, - |status| status.to, - ); // Creates. - transaction.creates = status.as_ref().and_then(|status| status.contract_address); - // Public key. - transaction.public_key = pubkey.as_ref().map(H512::from); + transaction.creates = status.and_then(|status| status.contract_address); transaction } diff --git a/client/rpc/src/eth/transaction.rs b/client/rpc/src/eth/transaction.rs index cf15d394bd..3eb5066cc2 100644 --- a/client/rpc/src/eth/transaction.rs +++ b/client/rpc/src/eth/transaction.rs @@ -115,7 +115,7 @@ where for txn in ethereum_transactions { let inner_hash = txn.hash(); if hash == inner_hash { - return Ok(Some(transaction_build(txn, None, None, None))); + return Ok(Some(transaction_build(&txn, None, None, None))); } } // Unknown transaction. @@ -131,9 +131,9 @@ where } = self.block_info_by_eth_block_hash(eth_block_hash).await?; match (block, statuses) { (Some(block), Some(statuses)) => Ok(Some(transaction_build( - block.transactions[index].clone(), - Some(block), - Some(statuses[index].clone()), + &block.transactions[index], + Some(&block), + Some(&statuses[index]), Some(base_fee), ))), _ => Ok(None), @@ -159,9 +159,9 @@ where (block.transactions.get(index), statuses.get(index)) { Ok(Some(transaction_build( - transaction.clone(), - Some(block), - Some(status.clone()), + transaction, + Some(&block), + Some(status), Some(base_fee), ))) } else { @@ -191,9 +191,9 @@ where (block.transactions.get(index), statuses.get(index)) { Ok(Some(transaction_build( - transaction.clone(), - Some(block), - Some(status.clone()), + transaction, + Some(&block), + Some(status), Some(base_fee), ))) } else { diff --git a/client/rpc/src/txpool.rs b/client/rpc/src/txpool.rs index 0523176d9d..03e5a61f8c 100644 --- a/client/rpc/src/txpool.rs +++ b/client/rpc/src/txpool.rs @@ -18,7 +18,7 @@ use std::{collections::HashMap, marker::PhantomData, sync::Arc}; -use ethereum::TransactionV2; +use ethereum::TransactionV2 as EthereumTransaction; use ethereum_types::{H160, H256, U256}; use jsonrpsee::core::RpcResult; use serde::Serialize; @@ -31,7 +31,7 @@ use sp_core::hashing::keccak_256; use sp_runtime::traits::Block as BlockT; // Frontier use fc_rpc_core::{ - types::{Get, Summary, TransactionMap, TxPoolResult, TxPoolTransaction}, + types::{BuildFrom, Summary, Transaction, TransactionMap, TxPoolResult}, TxPoolApiServer, }; use fp_rpc::EthereumRuntimeRPCApi; @@ -39,8 +39,8 @@ use fp_rpc::EthereumRuntimeRPCApi; use crate::{internal_err, public_key}; struct TxPoolTransactions { - ready: Vec, - future: Vec, + ready: Vec, + future: Vec, } pub struct TxPool { @@ -69,7 +69,7 @@ where { fn map_build(&self) -> RpcResult>> where - T: Get + Serialize, + T: BuildFrom + Serialize, { let txns = self.collect_txpool_transactions()?; let pending = Self::build_txn_map::<'_, T>(txns.ready.iter()); @@ -77,26 +77,27 @@ where Ok(TxPoolResult { pending, queued }) } - fn build_txn_map<'a, T>(txns: impl Iterator) -> TransactionMap + fn build_txn_map<'a, T>( + txns: impl Iterator, + ) -> TransactionMap where - T: Get + Serialize, + T: BuildFrom + Serialize, { let mut result = TransactionMap::::new(); for txn in txns { - let hash = txn.hash(); let nonce = match txn { - TransactionV2::Legacy(t) => t.nonce, - TransactionV2::EIP2930(t) => t.nonce, - TransactionV2::EIP1559(t) => t.nonce, + EthereumTransaction::Legacy(t) => t.nonce, + EthereumTransaction::EIP2930(t) => t.nonce, + EthereumTransaction::EIP1559(t) => t.nonce, }; - let from_address = match public_key(txn) { + let from = match public_key(txn) { Ok(pk) => H160::from(H256::from(keccak_256(&pk))), Err(_) => H160::default(), }; result - .entry(from_address) + .entry(from) .or_insert_with(HashMap::new) - .insert(nonce, T::get(hash, from_address, txn)); + .insert(nonce, T::build_from(from, txn)); } result } @@ -152,8 +153,8 @@ where C: HeaderBackend + 'static, A: ChainApi + 'static, { - fn content(&self) -> RpcResult>> { - self.map_build::() + fn content(&self) -> RpcResult>> { + self.map_build::() } fn inspect(&self) -> RpcResult>> { diff --git a/ts-tests/tests/test-pending-pool.ts b/ts-tests/tests/test-pending-pool.ts index 809ad0aa9b..9f89b9680f 100644 --- a/ts-tests/tests/test-pending-pool.ts +++ b/ts-tests/tests/test-pending-pool.ts @@ -28,8 +28,6 @@ describeWithFrontier("Frontier RPC (Pending Pool)", (context) => { expect(pendingTransaction).to.include({ blockNumber: null, hash: txHash, - publicKey: - "0x624f720eae676a04111631c9ca338c11d0f5a80ee42210c6be72983ceb620fbf645a96f951529fa2d70750432d11b7caba5270c4d677255be90b3871c8c58069", r: "0x8e3759de96b00f8a05a95c24fa905963f86a82a0038cca0fde035762fb2d24f7", s: "0x7131a2c265463f4bb063504f924df4d3d14bdad9cdfff8391041ea78295d186b", v: "0x77", @@ -40,8 +38,6 @@ describeWithFrontier("Frontier RPC (Pending Pool)", (context) => { const processedTransaction = (await customRequest(context.web3, "eth_getTransactionByHash", [txHash])).result; expect(processedTransaction).to.include({ hash: txHash, - publicKey: - "0x624f720eae676a04111631c9ca338c11d0f5a80ee42210c6be72983ceb620fbf645a96f951529fa2d70750432d11b7caba5270c4d677255be90b3871c8c58069", r: "0x8e3759de96b00f8a05a95c24fa905963f86a82a0038cca0fde035762fb2d24f7", s: "0x7131a2c265463f4bb063504f924df4d3d14bdad9cdfff8391041ea78295d186b", v: "0x77",