// Copyright 2018-2020 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 . //! Chain api required for the transaction pool. use std::{marker::PhantomData, pin::Pin, sync::Arc}; use codec::{Decode, Encode}; use futures::{ channel::oneshot, executor::{ThreadPool, ThreadPoolBuilder}, future::{Future, FutureExt, ready, Ready}, }; use sc_client_api::{ blockchain::HeaderBackend, light::{Fetcher, RemoteCallRequest, RemoteBodyRequest}, BlockBody, }; use sp_core::Hasher; use sp_runtime::{ generic::BlockId, traits::{self, Block as BlockT, BlockIdTo, Header as HeaderT, Hash as HashT}, transaction_validity::TransactionValidity, }; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; use sp_api::ProvideRuntimeApi; use crate::error::{self, Error}; /// The transaction pool logic for full client. pub struct FullChainApi { client: Arc, pool: ThreadPool, _marker: PhantomData, } impl FullChainApi where Block: BlockT, Client: ProvideRuntimeApi + BlockIdTo, { /// Create new transaction pool logic. pub fn new(client: Arc) -> Self { FullChainApi { client, pool: ThreadPoolBuilder::new() .pool_size(2) .name_prefix("txpool-verifier") .create() .expect("Failed to spawn verifier threads, that are critical for node operation."), _marker: Default::default() } } } impl sc_transaction_graph::ChainApi for FullChainApi where Block: BlockT, Client: ProvideRuntimeApi + BlockBody + BlockIdTo, Client: Send + Sync + 'static, Client::Api: TaggedTransactionQueue, sp_api::ApiErrorFor: Send, { type Block = Block; type Hash = Block::Hash; type Error = error::Error; type ValidationFuture = Pin> + Send>>; type BodyFuture = Ready::Extrinsic>>>>; fn block_body(&self, id: &BlockId) -> Self::BodyFuture { ready(self.client.block_body(&id).map_err(|e| error::Error::from(e))) } fn validate_transaction( &self, at: &BlockId, uxt: sc_transaction_graph::ExtrinsicFor, ) -> Self::ValidationFuture { let (tx, rx) = oneshot::channel(); let client = self.client.clone(); let at = at.clone(); self.pool.spawn_ok(futures_diagnose::diagnose("validate-transaction", async move { let res = client.runtime_api().validate_transaction(&at, uxt) .map_err(|e| Error::RuntimeApi(format!("{:?}", e))); if let Err(e) = tx.send(res) { log::warn!("Unable to send a validate transaction result: {:?}", e); } })); Box::pin(async move { match rx.await { Ok(r) => r, Err(_) => Err(Error::RuntimeApi("Validation was canceled".into())), } }) } fn block_id_to_number( &self, at: &BlockId, ) -> error::Result>> { self.client.to_number(at).map_err(|e| Error::BlockIdConversion(format!("{:?}", e))) } fn block_id_to_hash( &self, at: &BlockId, ) -> error::Result>> { self.client.to_hash(at).map_err(|e| Error::BlockIdConversion(format!("{:?}", e))) } fn hash_and_length(&self, ex: &sc_transaction_graph::ExtrinsicFor) -> (Self::Hash, usize) { ex.using_encoded(|x| { (traits::HasherFor::::hash(x), x.len()) }) } } /// The transaction pool logic for light client. pub struct LightChainApi { client: Arc, fetcher: Arc, _phantom: PhantomData, } impl LightChainApi where Block: BlockT, Client: HeaderBackend, F: Fetcher, { /// Create new transaction pool logic. pub fn new(client: Arc, fetcher: Arc) -> Self { LightChainApi { client, fetcher, _phantom: Default::default(), } } } impl sc_transaction_graph::ChainApi for LightChainApi where Block: BlockT, Client: HeaderBackend + 'static, F: Fetcher + 'static, { type Block = Block; type Hash = Block::Hash; type Error = error::Error; type ValidationFuture = Box> + Send + Unpin>; type BodyFuture = Pin::Extrinsic>>>> + Send>>; fn validate_transaction( &self, at: &BlockId, uxt: sc_transaction_graph::ExtrinsicFor, ) -> Self::ValidationFuture { let header_hash = self.client.expect_block_hash_from_id(at); let header_and_hash = header_hash .and_then(|header_hash| self.client.expect_header(BlockId::Hash(header_hash)) .map(|header| (header_hash, header))); let (block, header) = match header_and_hash { Ok((header_hash, header)) => (header_hash, header), Err(err) => return Box::new(ready(Err(err.into()))), }; let remote_validation_request = self.fetcher.remote_call(RemoteCallRequest { block, header, method: "TaggedTransactionQueue_validate_transaction".into(), call_data: uxt.encode(), retry_count: None, }); let remote_validation_request = remote_validation_request.then(move |result| { let result: error::Result = result .map_err(Into::into) .and_then(|result| Decode::decode(&mut &result[..]) .map_err(|e| Error::RuntimeApi( format!("Error decoding tx validation result: {:?}", e) )) ); ready(result) }); Box::new(remote_validation_request) } fn block_id_to_number(&self, at: &BlockId) -> error::Result>> { Ok(self.client.block_number_from_id(at)?) } fn block_id_to_hash(&self, at: &BlockId) -> error::Result>> { Ok(self.client.block_hash_from_id(at)?) } fn hash_and_length(&self, ex: &sc_transaction_graph::ExtrinsicFor) -> (Self::Hash, usize) { ex.using_encoded(|x| { (<::Hashing as HashT>::hash(x), x.len()) }) } fn block_body(&self, id: &BlockId) -> Self::BodyFuture { let header = self.client.header(*id) .and_then(|h| h.ok_or(sp_blockchain::Error::UnknownBlock(format!("{}", id)))); let header = match header { Ok(header) => header, Err(err) => { log::warn!(target: "txpool", "Failed to query header: {:?}", err); return Box::pin(ready(Ok(None))); } }; let fetcher = self.fetcher.clone(); async move { let transactions = fetcher.remote_body({ RemoteBodyRequest { header, retry_count: None, } }) .await .unwrap_or_else(|e| { log::warn!(target: "txpool", "Failed to fetch block body: {:?}", e); Vec::new() }); Ok(Some(transactions)) }.boxed() } }