From b7721881455993d5a3e522bc5c54a46abbec8ec8 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 2 Mar 2018 09:38:58 +0100 Subject: [PATCH] Integrate transaction pool to the proposal logic (#80) * reshuffle consensus libraries * polkadot-useful type definitions for statement table * begin BftService * primary selection logic * bft service implementation without I/O * extract out `BlockImport` trait * allow bft primitives to compile on wasm * Block builder (substrate) * take polkadot-consensus down to the core. * test for preemption * fix test build * Fix wasm build * Bulid on any block * Test for block builder. * Block import tests for client. * Tidy ups * clean up block builder instantiation * justification verification logic * JustifiedHeader and import * Propert block generation for tests * network and tablerouter trait * use statement import to drive creation of further statements * Fixed rpc tests * custom error type for consensus * create proposer * asynchronous proposal evaluation * inherent transactions in polkadot runtime * fix tests to match real polkadot block constraints * implicitly generate inherent functions * add inherent transaction functionality to block body * block builder logic for polkadot * some tests for the polkadot API * avoid redundancy in native code compatibility check * helper for extracting nonce * transaction pool implementation * transaction pool * integrate transaction pool with proposer * indentation * kill storage keys module * accept new transactions to replace old --- polkadot/api/src/lib.rs | 119 ++++--- polkadot/consensus/Cargo.toml | 1 + polkadot/consensus/src/error.rs | 7 + polkadot/consensus/src/lib.rs | 69 +++- polkadot/primitives/src/transaction.rs | 10 +- polkadot/runtime/src/api.rs | 5 +- polkadot/runtime/src/lib.rs | 2 +- polkadot/runtime/src/runtime/system.rs | 31 +- .../release/polkadot_runtime.compact.wasm | Bin 104417 -> 105536 bytes .../release/polkadot_runtime.wasm | Bin 104496 -> 105615 bytes polkadot/transaction-pool/Cargo.toml | 14 + polkadot/transaction-pool/src/lib.rs | 298 ++++++++++++++++++ 12 files changed, 493 insertions(+), 63 deletions(-) create mode 100644 polkadot/transaction-pool/Cargo.toml create mode 100644 polkadot/transaction-pool/src/lib.rs diff --git a/polkadot/api/src/lib.rs b/polkadot/api/src/lib.rs index 0ffb55233a..2eeb0cd06d 100644 --- a/polkadot/api/src/lib.rs +++ b/polkadot/api/src/lib.rs @@ -36,7 +36,7 @@ use polkadot_runtime::runtime; use polkadot_executor::Executor as LocalDispatch; use substrate_executor::{NativeExecutionDispatch, NativeExecutor}; use state_machine::OverlayedChanges; -use primitives::{AccountId, SessionKey, Timestamp}; +use primitives::{AccountId, SessionKey, Timestamp, TxOrder}; use primitives::block::{Id as BlockId, Block, Header, Body}; use primitives::transaction::UncheckedTransaction; use primitives::parachain::DutyRoster; @@ -85,6 +85,7 @@ impl From for Error { } } +/// A builder for blocks. pub trait BlockBuilder: Sized { /// Push a non-inherent transaction. fn push_transaction(&mut self, transaction: UncheckedTransaction) -> Result<()>; @@ -93,40 +94,64 @@ pub trait BlockBuilder: Sized { fn bake(self) -> Block; } +/// A checked block identifier. +pub trait CheckedBlockId: Clone { + /// Yield the underlying block ID. + fn block_id(&self) -> &BlockId; +} + /// Trait encapsulating the Polkadot API. /// /// All calls should fail when the exact runtime is unknown. pub trait PolkadotApi { + /// A checked block ID. Used to avoid redundancy of code check. + type CheckedBlockId: CheckedBlockId; + /// The type used to build blocks. type BlockBuilder: BlockBuilder; + /// Check whether requests at the given block ID can be served. + /// + /// It should not be possible to instantiate this type without going + /// through this function. + fn check_id(&self, id: BlockId) -> Result; + /// Get session keys at a given block. - fn session_keys(&self, at: &BlockId) -> Result>; + fn session_keys(&self, at: &Self::CheckedBlockId) -> Result>; /// Get validators at a given block. - fn validators(&self, at: &BlockId) -> Result>; + fn validators(&self, at: &Self::CheckedBlockId) -> Result>; /// Get the authority duty roster at a block. - fn duty_roster(&self, at: &BlockId) -> Result; + fn duty_roster(&self, at: &Self::CheckedBlockId) -> Result; /// Get the timestamp registered at a block. - fn timestamp(&self, at: &BlockId) -> Result; + fn timestamp(&self, at: &Self::CheckedBlockId) -> Result; + + /// Get the nonce of an account at a block. + fn nonce(&self, at: &Self::CheckedBlockId, account: AccountId) -> Result; + /// Evaluate a block and see if it gives an error. - fn evaluate_block(&self, at: &BlockId, block: Block) -> Result<()>; + fn evaluate_block(&self, at: &Self::CheckedBlockId, block: Block) -> Result<()>; /// Create a block builder on top of the parent block. - fn build_block(&self, parent: &BlockId, timestamp: u64) -> Result; + fn build_block(&self, parent: &Self::CheckedBlockId, timestamp: u64) -> Result; +} + +/// A checked block ID used for the substrate-client implementation of CheckedBlockId; +#[derive(Debug, Clone, Copy)] +pub struct CheckedId(BlockId); + +impl CheckedBlockId for CheckedId { + fn block_id(&self) -> &BlockId { + &self.0 + } } // set up the necessary scaffolding to execute the runtime. macro_rules! with_runtime { ($client: ident, $at: expr, $exec: expr) => {{ - // bail if the code is not the same as the natively linked. - if $client.code_at($at)? != LocalDispatch::native_equivalent() { - bail!(ErrorKind::UnknownRuntime); - } - - $client.state_at($at).map_err(Error::from).and_then(|state| { + $client.state_at($at.block_id()).map_err(Error::from).and_then(|state| { let mut changes = Default::default(); let mut ext = state_machine::Ext { overlay: &mut changes, @@ -141,33 +166,44 @@ macro_rules! with_runtime { impl PolkadotApi for Client> where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> { + type CheckedBlockId = CheckedId; type BlockBuilder = ClientBlockBuilder; - fn session_keys(&self, at: &BlockId) -> Result> { - with_runtime!(self, at, ::runtime::consensus::authorities) - } - - fn validators(&self, at: &BlockId) -> Result> { - with_runtime!(self, at, ::runtime::session::validators) - } - - fn duty_roster(&self, at: &BlockId) -> Result { - with_runtime!(self, at, ::runtime::parachains::calculate_duty_roster) - } - - fn timestamp(&self, at: &BlockId) -> Result { - with_runtime!(self, at, ::runtime::timestamp::get) - } - - fn evaluate_block(&self, at: &BlockId, block: Block) -> Result<()> { - with_runtime!(self, at, || ::runtime::system::internal::execute_block(block)) - } - - fn build_block(&self, parent: &BlockId, timestamp: Timestamp) -> Result { - if self.code_at(parent)? != LocalDispatch::native_equivalent() { + fn check_id(&self, id: BlockId) -> Result { + // bail if the code is not the same as the natively linked. + if self.code_at(&id)? != LocalDispatch::native_equivalent() { bail!(ErrorKind::UnknownRuntime); } + Ok(CheckedId(id)) + } + + fn session_keys(&self, at: &CheckedId) -> Result> { + with_runtime!(self, at, ::runtime::consensus::authorities) + } + + fn validators(&self, at: &CheckedId) -> Result> { + with_runtime!(self, at, ::runtime::session::validators) + } + + fn duty_roster(&self, at: &CheckedId) -> Result { + with_runtime!(self, at, ::runtime::parachains::calculate_duty_roster) + } + + fn timestamp(&self, at: &CheckedId) -> Result { + with_runtime!(self, at, ::runtime::timestamp::get) + } + + fn evaluate_block(&self, at: &CheckedId, block: Block) -> Result<()> { + with_runtime!(self, at, || ::runtime::system::internal::execute_block(block)) + } + + fn nonce(&self, at: &Self::CheckedBlockId, account: AccountId) -> Result { + with_runtime!(self, at, || ::runtime::system::nonce(account)) + } + + fn build_block(&self, parent: &CheckedId, timestamp: Timestamp) -> Result { + let parent = parent.block_id(); let header = Header { parent_hash: self.block_hash_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))?, number: self.block_number_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))? + 1, @@ -316,23 +352,24 @@ mod tests { #[test] fn gets_session_and_validator_keys() { let client = client(); - assert_eq!(client.session_keys(&BlockId::Number(0)).unwrap(), validators()); - assert_eq!(client.validators(&BlockId::Number(0)).unwrap(), validators()); + let id = client.check_id(BlockId::Number(0)).unwrap(); + assert_eq!(client.session_keys(&id).unwrap(), validators()); + assert_eq!(client.validators(&id).unwrap(), validators()); } #[test] fn build_block() { let client = client(); - let block_builder = client.build_block(&BlockId::Number(0), 1_000_000).unwrap(); + let id = client.check_id(BlockId::Number(0)).unwrap(); + let block_builder = client.build_block(&id, 1_000_000).unwrap(); let block = block_builder.bake(); assert_eq!(block.header.number, 1); } #[test] - fn cannot_build_block_on_unknown_parent() { - let client = client(); - assert!(client.build_block(&BlockId::Number(100), 1_000_000).is_err()); + fn fails_to_check_id_for_unknown_block() { + assert!(client().check_id(BlockId::Number(100)).is_err()); } } diff --git a/polkadot/consensus/Cargo.toml b/polkadot/consensus/Cargo.toml index 298d647870..60c8009b7c 100644 --- a/polkadot/consensus/Cargo.toml +++ b/polkadot/consensus/Cargo.toml @@ -13,6 +13,7 @@ polkadot-api = { path = "../api" } polkadot-collator = { path = "../collator" } polkadot-primitives = { path = "../primitives" } polkadot-statement-table = { path = "../statement-table" } +polkadot-transaction-pool = { path = "../transaction-pool" } substrate-bft = { path = "../../substrate/bft" } substrate-codec = { path = "../../substrate/codec" } substrate-primitives = { path = "../../substrate/primitives" } diff --git a/polkadot/consensus/src/error.rs b/polkadot/consensus/src/error.rs index 648072ef0b..4d1387d17e 100644 --- a/polkadot/consensus/src/error.rs +++ b/polkadot/consensus/src/error.rs @@ -41,6 +41,13 @@ error_chain! { description("Proposal had wrong parent hash."), display("Proposal had wrong parent hash. Expected {:?}, got {:?}", expected, got), } + ProposalTooLarge(size: usize) { + description("Proposal exceeded the maximum size."), + display( + "Proposal exceeded the maximum size of {} by {} bytes.", + ::MAX_TRANSACTIONS_SIZE, ::MAX_TRANSACTIONS_SIZE.saturating_sub(*size) + ), + } } } diff --git a/polkadot/consensus/src/lib.rs b/polkadot/consensus/src/lib.rs index ef00318209..7321948dd7 100644 --- a/polkadot/consensus/src/lib.rs +++ b/polkadot/consensus/src/lib.rs @@ -37,6 +37,7 @@ extern crate polkadot_api; extern crate polkadot_collator as collator; extern crate polkadot_statement_table as table; extern crate polkadot_primitives; +extern crate polkadot_transaction_pool as transaction_pool; extern crate substrate_bft as bft; extern crate substrate_codec as codec; extern crate substrate_primitives as primitives; @@ -56,6 +57,7 @@ use polkadot_primitives::block::Block as PolkadotBlock; use polkadot_primitives::parachain::{Id as ParaId, DutyRoster, BlockData, Extrinsic, CandidateReceipt}; use primitives::block::{Block as SubstrateBlock, Header as SubstrateHeader, HeaderHash, Id as BlockId}; use primitives::AuthorityId; +use transaction_pool::TransactionPool; use futures::prelude::*; use futures::future; @@ -65,6 +67,9 @@ pub use self::error::{ErrorKind, Error}; mod error; +// block size limit. +const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024; + /// A handle to a statement table router. pub trait TableRouter { /// Errors when fetching data from the network. @@ -455,6 +460,8 @@ fn make_group_info(roster: DutyRoster, authorities: &[AuthorityId]) -> Result { /// The client instance. pub client: Arc, + /// The transaction pool. + pub transaction_pool: Arc>, /// The backing network handle. pub network: N, } @@ -465,7 +472,9 @@ impl bft::ProposerFactory for ProposerFactory fn init(&self, parent_header: &SubstrateHeader, authorities: &[AuthorityId], sign_with: Arc) -> Result { let parent_hash = parent_header.hash(); - let duty_roster = self.client.duty_roster(&BlockId::Hash(parent_hash))?; + + let checked_id = self.client.check_id(BlockId::Hash(parent_hash))?; + let duty_roster = self.client.duty_roster(&checked_id)?; let group_info = make_group_info(duty_roster, authorities)?; let table = Arc::new(SharedTable::new(group_info, sign_with, parent_hash)); @@ -474,9 +483,11 @@ impl bft::ProposerFactory for ProposerFactory // TODO [PoC-2]: kick off collation process. Ok(Proposer { parent_hash, + parent_id: checked_id, _table: table, _router: router, client: self.client.clone(), + transaction_pool: self.transaction_pool.clone(), }) } } @@ -490,9 +501,11 @@ fn current_timestamp() -> Timestamp { } /// The Polkadot proposer logic. -pub struct Proposer { +pub struct Proposer { parent_hash: HeaderHash, + parent_id: C::CheckedBlockId, client: Arc, + transaction_pool: Arc>, _table: Arc, _router: R, } @@ -503,14 +516,45 @@ impl bft::Proposer for Proposer { type Evaluate = Result; fn propose(&self) -> Result { + use transaction_pool::Ready; + // TODO: handle case when current timestamp behind that in state. - let polkadot_block = self.client.build_block( - &BlockId::Hash(self.parent_hash), + let mut block_builder = self.client.build_block( + &self.parent_id, current_timestamp() - )?.bake(); + )?; - // TODO: integrate transaction queue and `push_transaction`s. + let readiness_evaluator = Ready::create(self.parent_id.clone(), &*self.client); + { + let mut pool = self.transaction_pool.lock(); + let mut unqueue_invalid = Vec::new(); + let mut pending_size = 0; + for pending in pool.pending(readiness_evaluator.clone()) { + // skip and cull transactions which are too large. + if pending.encoded_size() > MAX_TRANSACTIONS_SIZE { + unqueue_invalid.push(pending.hash().clone()); + continue + } + + if pending_size + pending.encoded_size() >= MAX_TRANSACTIONS_SIZE { break } + + match block_builder.push_transaction(pending.as_transaction().clone()) { + Ok(()) => { + pending_size += pending.encoded_size(); + } + Err(_) => { + unqueue_invalid.push(pending.hash().clone()); + } + } + } + + for tx_hash in unqueue_invalid { + pool.remove(&tx_hash, false); + } + } + + let polkadot_block = block_builder.bake(); let substrate_block = Slicable::decode(&mut polkadot_block.encode().as_slice()) .expect("polkadot blocks defined to serialize to substrate blocks correctly; qed"); @@ -519,7 +563,7 @@ impl bft::Proposer for Proposer { // TODO: certain kinds of errors here should lead to a misbehavior report. fn evaluate(&self, proposal: &SubstrateBlock) -> Result { - evaluate_proposal(proposal, &*self.client, current_timestamp(), &self.parent_hash) + evaluate_proposal(proposal, &*self.client, current_timestamp(), &self.parent_hash, &self.parent_id) } } @@ -528,6 +572,7 @@ fn evaluate_proposal( client: &C, now: Timestamp, parent_hash: &HeaderHash, + parent_id: &C::CheckedBlockId, ) -> Result { const MAX_TIMESTAMP_DRIFT: Timestamp = 4; @@ -535,6 +580,14 @@ fn evaluate_proposal( let proposal = PolkadotBlock::decode(&mut &encoded[..]) .ok_or_else(|| ErrorKind::ProposalNotForPolkadot)?; + let transactions_size = proposal.body.transactions.iter().fold(0, |a, tx| { + a + Slicable::encode(tx).len() + }); + + if transactions_size > MAX_TRANSACTIONS_SIZE { + bail!(ErrorKind::ProposalTooLarge(transactions_size)) + } + if proposal.header.parent_hash != *parent_hash { bail!(ErrorKind::WrongParentHash(*parent_hash, proposal.header.parent_hash)); } @@ -551,6 +604,6 @@ fn evaluate_proposal( } // execute the block. - client.evaluate_block(&BlockId::Hash(*parent_hash), proposal)?; + client.evaluate_block(parent_id, proposal)?; Ok(true) } diff --git a/polkadot/primitives/src/transaction.rs b/polkadot/primitives/src/transaction.rs index d0b5ab9557..3d4095b86c 100644 --- a/polkadot/primitives/src/transaction.rs +++ b/polkadot/primitives/src/transaction.rs @@ -237,7 +237,10 @@ impl Function { /// /// Transactions containing inherent functions should not be signed. pub fn is_inherent(&self) -> bool { - self.inherent_index().is_some() + match *self { + Function::Inherent(_) => true, + _ => false, + } } /// If this function is inherent, returns the index it should occupy @@ -370,6 +373,11 @@ impl UncheckedTransaction { } } + /// Whether this transaction invokes an inherent function. + pub fn is_inherent(&self) -> bool { + self.transaction.function.is_inherent() + } + /// Create a new inherent-style transaction from the given function. pub fn inherent(function: InherentFunction) -> Self { UncheckedTransaction { diff --git a/polkadot/runtime/src/api.rs b/polkadot/runtime/src/api.rs index b8371a5da8..7ce51381e9 100644 --- a/polkadot/runtime/src/api.rs +++ b/polkadot/runtime/src/api.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use runtime::{system, parachains, consensus, session, timestamp}; +use runtime::{system, parachains, consensus, session}; impl_stubs!( execute_block => |block| system::internal::execute_block(block), @@ -24,5 +24,6 @@ impl_stubs!( validators => |()| session::validators(), authorities => |()| consensus::authorities(), duty_roster => |()| parachains::calculate_duty_roster(), - get_timestamp => |()| timestamp::get() + timestamp => |()| ::runtime::timestamp::get(), + nonce => |account_id| system::nonce(account_id) ); diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs index 4059a16551..16379cac8e 100644 --- a/polkadot/runtime/src/lib.rs +++ b/polkadot/runtime/src/lib.rs @@ -31,9 +31,9 @@ extern crate polkadot_primitives; #[cfg(test)] #[macro_use] extern crate hex_literal; +pub mod api; pub mod environment; pub mod runtime; -pub mod api; #[cfg(feature = "std")] pub mod genesismap; diff --git a/polkadot/runtime/src/runtime/system.rs b/polkadot/runtime/src/runtime/system.rs index b9d28cf18e..aa6cfc45ce 100644 --- a/polkadot/runtime/src/runtime/system.rs +++ b/polkadot/runtime/src/runtime/system.rs @@ -17,19 +17,26 @@ //! System manager: Handles all of the top-level stuff; executing block/transaction, setting code //! and depositing logs. -use rstd::prelude::*; use rstd::mem; -use runtime_io::{print, storage_root, enumerated_trie_root}; +use rstd::prelude::*; + use codec::{KeyedVec, Slicable}; -use runtime_support::{Hashable, storage}; use environment::with_env; -use polkadot_primitives::{AccountId, Hash, TxOrder, BlockNumber, Block, Header, - UncheckedTransaction, Function, InherentFunction, Log}; +use polkadot_primitives::{ + AccountId, Hash, TxOrder, BlockNumber, Block, Header, + UncheckedTransaction, Function, InherentFunction, Log +}; + +use runtime_io::{print, storage_root, enumerated_trie_root}; +use runtime_support::{Hashable, storage}; use runtime::{staking, session}; -const NONCE_OF: &[u8] = b"sys:non:"; -const BLOCK_HASH_AT: &[u8] = b"sys:old:"; -const TEMP_TRANSACTION_NUMBER: &[u8] = b"temp:txcount:"; +/// Prefixes account ID and stores u64 nonce. +pub const NONCE_OF: &[u8] = b"sys:non:"; +/// Prefixes block number and stores hash of that block. +pub const BLOCK_HASH_AT: &[u8] = b"sys:old:"; +/// Stores the temporary current transaction number. +pub const TEMP_TRANSACTION_NUMBER: &[u8] = b"temp:txcount"; /// The current block number being processed. Set by `execute_block`. pub fn block_number() -> BlockNumber { @@ -53,8 +60,6 @@ pub mod privileged { pub mod internal { use super::*; - struct CheckedTransaction(UncheckedTransaction); - /// Deposits a log and ensures it matches the blocks log data. pub fn deposit_log(log: Log) { with_env(|e| e.digest.logs.push(log)); @@ -141,6 +146,12 @@ pub mod internal { } } +/// Get an account's current nonce. +pub fn nonce(account: AccountId) -> TxOrder { + let nonce_key = account.to_keyed_vec(NONCE_OF); + storage::get_or(&nonce_key, 0) +} + /// Dispatch a function. fn dispatch_function(function: &Function, transactor: &AccountId) { match *function { diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index 2960433376153fae66e4e6110e4cc63b1ad55108..baf86f834c43690f31d70529c1922f3a13cd9555 100644 GIT binary patch delta 8406 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 delta 7275 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 F{{{7t1EK%` diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm index e7a92e3450d71860e7fe808ac128935f91f46ba3..a2beecf10a4be746e6f80c32f7c1ebdcb1dd18d0 100644 GIT binary patch delta 8450 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 utm{R5!(H4z8+iANd2Wo8~+#cAlo?r delta 7410 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 ZJxQq(h8(gbLuqnuM^504a`9"] + +[dependencies] +transaction-pool = "1.9.0" +error-chain = "0.11" +polkadot-api = { path = "../api" } +polkadot-primitives = { path = "../primitives" } +substrate-primitives = { path = "../../substrate/primitives" } +substrate-codec = { path = "../../substrate/codec" } +ed25519 = { path = "../../substrate/ed25519" } +ethereum-types = "0.2" diff --git a/polkadot/transaction-pool/src/lib.rs b/polkadot/transaction-pool/src/lib.rs new file mode 100644 index 0000000000..4ab327bd18 --- /dev/null +++ b/polkadot/transaction-pool/src/lib.rs @@ -0,0 +1,298 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +extern crate transaction_pool; +extern crate polkadot_api; +extern crate polkadot_primitives as primitives; +extern crate substrate_primitives as substrate_primitives; +extern crate substrate_codec as codec; +extern crate ed25519; +extern crate ethereum_types; + +#[macro_use] +extern crate error_chain; + +use std::collections::HashMap; +use std::cmp::Ordering; +use std::sync::Arc; + +use polkadot_api::PolkadotApi; +use primitives::AccountId; +use primitives::transaction::UncheckedTransaction; +use transaction_pool::{Pool, Readiness}; +use transaction_pool::scoring::{Change, Choice}; + +// TODO: make queue generic over hash and sender so we don't need ethereum-types +pub use ethereum_types::{Address as TruncatedAccountId, H256 as TransactionHash}; +pub use transaction_pool::{Options, Status, LightStatus, NoopListener, VerifiedTransaction as VerifiedTransactionOps}; + +/// Truncate an account ID to 160 bits. +pub fn truncate_id(id: &AccountId) -> TruncatedAccountId { + TruncatedAccountId::from_slice(&id[..20]) +} + +/// Iterator over pending transactions. +pub type PendingIterator<'a, C> = + transaction_pool::PendingIterator<'a, VerifiedTransaction, Ready<'a, C>, Scoring, NoopListener>; + +error_chain! { + errors { + /// Attempted to queue an inherent transaction. + IsInherent(tx: UncheckedTransaction) { + description("Inherent transactions cannot be queued."), + display("Inehrent transactions cannot be queued."), + } + /// Attempted to queue a transaction with bad signature. + BadSignature(tx: UncheckedTransaction) { + description("Transaction had bad signature."), + display("Transaction had bad signature."), + } + /// Import error. + Import(err: Box<::std::error::Error + Send>) { + description("Error importing transaction"), + display("Error importing transaction: {}", err.description()), + } + } +} + +/// A verified transaction which should be includable and non-inherent. +#[derive(Debug, Clone)] +pub struct VerifiedTransaction { + inner: UncheckedTransaction, + hash: TransactionHash, + address: TruncatedAccountId, + insertion_id: u64, + encoded_size: usize, +} + +impl VerifiedTransaction { + /// Attempt to verify a transaction. + fn create(tx: UncheckedTransaction, insertion_id: u64) -> Result { + if tx.is_inherent() { + bail!(ErrorKind::IsInherent(tx)) + } + + let message = codec::Slicable::encode(&tx); + if ed25519::verify(&*tx.signature, &message, &tx.transaction.signed[..]) { + // TODO: make transaction-pool use generic types. + let hash = substrate_primitives::hashing::blake2_256(&message); + let address = truncate_id(&tx.transaction.signed); + Ok(VerifiedTransaction { + inner: tx, + hash: hash.into(), + encoded_size: message.len(), + address, + insertion_id, + }) + } else { + Err(ErrorKind::BadSignature(tx).into()) + } + } + + /// Access the underlying transaction. + pub fn as_transaction(&self) -> &UncheckedTransaction { + self.as_ref() + } + + /// Consume the verified transaciton, yielding the unchecked counterpart. + pub fn into_inner(self) -> UncheckedTransaction { + self.inner + } + + /// Get the 256-bit hash of this transaction. + pub fn hash(&self) -> &TransactionHash { + &self.hash + } + + /// Get the truncated account ID of the sender of this transaction. + pub fn sender(&self) -> &TruncatedAccountId { + &self.address + } + + /// Get encoded size of the transaction. + pub fn encoded_size(&self) -> usize { + self.encoded_size + } +} + +impl AsRef for VerifiedTransaction { + fn as_ref(&self) -> &UncheckedTransaction { + &self.inner + } +} + +impl transaction_pool::VerifiedTransaction for VerifiedTransaction { + fn hash(&self) -> &TransactionHash { + &self.hash + } + + fn sender(&self) -> &TruncatedAccountId { + &self.address + } + + fn mem_usage(&self) -> usize { + 1 // TODO + } + + fn insertion_id(&self) -> u64 { + self.insertion_id + } +} + +/// Scoring implementation for polkadot transactions. +pub struct Scoring; + +impl transaction_pool::Scoring for Scoring { + type Score = u64; + + fn compare(&self, old: &VerifiedTransaction, other: &VerifiedTransaction) -> Ordering { + old.inner.transaction.nonce.cmp(&other.inner.transaction.nonce) + } + + fn choose(&self, _old: &VerifiedTransaction, _new: &VerifiedTransaction) -> Choice { + Choice::InsertNew + } + + fn update_scores( + &self, + txs: &[Arc], + scores: &mut [Self::Score], + _change: Change + ) { + for i in 0..txs.len() { + // all the same score since there are no fees. + // TODO: prioritize things like misbehavior or fishermen reports + scores[i] = 1; + } + } + fn should_replace(&self, _old: &VerifiedTransaction, _new: &VerifiedTransaction) -> bool { + false // no fees to determine which is better. + } +} + +/// Readiness evaluator for polkadot transactions. +pub struct Ready<'a, T: 'a + PolkadotApi> { + at_block: T::CheckedBlockId, + api_handle: &'a T, + known_nonces: HashMap, +} + +impl<'a, T: 'a + PolkadotApi> Clone for Ready<'a, T> { + fn clone(&self) -> Self { + Ready { + at_block: self.at_block.clone(), + api_handle: self.api_handle, + known_nonces: self.known_nonces.clone(), + } + } +} + +impl<'a, T: 'a + PolkadotApi> Ready<'a, T> { + /// Create a new readiness evaluator at the given block. Requires that + /// the ID has already been checked for local corresponding and available state. + pub fn create(at: T::CheckedBlockId, client: &'a T) -> Self { + Ready { + at_block: at, + api_handle: client, + known_nonces: HashMap::new(), + } + } +} + +impl<'a, T: 'a + PolkadotApi> transaction_pool::Ready for Ready<'a, T> { + fn is_ready(&mut self, tx: &VerifiedTransaction) -> Readiness { + let sender = tx.inner.transaction.signed; + + // TODO: find a way to handle nonce error properly -- will need changes to + // transaction-pool trait. + let (api_handle, at_block) = (&self.api_handle, &self.at_block); + let next_nonce = self.known_nonces.entry(sender) + .or_insert_with(|| api_handle.nonce(at_block, sender).ok().unwrap_or_else(u64::max_value)); + + *next_nonce += 1; + + match tx.inner.transaction.nonce.cmp(&next_nonce) { + Ordering::Greater => Readiness::Future, + Ordering::Equal => Readiness::Ready, + Ordering::Less => Readiness::Stalled, + } + } +} + +/// The polkadot transaction pool. +/// +/// Wraps a `transaction-pool::Pool`. +pub struct TransactionPool { + inner: transaction_pool::Pool, + insertion_index: u64, // TODO: use AtomicU64 when it stabilizes +} + +impl TransactionPool { + /// Create a new transaction pool. + pub fn new(options: Options) -> Self { + TransactionPool { + inner: Pool::new(NoopListener, Scoring, options), + insertion_index: 0, + } + } + + /// Verify and import a transaction into the pool. + pub fn import(&mut self, tx: UncheckedTransaction) -> Result> { + let insertion_index = self.insertion_index; + self.insertion_index += 1; + + let verified = VerifiedTransaction::create(tx, insertion_index)?; + + // TODO: just use a foreign link when the error type is made public. + self.inner.import(verified) + .map_err(|e| ErrorKind::Import(Box::new(e))) + .map_err(Into::into) + } + + /// Clear the pool. + pub fn clear(&mut self) { + self.inner.clear(); + } + + /// Remove from the pool. + pub fn remove(&mut self, hash: &TransactionHash, is_valid: bool) -> Option> { + self.inner.remove(hash, is_valid) + } + + /// Cull transactions from the queue. + pub fn cull(&mut self, senders: Option<&[TruncatedAccountId]>, ready: Ready) -> usize { + self.inner.cull(senders, ready) + } + + /// Get an iterator of pending transactions. + pub fn pending<'a, T: 'a + PolkadotApi>(&'a self, ready: Ready<'a, T>) -> PendingIterator<'a, T> { + self.inner.pending(ready) + } + + /// Get the full status of the queue (including readiness) + pub fn status(&self, ready: Ready) -> Status { + self.inner.status(ready) + } + + /// Returns light status of the pool. + pub fn light_status(&self) -> LightStatus { + self.inner.light_status() + } +} + +#[cfg(test)] +mod tests { +}