diff --git a/client/rpc/txpool/Cargo.toml b/client/rpc/txpool/Cargo.toml index b0c7bdc104..6cdd75120c 100644 --- a/client/rpc/txpool/Cargo.toml +++ b/client/rpc/txpool/Cargo.toml @@ -19,6 +19,7 @@ sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0 sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } +sc-transaction-graph = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } serde = { version = "1.0", features = ["derive"] } diff --git a/client/rpc/txpool/src/lib.rs b/client/rpc/txpool/src/lib.rs index 831c97ff9f..0a3c1c695d 100644 --- a/client/rpc/txpool/src/lib.rs +++ b/client/rpc/txpool/src/lib.rs @@ -21,105 +21,125 @@ use jsonrpc_core::Result as RpcResult; pub use moonbeam_rpc_core_txpool::{ GetT, Summary, Transaction, TransactionMap, TxPool as TxPoolT, TxPoolResult, TxPoolServer, }; +use sc_transaction_graph::{ChainApi, Pool}; +use serde::Serialize; use sha3::{Digest, Keccak256}; use sp_api::{BlockId, ProvideRuntimeApi}; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use sp_runtime::traits::Block as BlockT; -use sp_transaction_pool::{InPoolTransaction, TransactionPool}; +use sp_transaction_pool::InPoolTransaction; use std::collections::HashMap; use std::{marker::PhantomData, sync::Arc}; -use moonbeam_rpc_primitives_txpool::TxPoolRuntimeApi; +use moonbeam_rpc_primitives_txpool::{TxPoolResponse, TxPoolRuntimeApi}; -pub struct TxPool { +pub struct TxPool { client: Arc, - pool: Arc

, + graph: Arc>, _marker: PhantomData, } -impl TxPool +impl TxPool where C: ProvideRuntimeApi, C: HeaderMetadata + HeaderBackend + 'static, C: Send + Sync + 'static, B: BlockT + Send + Sync + 'static, - P: TransactionPool + Send + Sync + 'static, + A: ChainApi + 'static, C::Api: TxPoolRuntimeApi, { - fn map_build(&self) -> RpcResult> + /// Use the transaction graph interface to get the extrinsics currently in the ready and future + /// queues. + fn map_build(&self) -> RpcResult>> where - T: GetT, + T: GetT + Serialize, { - let txs: Vec<_> = self - .pool + // Collect transactions in the ready validated pool. + let txs_ready = self + .graph + .validated_pool() .ready() .map(|in_pool_tx| in_pool_tx.data().clone()) .collect(); + // Collect transactions in the future validated pool. + let txs_future = self + .graph + .validated_pool() + .futures() + .iter() + .map(|(_hash, extrinsic)| extrinsic.clone()) + .collect(); + + // Use the runtime to match the (here) opaque extrinsics against ethereum transactions. let best_block: BlockId = BlockId::Hash(self.client.info().best_hash); - let ethereum_txns = self + let ethereum_txns: TxPoolResponse = self .client .runtime_api() - .extrinsic_filter(&best_block, txs) + .extrinsic_filter(&best_block, txs_ready, txs_future) .map_err(|err| { internal_err(format!("fetch runtime extrinsic filter failed: {:?}", err)) })?; - let mut out = TransactionMap::::new(); - for txn in ethereum_txns.iter() { + // Build the T response. + let mut pending = TransactionMap::::new(); + for txn in ethereum_txns.ready.iter() { let transaction_message = TransactionMessage::from(txn.clone()); let hash = transaction_message.hash(); let from_address = match public_key(txn) { Ok(pk) => H160::from(H256::from_slice(Keccak256::digest(&pk).as_slice())), Err(_e) => H160::default(), }; - out.entry(from_address) + pending + .entry(from_address) .or_insert_with(HashMap::new) .insert(txn.nonce, T::get(hash, from_address, txn)); } - Ok(out) + let mut queued = TransactionMap::::new(); + for txn in ethereum_txns.future.iter() { + let transaction_message = TransactionMessage::from(txn.clone()); + let hash = transaction_message.hash(); + let from_address = match public_key(txn) { + Ok(pk) => H160::from(H256::from_slice(Keccak256::digest(&pk).as_slice())), + Err(_e) => H160::default(), + }; + queued + .entry(from_address) + .or_insert_with(HashMap::new) + .insert(txn.nonce, T::get(hash, from_address, txn)); + } + Ok(TxPoolResult { pending, queued }) } } -impl TxPool { - pub fn new(client: Arc, pool: Arc

) -> Self { +impl TxPool { + pub fn new(client: Arc, graph: Arc>) -> Self { Self { client, - pool, + graph, _marker: PhantomData, } } } -impl TxPoolT for TxPool +impl TxPoolT for TxPool where C: ProvideRuntimeApi, C: HeaderMetadata + HeaderBackend, C: Send + Sync + 'static, B: BlockT + Send + Sync + 'static, - P: TransactionPool + Send + Sync + 'static, + A: ChainApi + 'static, C::Api: TxPoolRuntimeApi, { fn content(&self) -> RpcResult>> { - let pending = self.map_build::()?; - Ok(TxPoolResult { - pending, - // Future queue not yet supported. We need to do something like: - // - Use InpoolTransaction::requires() to get the TransactionTag bytes. - // - Somehow decode and identify the tag to either add it to the future or pending pool. - queued: HashMap::new(), - }) + self.map_build::() } fn inspect(&self) -> RpcResult>> { - let pending = self.map_build::

()?; - Ok(TxPoolResult { - pending, - queued: HashMap::new(), - }) + self.map_build::() } fn status(&self) -> RpcResult> { - let status = self.pool.status(); + let status = self.graph.validated_pool().status(); Ok(TxPoolResult { pending: U256::from(status.ready), queued: U256::from(status.future), diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index 41749ffef0..47650cb741 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -58,6 +58,7 @@ sc-executor = { git = "https://github.com/paritytech/substrate", branch = "polka sc-service = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } +sc-transaction-graph = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } sc-network = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2" } diff --git a/node/service/src/lib.rs b/node/service/src/lib.rs index 6cb553a654..ddde689bb9 100644 --- a/node/service/src/lib.rs +++ b/node/service/src/lib.rs @@ -541,6 +541,7 @@ where let deps = rpc::FullDeps { client: client.clone(), pool: pool.clone(), + graph: pool.pool().clone(), deny_unsafe, is_authority: collator, network: network.clone(), @@ -936,6 +937,7 @@ pub fn new_dev( let deps = rpc::FullDeps { client: client.clone(), pool: pool.clone(), + graph: pool.pool().clone(), deny_unsafe, is_authority: collator, network: network.clone(), diff --git a/node/service/src/rpc.rs b/node/service/src/rpc.rs index a420de6c6e..10aa94ca01 100644 --- a/node/service/src/rpc.rs +++ b/node/service/src/rpc.rs @@ -41,6 +41,7 @@ use sc_consensus_manual_seal::rpc::{EngineCommand, ManualSeal, ManualSealApi}; use sc_network::NetworkService; use sc_rpc::SubscriptionTaskExecutor; use sc_rpc_api::DenyUnsafe; +use sc_transaction_graph::{ChainApi, Pool}; use sp_api::ProvideRuntimeApi; use sp_blockchain::{ Backend as BlockchainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, @@ -51,11 +52,13 @@ use std::collections::BTreeMap; use substrate_frame_rpc_system::{FullSystem, SystemApi}; /// Full client dependencies. -pub struct FullDeps { +pub struct FullDeps { /// The client instance to use. pub client: Arc, /// Transaction pool instance. pub pool: Arc

, + /// Graph pool instance. + pub graph: Arc>, /// Whether to deny unsafe calls pub deny_unsafe: DenyUnsafe, /// The Node authority flag @@ -86,8 +89,8 @@ pub struct FullDeps { pub transaction_converter: TransactionConverters, } /// Instantiate all Full RPC extensions. -pub fn create_full( - deps: FullDeps, +pub fn create_full( + deps: FullDeps, subscription_task_executor: SubscriptionTaskExecutor, ) -> jsonrpc_core::IoHandler where @@ -98,6 +101,7 @@ where C: BlockchainEvents, C: HeaderBackend + HeaderMetadata + 'static, C: Send + Sync + 'static, + A: ChainApi + 'static, C::Api: RuntimeApiCollection, P: TransactionPool + 'static, { @@ -105,6 +109,7 @@ where let FullDeps { client, pool, + graph, deny_unsafe, is_authority, network, @@ -176,7 +181,7 @@ where ))); io.extend_with(Web3ApiServer::to_delegate(Web3Api::new(client.clone()))); io.extend_with(EthPubSubApiServer::to_delegate(EthPubSubApi::new( - pool.clone(), + pool, client.clone(), network, SubscriptionManager::::with_id_provider( @@ -188,7 +193,7 @@ where if ethapi_cmd.contains(&EthApiCmd::Txpool) { io.extend_with(TxPoolServer::to_delegate(TxPool::new( Arc::clone(&client), - pool, + graph, ))); } diff --git a/primitives/rpc/txpool/Cargo.toml b/primitives/rpc/txpool/Cargo.toml index 1289a4e182..b623775a61 100644 --- a/primitives/rpc/txpool/Cargo.toml +++ b/primitives/rpc/txpool/Cargo.toml @@ -8,6 +8,7 @@ license = 'GPL-3.0-only' repository = 'https://github.com/PureStake/moonbeam/' [dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } ethereum = { version = "0.7.1", default-features = false, features = ["with-codec"] } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2", default-features = false } sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.2", default-features = false } diff --git a/primitives/rpc/txpool/src/lib.rs b/primitives/rpc/txpool/src/lib.rs index 4a28f68fdf..b685e6028c 100644 --- a/primitives/rpc/txpool/src/lib.rs +++ b/primitives/rpc/txpool/src/lib.rs @@ -19,12 +19,22 @@ #![allow(clippy::unnecessary_mut_passed)] #![allow(clippy::too_many_arguments)] +use codec::{Decode, Encode}; use ethereum::Transaction; use sp_runtime::traits::Block as BlockT; use sp_std::vec::Vec; +#[derive(Eq, PartialEq, Clone, Encode, Decode, sp_runtime::RuntimeDebug)] +pub struct TxPoolResponse { + pub ready: Vec, + pub future: Vec, +} + sp_api::decl_runtime_apis! { pub trait TxPoolRuntimeApi { - fn extrinsic_filter(xt: Vec<::Extrinsic>) -> Vec; + fn extrinsic_filter( + xt_ready: Vec<::Extrinsic>, + xt_future: Vec<::Extrinsic>, + ) -> TxPoolResponse; } } diff --git a/runtime/moonbase/src/lib.rs b/runtime/moonbase/src/lib.rs index 1a6bb05761..c4c885c786 100644 --- a/runtime/moonbase/src/lib.rs +++ b/runtime/moonbase/src/lib.rs @@ -41,6 +41,7 @@ pub use moonbeam_core_primitives::{ Signature, }; use moonbeam_extensions_evm::runner::stack::TraceRunner as TraceRunnerT; +use moonbeam_rpc_primitives_txpool::TxPoolResponse; use pallet_balances::NegativeImbalance; use pallet_ethereum::Call::transact; use pallet_ethereum::{Transaction as EthereumTransaction, TransactionAction}; @@ -1101,12 +1102,19 @@ impl_runtime_apis! { impl moonbeam_rpc_primitives_txpool::TxPoolRuntimeApi for Runtime { fn extrinsic_filter( - xts: Vec<::Extrinsic> - ) -> Vec { - xts.into_iter().filter_map(|xt| match xt.function { - Call::Ethereum(transact(t)) => Some(t), - _ => None - }).collect() + xts_ready: Vec<::Extrinsic>, + xts_future: Vec<::Extrinsic> + ) -> TxPoolResponse { + TxPoolResponse { + ready: xts_ready.into_iter().filter_map(|xt| match xt.function { + Call::Ethereum(transact(t)) => Some(t), + _ => None + }).collect(), + future: xts_future.into_iter().filter_map(|xt| match xt.function { + Call::Ethereum(transact(t)) => Some(t), + _ => None + }).collect(), + } } } diff --git a/runtime/moonbeam/src/lib.rs b/runtime/moonbeam/src/lib.rs index 1e316a865d..2bca93d401 100644 --- a/runtime/moonbeam/src/lib.rs +++ b/runtime/moonbeam/src/lib.rs @@ -41,6 +41,7 @@ pub use moonbeam_core_primitives::{ Signature, }; use moonbeam_extensions_evm::runner::stack::TraceRunner as TraceRunnerT; +use moonbeam_rpc_primitives_txpool::TxPoolResponse; use pallet_balances::NegativeImbalance; use pallet_ethereum::Call::transact; use pallet_ethereum::{Transaction as EthereumTransaction, TransactionAction}; @@ -1117,12 +1118,19 @@ impl_runtime_apis! { impl moonbeam_rpc_primitives_txpool::TxPoolRuntimeApi for Runtime { fn extrinsic_filter( - xts: Vec<::Extrinsic> - ) -> Vec { - xts.into_iter().filter_map(|xt| match xt.function { - Call::Ethereum(transact(t)) => Some(t), - _ => None - }).collect() + xts_ready: Vec<::Extrinsic>, + xts_future: Vec<::Extrinsic> + ) -> TxPoolResponse { + TxPoolResponse { + ready: xts_ready.into_iter().filter_map(|xt| match xt.function { + Call::Ethereum(transact(t)) => Some(t), + _ => None + }).collect(), + future: xts_future.into_iter().filter_map(|xt| match xt.function { + Call::Ethereum(transact(t)) => Some(t), + _ => None + }).collect(), + } } } diff --git a/runtime/moonriver/src/lib.rs b/runtime/moonriver/src/lib.rs index 2f44eaab58..3c1078c435 100644 --- a/runtime/moonriver/src/lib.rs +++ b/runtime/moonriver/src/lib.rs @@ -41,6 +41,7 @@ pub use moonbeam_core_primitives::{ Signature, }; use moonbeam_extensions_evm::runner::stack::TraceRunner as TraceRunnerT; +use moonbeam_rpc_primitives_txpool::TxPoolResponse; use pallet_balances::NegativeImbalance; use pallet_ethereum::Call::transact; use pallet_ethereum::{Transaction as EthereumTransaction, TransactionAction}; @@ -1116,15 +1117,21 @@ impl_runtime_apis! { impl moonbeam_rpc_primitives_txpool::TxPoolRuntimeApi for Runtime { fn extrinsic_filter( - xts: Vec<::Extrinsic> - ) -> Vec { - xts.into_iter().filter_map(|xt| match xt.function { - Call::Ethereum(transact(t)) => Some(t), - _ => None - }).collect() + xts_ready: Vec<::Extrinsic>, + xts_future: Vec<::Extrinsic> + ) -> TxPoolResponse { + TxPoolResponse { + ready: xts_ready.into_iter().filter_map(|xt| match xt.function { + Call::Ethereum(transact(t)) => Some(t), + _ => None + }).collect(), + future: xts_future.into_iter().filter_map(|xt| match xt.function { + Call::Ethereum(transact(t)) => Some(t), + _ => None + }).collect(), + } } } - impl fp_rpc::EthereumRuntimeRPCApi for Runtime { fn chain_id() -> u64 { ::ChainId::get() diff --git a/runtime/moonshadow/src/lib.rs b/runtime/moonshadow/src/lib.rs index b7fb5e9be3..830331e9b5 100644 --- a/runtime/moonshadow/src/lib.rs +++ b/runtime/moonshadow/src/lib.rs @@ -41,6 +41,7 @@ pub use moonbeam_core_primitives::{ Signature, }; use moonbeam_extensions_evm::runner::stack::TraceRunner as TraceRunnerT; +use moonbeam_rpc_primitives_txpool::TxPoolResponse; use pallet_balances::NegativeImbalance; use pallet_ethereum::Call::transact; use pallet_ethereum::{Transaction as EthereumTransaction, TransactionAction}; @@ -1116,12 +1117,19 @@ impl_runtime_apis! { impl moonbeam_rpc_primitives_txpool::TxPoolRuntimeApi for Runtime { fn extrinsic_filter( - xts: Vec<::Extrinsic> - ) -> Vec { - xts.into_iter().filter_map(|xt| match xt.function { - Call::Ethereum(transact(t)) => Some(t), - _ => None - }).collect() + xts_ready: Vec<::Extrinsic>, + xts_future: Vec<::Extrinsic> + ) -> TxPoolResponse { + TxPoolResponse { + ready: xts_ready.into_iter().filter_map(|xt| match xt.function { + Call::Ethereum(transact(t)) => Some(t), + _ => None + }).collect(), + future: xts_future.into_iter().filter_map(|xt| match xt.function { + Call::Ethereum(transact(t)) => Some(t), + _ => None + }).collect(), + } } } diff --git a/tests/tests/test-txpool-future-eth.ts b/tests/tests/test-txpool-future-eth.ts deleted file mode 100644 index cf99e8a688..0000000000 --- a/tests/tests/test-txpool-future-eth.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { expect } from "chai"; - -import { createContract } from "../util/transactions"; -import { describeDevMoonbeam } from "../util/setup-dev-tests"; -import { customWeb3Request } from "../util/providers"; - -describeDevMoonbeam("TxPool - Genesis", (context) => { - it("should be empty", async function () { - let inspect = await customWeb3Request(context.web3, "txpool_inspect", []); - expect(inspect.result.pending).to.be.empty; - let content = await customWeb3Request(context.web3, "txpool_content", []); - expect(content.result.pending).to.be.empty; - }); -}); - -describeDevMoonbeam("TxPool - New block", (context) => { - before("Setup: Create transaction and empty block", async () => { - const { rawTx } = await createContract(context.web3, "TestContract", { - gas: 1048576, - }); - await context.createBlock({ transactions: [rawTx] }); - await context.createBlock(); - }); - - it("should reset the txpool", async function () { - let inspect = await customWeb3Request(context.web3, "txpool_inspect", []); - expect(inspect.result.pending).to.be.empty; - let content = await customWeb3Request(context.web3, "txpool_content", []); - expect(content.result.pending).to.be.empty; - }); -}); diff --git a/tests/tests/test-txpool-future.ts b/tests/tests/test-txpool-future.ts new file mode 100644 index 0000000000..8700b5e388 --- /dev/null +++ b/tests/tests/test-txpool-future.ts @@ -0,0 +1,43 @@ +import { expect } from "chai"; +import { Contract } from "web3-eth-contract"; + +import { GENESIS_ACCOUNT } from "../util/constants"; +import { createContract, createContractExecution } from "../util/transactions"; +import { describeDevMoonbeam } from "../util/setup-dev-tests"; +import { customWeb3Request } from "../util/providers"; + +describeDevMoonbeam("TxPool - Future Ethereum transaction", (context) => { + let txHash; + before("Setup: Create transaction", async () => { + const { rawTx } = await createContract(context.web3, "TestContract", { + gas: 1048576, + nonce: 1, // future nonce + }); + txHash = (await customWeb3Request(context.web3, "eth_sendRawTransaction", [rawTx])).result; + }); + + it("should appear in the txpool inspection", async function () { + let inspect = await customWeb3Request(context.web3, "txpool_inspect", []); + let data = inspect.result.queued[GENESIS_ACCOUNT][context.web3.utils.toHex(1)]; + expect(data).to.not.be.undefined; + expect(data).to.be.equal( + "0x0000000000000000000000000000000000000000: 0 wei + 1048576 gas x 1000000000 wei" + ); + }); + + it("should appear in the txpool content", async function () { + let content = await customWeb3Request(context.web3, "txpool_content", []); + + const data = content.result.queued[GENESIS_ACCOUNT][context.web3.utils.toHex(1)]; + expect(data).to.include({ + blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + blockNumber: null, + from: GENESIS_ACCOUNT.toString(), + gas: "0x100000", + gasPrice: "0x3b9aca00", + nonce: context.web3.utils.toHex(1), + to: "0x0000000000000000000000000000000000000000", + value: "0x0", + }); + }); +});