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 89ed90c99d..1a7785d626 100644 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ 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 3ca31b5a11..7528a02d42 100644 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ diff --git a/polkadot/transaction-pool/Cargo.toml b/polkadot/transaction-pool/Cargo.toml new file mode 100644 index 0000000000..51271feece --- /dev/null +++ b/polkadot/transaction-pool/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "polkadot-transaction-pool" +version = "0.1.0" +authors = ["Parity Technologies "] + +[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 { +}