// Copyright 2019 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . //! Node-specific RPC methods for Accounts. use std::sync::Arc; use client::blockchain::HeaderBackend; use jsonrpc_core::{Result, Error, ErrorCode}; use jsonrpc_derive::rpc; use node_primitives::{ AccountId, Index, AccountNonceApi, Block, BlockId, }; use codec::Encode; use sr_primitives::traits; use substrate_primitives::hexdisplay::HexDisplay; use transaction_pool::txpool::{self, Pool}; pub use self::gen_client::Client as AccountsClient; const RUNTIME_ERROR: i64 = 1; /// Accounts RPC methods. #[rpc] pub trait AccountsApi { /// Returns the next valid index (aka nonce) for given account. /// /// This method takes into consideration all pending transactions /// currently in the pool and if no transactions are found in the pool /// it fallbacks to query the index from the runtime (aka. state nonce). #[rpc(name = "account_nextIndex")] fn nonce(&self, account: AccountId) -> Result; } /// An implementation of Accounts specific RPC methods. pub struct Accounts { client: Arc, pool: Arc>, } impl Accounts { /// Create new `Accounts` given client and transaction pool. pub fn new(client: Arc, pool: Arc>) -> Self { Accounts { client, pool } } } impl AccountsApi for Accounts where C: traits::ProvideRuntimeApi, C: HeaderBackend, C: Send + Sync + 'static, C::Api: AccountNonceApi, P: txpool::ChainApi + Sync + Send + 'static, { fn nonce(&self, account: AccountId) -> Result { let api = self.client.runtime_api(); let best = self.client.info().best_hash; let at = BlockId::hash(best); let nonce = api.account_nonce(&at, account.clone()).map_err(|e| Error { code: ErrorCode::ServerError(RUNTIME_ERROR), message: "Unable to query nonce.".into(), data: Some(format!("{:?}", e).into()), })?; log::debug!(target: "rpc", "State nonce for {}: {}", account, nonce); // Now we need to query the transaction pool // and find transactions originating from the same sender. // // Since extrinsics are opaque to us, we look for them using // `provides` tag. And increment the nonce if we find a transaction // that matches the current one. let mut current_nonce = nonce; let mut current_tag = (account.clone(), nonce).encode(); for tx in self.pool.ready() { log::debug!( target: "rpc", "Current nonce to {:?}, checking {} vs {:?}", current_nonce, HexDisplay::from(¤t_tag), tx.provides.iter().map(|x| format!("{}", HexDisplay::from(x))).collect::>(), ); // since transactions in `ready()` need to be ordered by nonce // it's fine to continue with current iterator. if tx.provides.get(0) == Some(¤t_tag) { current_nonce += 1; current_tag = (account.clone(), current_nonce).encode(); } } Ok(current_nonce) } } #[cfg(test)] mod tests { use super::*; use node_runtime::{CheckedExtrinsic, Call, TimestampCall}; use codec::Decode; use node_testing::{ client::{ClientExt, TestClientBuilder, TestClientBuilderExt}, keyring::{self, alice, signed_extra}, }; const VERSION: u32 = node_runtime::VERSION.spec_version; #[test] fn should_return_next_nonce_for_some_account() { // given let _ = env_logger::try_init(); let client = Arc::new(TestClientBuilder::new().build()); let pool = Arc::new(Pool::new(Default::default(), transaction_pool::ChainApi::new(client.clone()))); let new_transaction = |extra| { let ex = CheckedExtrinsic { signed: Some((alice().into(), extra)), function: Call::Timestamp(TimestampCall::set(5)), }; let xt = keyring::sign(ex, VERSION, client.genesis_hash().into()); // Convert to OpaqueExtrinsic let encoded = xt.encode(); node_primitives::UncheckedExtrinsic::decode(&mut &*encoded).unwrap() }; // Populate the pool let ext0 = new_transaction(signed_extra(0, 0)); pool.submit_one(&BlockId::number(0), ext0).unwrap(); let ext1 = new_transaction(signed_extra(1, 0)); pool.submit_one(&BlockId::number(0), ext1).unwrap(); let accounts = Accounts::new(client, pool); // when let nonce = accounts.nonce(alice().into()); // then assert_eq!(nonce.unwrap(), 2); } }