mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-18 01:15:44 +00:00
More generic extrinsic pool
This commit is contained in:
@@ -14,14 +14,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use extrinsic_pool::{self, txpool};
|
||||
use extrinsic_pool;
|
||||
use polkadot_api;
|
||||
use primitives::Hash;
|
||||
use runtime::{Address, UncheckedExtrinsic};
|
||||
|
||||
error_chain! {
|
||||
links {
|
||||
Pool(txpool::Error, txpool::ErrorKind);
|
||||
Pool(extrinsic_pool::Error, extrinsic_pool::ErrorKind);
|
||||
Api(polkadot_api::Error, polkadot_api::ErrorKind);
|
||||
}
|
||||
errors {
|
||||
@@ -33,7 +33,7 @@ error_chain! {
|
||||
/// Attempted to queue an inherent transaction.
|
||||
IsInherent(xt: UncheckedExtrinsic) {
|
||||
description("Inherent transactions cannot be queued."),
|
||||
display("Inehrent transactions cannot be queued."),
|
||||
display("Inherent transactions cannot be queued."),
|
||||
}
|
||||
/// Attempted to queue a transaction with bad signature.
|
||||
BadSignature(e: &'static str) {
|
||||
@@ -63,10 +63,10 @@ error_chain! {
|
||||
}
|
||||
}
|
||||
|
||||
impl extrinsic_pool::api::Error for Error {
|
||||
fn into_pool_error(self) -> ::std::result::Result<txpool::Error, Self> {
|
||||
impl extrinsic_pool::IntoPoolError for Error {
|
||||
fn into_pool_error(self) -> ::std::result::Result<extrinsic_pool::Error, Self> {
|
||||
match self {
|
||||
Error(ErrorKind::Pool(e), c) => Ok(txpool::Error(e, c)),
|
||||
Error(ErrorKind::Pool(e), c) => Ok(extrinsic_pool::Error(e, c)),
|
||||
e => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,26 +38,18 @@ mod error;
|
||||
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, HashMap},
|
||||
ops::Deref,
|
||||
collections::HashMap,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use extrinsic_pool::{
|
||||
api::{ExtrinsicPool, EventStream},
|
||||
txpool::{self, Readiness, scoring::{Change, Choice}},
|
||||
watcher::Watcher,
|
||||
Pool,
|
||||
Listener,
|
||||
};
|
||||
use extrinsic_pool::{Readiness, scoring::{Change, Choice}, VerifiedFor, ExtrinsicFor};
|
||||
use polkadot_api::PolkadotApi;
|
||||
use primitives::{AccountId, BlockId, Hash, Index, UncheckedExtrinsic as FutureProofUncheckedExtrinsic};
|
||||
use primitives::{AccountId, BlockId, Block, Hash, Index};
|
||||
use runtime::{Address, UncheckedExtrinsic};
|
||||
use substrate_primitives::Bytes;
|
||||
use substrate_runtime_primitives::traits::{Bounded, Checkable, Hash as HashT, BlakeTwo256};
|
||||
|
||||
pub use extrinsic_pool::txpool::{Options, Status, LightStatus, VerifiedTransaction as VerifiedTransactionOps};
|
||||
pub use extrinsic_pool::{Options, Status, LightStatus, VerifiedTransaction as VerifiedTransactionOps};
|
||||
pub use error::{Error, ErrorKind, Result};
|
||||
|
||||
/// Maximal size of a single encoded extrinsic.
|
||||
@@ -68,28 +60,20 @@ const MAX_TRANSACTION_SIZE: usize = 4 * 1024 * 1024;
|
||||
/// Type alias for convenience.
|
||||
pub type CheckedExtrinsic = <UncheckedExtrinsic as Checkable<fn(Address) -> std::result::Result<AccountId, &'static str>>>::Checked;
|
||||
|
||||
/// Type alias for polkadot transaction pool.
|
||||
pub type TransactionPool<A> = extrinsic_pool::Pool<ChainApi<A>>;
|
||||
|
||||
/// A verified transaction which should be includable and non-inherent.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct VerifiedTransaction {
|
||||
original: UncheckedExtrinsic,
|
||||
inner: Option<CheckedExtrinsic>,
|
||||
sender: Option<AccountId>,
|
||||
hash: Hash,
|
||||
encoded_size: usize,
|
||||
index: Index,
|
||||
}
|
||||
|
||||
impl VerifiedTransaction {
|
||||
/// Access the underlying transaction.
|
||||
pub fn as_transaction(&self) -> &UncheckedExtrinsic {
|
||||
&self.original
|
||||
}
|
||||
|
||||
/// Convert to primitive unchecked extrinsic.
|
||||
pub fn primitive_extrinsic(&self) -> ::primitives::UncheckedExtrinsic {
|
||||
Decode::decode(&mut self.as_transaction().encode().as_slice())
|
||||
.expect("UncheckedExtrinsic shares repr with Vec<u8>; qed")
|
||||
}
|
||||
|
||||
/// Consume the verified transaction, yielding the checked counterpart.
|
||||
pub fn into_inner(self) -> Option<CheckedExtrinsic> {
|
||||
self.inner
|
||||
@@ -107,7 +91,7 @@ impl VerifiedTransaction {
|
||||
|
||||
/// Get the account ID of the sender of this transaction.
|
||||
pub fn index(&self) -> Index {
|
||||
self.original.extrinsic.index
|
||||
self.index
|
||||
}
|
||||
|
||||
/// Get encoded size of the transaction.
|
||||
@@ -121,7 +105,7 @@ impl VerifiedTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
impl txpool::VerifiedTransaction for VerifiedTransaction {
|
||||
impl extrinsic_pool::VerifiedTransaction for VerifiedTransaction {
|
||||
type Hash = Hash;
|
||||
type Sender = Option<AccountId>;
|
||||
|
||||
@@ -138,139 +122,25 @@ impl txpool::VerifiedTransaction for VerifiedTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Scoring implementation for polkadot transactions.
|
||||
#[derive(Debug)]
|
||||
pub struct Scoring;
|
||||
|
||||
impl txpool::Scoring<VerifiedTransaction> for Scoring {
|
||||
type Score = u64;
|
||||
type Event = ();
|
||||
|
||||
fn compare(&self, old: &VerifiedTransaction, other: &VerifiedTransaction) -> Ordering {
|
||||
old.index().cmp(&other.index())
|
||||
}
|
||||
|
||||
fn choose(&self, old: &VerifiedTransaction, new: &VerifiedTransaction) -> Choice {
|
||||
if old.is_fully_verified() {
|
||||
assert!(new.is_fully_verified(), "Scoring::choose called with transactions from different senders");
|
||||
if old.index() == new.index() {
|
||||
// TODO [ToDr] Do we allow replacement? If yes then it should be Choice::ReplaceOld
|
||||
return Choice::RejectNew;
|
||||
}
|
||||
}
|
||||
|
||||
// This will keep both transactions, even though they have the same indices.
|
||||
// It's fine for not fully verified transactions, we might also allow it for
|
||||
// verified transactions but it would mean that only one of the two is actually valid
|
||||
// (most likely the first to be included in the block).
|
||||
Choice::InsertNew
|
||||
}
|
||||
|
||||
fn update_scores(
|
||||
&self,
|
||||
xts: &[txpool::Transaction<VerifiedTransaction>],
|
||||
scores: &mut [Self::Score],
|
||||
_change: Change<()>
|
||||
) {
|
||||
for i in 0..xts.len() {
|
||||
if !xts[i].is_fully_verified() {
|
||||
scores[i] = 0;
|
||||
} else {
|
||||
// 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) -> Choice {
|
||||
// Always replace not fully verified transactions.
|
||||
match old.is_fully_verified() {
|
||||
true => Choice::RejectNew,
|
||||
false => Choice::ReplaceOld
|
||||
}
|
||||
}
|
||||
/// The polkadot transaction pool logic.
|
||||
pub struct ChainApi<A> {
|
||||
api: Arc<A>,
|
||||
}
|
||||
|
||||
/// Readiness evaluator for polkadot transactions.
|
||||
pub struct Ready<'a, A: 'a + PolkadotApi> {
|
||||
at_block: BlockId,
|
||||
api: &'a A,
|
||||
known_nonces: HashMap<AccountId, ::primitives::Index>,
|
||||
}
|
||||
|
||||
impl<'a, A: 'a + PolkadotApi> Ready<'a, A> {
|
||||
/// Create a new readiness evaluator at the given block. Requires that
|
||||
/// the ID has already been checked for local corresponding and available state.
|
||||
fn create(at: BlockId, api: &'a A) -> Self {
|
||||
Ready {
|
||||
at_block: at,
|
||||
api,
|
||||
known_nonces: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + PolkadotApi> Clone for Ready<'a, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Ready {
|
||||
at_block: self.at_block.clone(),
|
||||
api: self.api,
|
||||
known_nonces: self.known_nonces.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: 'a + PolkadotApi> txpool::Ready<VerifiedTransaction> for Ready<'a, A>
|
||||
{
|
||||
fn is_ready(&mut self, xt: &VerifiedTransaction) -> Readiness {
|
||||
let sender = match xt.sender() {
|
||||
Some(sender) => sender,
|
||||
None => return Readiness::Future
|
||||
};
|
||||
|
||||
trace!(target: "transaction-pool", "Checking readiness of {} (from {})", xt.hash, Hash::from(sender));
|
||||
|
||||
// TODO: find a way to handle index error properly -- will need changes to
|
||||
// transaction-pool trait.
|
||||
let (api, at_block) = (&self.api, &self.at_block);
|
||||
let next_index = self.known_nonces.entry(sender)
|
||||
.or_insert_with(|| api.index(at_block, sender).ok().unwrap_or_else(Bounded::max_value));
|
||||
|
||||
trace!(target: "transaction-pool", "Next index for sender is {}; xt index is {}", next_index, xt.original.extrinsic.index);
|
||||
|
||||
let result = match xt.original.extrinsic.index.cmp(&next_index) {
|
||||
// TODO: this won't work perfectly since accounts can now be killed, returning the nonce
|
||||
// to zero.
|
||||
// We should detect if the index was reset and mark all transactions as `Stale` for cull to work correctly.
|
||||
// Otherwise those transactions will keep occupying the queue.
|
||||
// Perhaps we could mark as stale if `index - state_index` > X?
|
||||
Ordering::Greater => Readiness::Future,
|
||||
Ordering::Equal => Readiness::Ready,
|
||||
// TODO [ToDr] Should mark transactions referrencing too old blockhash as `Stale` as well.
|
||||
Ordering::Less => Readiness::Stale,
|
||||
};
|
||||
|
||||
// remember to increment `next_index`
|
||||
*next_index = next_index.saturating_add(1);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Verifier<'a, A: 'a> {
|
||||
api: &'a A,
|
||||
at_block: BlockId,
|
||||
}
|
||||
|
||||
impl<'a, A> Verifier<'a, A> where
|
||||
A: 'a + PolkadotApi,
|
||||
impl<A> ChainApi<A> where
|
||||
A: PolkadotApi,
|
||||
{
|
||||
const NO_ACCOUNT: &'static str = "Account not found.";
|
||||
/// Create a new instance.
|
||||
pub fn new(api: Arc<A>) -> Self {
|
||||
ChainApi {
|
||||
api,
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup(&self, address: Address) -> ::std::result::Result<AccountId, &'static str> {
|
||||
fn lookup(&self, at: &BlockId, address: Address) -> ::std::result::Result<AccountId, &'static str> {
|
||||
// TODO [ToDr] Consider introducing a cache for this.
|
||||
match self.api.lookup(&self.at_block, address.clone()) {
|
||||
match self.api.lookup(at, address.clone()) {
|
||||
Ok(Some(address)) => Ok(address),
|
||||
Ok(None) => Err(Self::NO_ACCOUNT.into()),
|
||||
Err(e) => {
|
||||
@@ -281,28 +151,32 @@ impl<'a, A> Verifier<'a, A> where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A> txpool::Verifier<UncheckedExtrinsic> for Verifier<'a, A> where
|
||||
A: 'a + PolkadotApi,
|
||||
impl<A> extrinsic_pool::ChainApi for ChainApi<A> where
|
||||
A: PolkadotApi + Send + Sync,
|
||||
{
|
||||
type VerifiedTransaction = VerifiedTransaction;
|
||||
type Block = Block;
|
||||
type Hash = Hash;
|
||||
type Sender = Option<AccountId>;
|
||||
type VEx = VerifiedTransaction;
|
||||
type Ready = HashMap<AccountId, u32>;
|
||||
type Error = Error;
|
||||
type Score = u64;
|
||||
type Event = ();
|
||||
|
||||
fn verify_transaction(&self, uxt: UncheckedExtrinsic) -> Result<Self::VerifiedTransaction> {
|
||||
fn verify_transaction(&self, at: &BlockId, xt: &ExtrinsicFor<Self>) -> Result<Self::VEx> {
|
||||
let encoded = xt.encode();
|
||||
let uxt = UncheckedExtrinsic::decode(&mut encoded.as_slice()).ok_or_else(|| ErrorKind::InvalidExtrinsicFormat)?;
|
||||
if !uxt.is_signed() {
|
||||
bail!(ErrorKind::IsInherent(uxt))
|
||||
}
|
||||
|
||||
let encoded = uxt.encode();
|
||||
let encoded_size = encoded.len();
|
||||
|
||||
let (encoded_size, hash) = (encoded.len(), BlakeTwo256::hash(&encoded));
|
||||
if encoded_size > MAX_TRANSACTION_SIZE {
|
||||
bail!(ErrorKind::TooLarge(encoded_size, MAX_TRANSACTION_SIZE));
|
||||
}
|
||||
|
||||
let hash = BlakeTwo256::hash(&encoded);
|
||||
debug!(target: "transaction-pool", "Transaction submitted: {}", ::substrate_primitives::hexdisplay::HexDisplay::from(&encoded));
|
||||
|
||||
let inner = match uxt.clone().check_with(|a| self.lookup(a)) {
|
||||
let inner = match uxt.clone().check_with(|a| self.lookup(at, a)) {
|
||||
Ok(xt) => Some(xt),
|
||||
// keep the transaction around in the future pool and attempt to promote it later.
|
||||
Err(Self::NO_ACCOUNT) => None,
|
||||
@@ -317,157 +191,101 @@ impl<'a, A> txpool::Verifier<UncheckedExtrinsic> for Verifier<'a, A> where
|
||||
}
|
||||
|
||||
Ok(VerifiedTransaction {
|
||||
original: uxt,
|
||||
index: uxt.extrinsic.index,
|
||||
inner,
|
||||
sender,
|
||||
hash,
|
||||
encoded_size
|
||||
encoded_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The polkadot transaction pool.
|
||||
///
|
||||
/// Wraps a `extrinsic_pool::Pool`.
|
||||
pub struct TransactionPool<A> {
|
||||
inner: Pool<Hash, VerifiedTransaction, Scoring, Error>,
|
||||
api: Arc<A>,
|
||||
}
|
||||
fn ready(&self) -> Self::Ready {
|
||||
HashMap::default()
|
||||
}
|
||||
|
||||
impl<A> TransactionPool<A> where
|
||||
A: PolkadotApi,
|
||||
{
|
||||
/// Create a new transaction pool.
|
||||
pub fn new(options: Options, api: Arc<A>) -> Self {
|
||||
TransactionPool {
|
||||
inner: Pool::new(options, Scoring),
|
||||
api,
|
||||
fn is_ready(&self, at: &BlockId, known_nonces: &mut Self::Ready, xt: &VerifiedFor<Self>) -> Readiness {
|
||||
let sender = match xt.verified.sender() {
|
||||
Some(sender) => sender,
|
||||
None => return Readiness::Future
|
||||
};
|
||||
|
||||
trace!(target: "transaction-pool", "Checking readiness of {} (from {})", xt.verified.hash, Hash::from(sender));
|
||||
|
||||
// TODO: find a way to handle index error properly -- will need changes to
|
||||
// transaction-pool trait.
|
||||
let api = &self.api;
|
||||
let next_index = known_nonces.entry(sender)
|
||||
.or_insert_with(|| api.index(at, sender).ok().unwrap_or_else(Bounded::max_value));
|
||||
|
||||
trace!(target: "transaction-pool", "Next index for sender is {}; xt index is {}", next_index, xt.verified.index);
|
||||
|
||||
let result = match xt.verified.index.cmp(&next_index) {
|
||||
// TODO: this won't work perfectly since accounts can now be killed, returning the nonce
|
||||
// to zero.
|
||||
// We should detect if the index was reset and mark all transactions as `Stale` for cull to work correctly.
|
||||
// Otherwise those transactions will keep occupying the queue.
|
||||
// Perhaps we could mark as stale if `index - state_index` > X?
|
||||
Ordering::Greater => Readiness::Future,
|
||||
Ordering::Equal => Readiness::Ready,
|
||||
// TODO [ToDr] Should mark transactions referencing too old blockhash as `Stale` as well.
|
||||
Ordering::Less => Readiness::Stale,
|
||||
};
|
||||
|
||||
// remember to increment `next_index`
|
||||
*next_index = next_index.saturating_add(1);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn compare(old: &VerifiedFor<Self>, other: &VerifiedFor<Self>) -> Ordering {
|
||||
old.verified.index().cmp(&other.verified.index())
|
||||
}
|
||||
|
||||
fn choose(old: &VerifiedFor<Self>, new: &VerifiedFor<Self>) -> Choice {
|
||||
if old.verified.is_fully_verified() {
|
||||
assert!(new.verified.is_fully_verified(), "Scoring::choose called with transactions from different senders");
|
||||
if old.verified.index() == new.verified.index() {
|
||||
return Choice::ReplaceOld;
|
||||
}
|
||||
}
|
||||
|
||||
// This will keep both transactions, even though they have the same indices.
|
||||
// It's fine for not fully verified transactions, we might also allow it for
|
||||
// verified transactions but it would mean that only one of the two is actually valid
|
||||
// (most likely the first to be included in the block).
|
||||
Choice::InsertNew
|
||||
}
|
||||
|
||||
fn update_scores(
|
||||
xts: &[extrinsic_pool::Transaction<VerifiedFor<Self>>],
|
||||
scores: &mut [Self::Score],
|
||||
_change: Change<()>
|
||||
) {
|
||||
for i in 0..xts.len() {
|
||||
if !xts[i].verified.is_fully_verified() {
|
||||
scores[i] = 0;
|
||||
} else {
|
||||
// all the same score since there are no fees.
|
||||
// TODO: prioritize things like misbehavior or fishermen reports
|
||||
scores[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to directly import `UncheckedExtrinsic` without going through serialization.
|
||||
pub fn import_unchecked_extrinsic(&self, block: BlockId, uxt: UncheckedExtrinsic) -> Result<Arc<VerifiedTransaction>> {
|
||||
let verifier = Verifier {
|
||||
api: &*self.api,
|
||||
at_block: block,
|
||||
};
|
||||
self.inner.submit(verifier, vec![uxt]).map(|mut v| v.swap_remove(0))
|
||||
}
|
||||
|
||||
/// Retry to import all semi-verified transactions (unknown account indices)
|
||||
pub fn retry_verification(&self, block: BlockId) -> Result<()> {
|
||||
let to_reverify = self.inner.remove_sender(None);
|
||||
let verifier = Verifier {
|
||||
api: &*self.api,
|
||||
at_block: block,
|
||||
};
|
||||
|
||||
self.inner.submit(verifier, to_reverify.into_iter().map(|tx| tx.original.clone()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reverify transaction that has been reported incorrect.
|
||||
///
|
||||
/// Returns `Ok(None)` in case the hash is missing, `Err(e)` in case of verification error and new transaction
|
||||
/// reference otherwise.
|
||||
///
|
||||
/// TODO [ToDr] That method is currently unused, should be used together with BlockBuilder
|
||||
/// when we detect that particular transaction has failed.
|
||||
/// In such case we will attempt to remove or re-verify it.
|
||||
pub fn reverify_transaction(&self, block: BlockId, hash: Hash) -> Result<Option<Arc<VerifiedTransaction>>> {
|
||||
let result = self.inner.remove(&[hash], false).pop().expect("One hash passed; one result received; qed");
|
||||
if let Some(tx) = result {
|
||||
self.import_unchecked_extrinsic(block, tx.original.clone()).map(Some)
|
||||
fn should_replace(old: &VerifiedFor<Self>, _new: &VerifiedFor<Self>) -> Choice {
|
||||
if old.verified.is_fully_verified() {
|
||||
// Don't allow new transactions if we are reaching the limit.
|
||||
Choice::RejectNew
|
||||
} else {
|
||||
Ok(None)
|
||||
// Always replace not fully verified transactions.
|
||||
Choice::ReplaceOld
|
||||
}
|
||||
}
|
||||
|
||||
/// Cull old transactions from the queue.
|
||||
pub fn cull(&self, block: BlockId) -> Result<usize> {
|
||||
let ready = Ready::create(block, &*self.api);
|
||||
Ok(self.inner.cull(None, ready))
|
||||
}
|
||||
|
||||
/// Cull transactions from the queue and then compute the pending set.
|
||||
pub fn cull_and_get_pending<F, T>(&self, block: BlockId, f: F) -> Result<T> where
|
||||
F: FnOnce(txpool::PendingIterator<VerifiedTransaction, Ready<A>, Scoring, Listener<Hash>>) -> T,
|
||||
{
|
||||
let ready = Ready::create(block, &*self.api);
|
||||
self.inner.cull(None, ready.clone());
|
||||
Ok(self.inner.pending(ready, f))
|
||||
}
|
||||
|
||||
/// Remove a set of transactions idenitified by hashes.
|
||||
pub fn remove(&self, hashes: &[Hash], is_valid: bool) -> Vec<Option<Arc<VerifiedTransaction>>> {
|
||||
self.inner.remove(hashes, is_valid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> Deref for TransactionPool<A> {
|
||||
type Target = Pool<Hash, VerifiedTransaction, Scoring, Error>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: more general transaction pool, which can handle more kinds of vec-encoded transactions,
|
||||
// even when runtime is out of date.
|
||||
impl<A> ExtrinsicPool<FutureProofUncheckedExtrinsic, BlockId, Hash> for TransactionPool<A> where
|
||||
A: Send + Sync + 'static,
|
||||
A: PolkadotApi,
|
||||
{
|
||||
type Error = Error;
|
||||
type InPool = BTreeMap<AccountId, Vec<Bytes>>;
|
||||
|
||||
fn submit(&self, block: BlockId, xts: Vec<FutureProofUncheckedExtrinsic>) -> Result<Vec<Hash>> {
|
||||
xts.into_iter()
|
||||
.map(|xt| xt.encode())
|
||||
.map(|encoded| {
|
||||
let decoded = UncheckedExtrinsic::decode(&mut &encoded[..]).ok_or(ErrorKind::InvalidExtrinsicFormat)?;
|
||||
let tx = self.import_unchecked_extrinsic(block, decoded)?;
|
||||
Ok(*tx.hash())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn submit_and_watch(&self, block: BlockId, xt: FutureProofUncheckedExtrinsic) -> Result<Watcher<Hash>> {
|
||||
let encoded = xt.encode();
|
||||
let decoded = UncheckedExtrinsic::decode(&mut &encoded[..]).ok_or(ErrorKind::InvalidExtrinsicFormat)?;
|
||||
|
||||
let verifier = Verifier {
|
||||
api: &*self.api,
|
||||
at_block: block,
|
||||
};
|
||||
|
||||
self.inner.submit_and_watch(verifier, decoded)
|
||||
}
|
||||
|
||||
fn light_status(&self) -> LightStatus {
|
||||
self.inner.light_status()
|
||||
}
|
||||
|
||||
fn import_notification_stream(&self) -> EventStream {
|
||||
self.inner.import_notification_stream()
|
||||
}
|
||||
|
||||
fn all(&self) -> Self::InPool {
|
||||
self.inner.all(|it| it.fold(Default::default(), |mut map: Self::InPool, tx| {
|
||||
// Map with `null` key is not serializable, so we fallback to default accountId.
|
||||
map.entry(tx.sender().unwrap_or_default())
|
||||
.or_insert_with(Vec::new)
|
||||
// use bytes type to make it serialize nicer.
|
||||
.push(Bytes(tx.primitive_extrinsic()));
|
||||
map
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{atomic::{self, AtomicBool}, Arc};
|
||||
use super::TransactionPool;
|
||||
use substrate_keyring::Keyring::{self, *};
|
||||
use codec::{Decode, Encode};
|
||||
use polkadot_api::{PolkadotApi, BlockBuilder, Result};
|
||||
@@ -476,6 +294,8 @@ mod tests {
|
||||
use runtime::{RawAddress, Call, TimestampCall, BareExtrinsic, Extrinsic, UncheckedExtrinsic};
|
||||
use primitives::parachain::{DutyRoster, Id as ParaId};
|
||||
use substrate_runtime_primitives::{MaybeUnsigned, generic};
|
||||
use extrinsic_pool::Pool;
|
||||
use super::ChainApi;
|
||||
|
||||
struct TestBlockBuilder;
|
||||
impl BlockBuilder for TestBlockBuilder {
|
||||
@@ -545,7 +365,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn uxt(who: Keyring, nonce: Index, use_id: bool) -> UncheckedExtrinsic {
|
||||
fn uxt(who: Keyring, nonce: Index, use_id: bool) -> FutureProofUncheckedExtrinsic {
|
||||
let sxt = BareExtrinsic {
|
||||
signed: who.to_raw_public().into(),
|
||||
index: nonce,
|
||||
@@ -567,20 +387,20 @@ mod tests {
|
||||
)},
|
||||
index: sxt.index,
|
||||
function: sxt.function,
|
||||
}, MaybeUnsigned(sig.into())).using_encoded(|e| UncheckedExtrinsic::decode(&mut &e[..])).unwrap()
|
||||
}, MaybeUnsigned(sig.into())).using_encoded(|e| FutureProofUncheckedExtrinsic::decode(&mut &e[..])).unwrap()
|
||||
}
|
||||
|
||||
fn pool(api: &TestPolkadotApi) -> TransactionPool<TestPolkadotApi> {
|
||||
TransactionPool::new(Default::default(), Arc::new(api.clone()))
|
||||
fn pool(api: &TestPolkadotApi) -> Pool<ChainApi<TestPolkadotApi>> {
|
||||
Pool::new(Default::default(), ChainApi { api: Arc::new(api.clone()) })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_submission_should_work() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209)]);
|
||||
}
|
||||
|
||||
@@ -588,9 +408,9 @@ mod tests {
|
||||
fn index_submission_should_work() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209)]);
|
||||
}
|
||||
|
||||
@@ -598,10 +418,10 @@ mod tests {
|
||||
fn multiple_id_submission_should_work() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209), (Some(Alice.to_raw_public().into()), 210)]);
|
||||
}
|
||||
|
||||
@@ -609,10 +429,10 @@ mod tests {
|
||||
fn multiple_index_submission_should_work() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, false)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210, false)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209), (Some(Alice.to_raw_public().into()), 210)]);
|
||||
}
|
||||
|
||||
@@ -620,9 +440,9 @@ mod tests {
|
||||
fn id_based_early_nonce_should_be_culled() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 208, true)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 208, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
}
|
||||
|
||||
@@ -630,9 +450,9 @@ mod tests {
|
||||
fn index_based_early_nonce_should_be_culled() {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 208, false)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 208, false)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
}
|
||||
|
||||
@@ -641,12 +461,12 @@ mod tests {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209), (Some(Alice.to_raw_public().into()), 210)]);
|
||||
}
|
||||
|
||||
@@ -655,12 +475,12 @@ mod tests {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, false)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210, false)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![(Some(Alice.to_raw_public().into()), 209), (Some(Alice.to_raw_public().into()), 210)]);
|
||||
}
|
||||
|
||||
@@ -668,16 +488,16 @@ mod tests {
|
||||
fn index_then_id_submission_should_make_progress() {
|
||||
let api = TestPolkadotApi::without_lookup();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
api.enable_lookup();
|
||||
pool.retry_verification(BlockId::number(0)).unwrap();
|
||||
pool.retry_verification(&BlockId::number(0), None).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![
|
||||
(Some(Alice.to_raw_public().into()), 209),
|
||||
(Some(Alice.to_raw_public().into()), 210)
|
||||
@@ -688,15 +508,15 @@ mod tests {
|
||||
fn retrying_verification_might_not_change_anything() {
|
||||
let api = TestPolkadotApi::without_lookup();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209, false)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210, true)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
pool.retry_verification(BlockId::number(1)).unwrap();
|
||||
pool.retry_verification(&BlockId::number(1), None).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
}
|
||||
|
||||
@@ -704,19 +524,19 @@ mod tests {
|
||||
fn id_then_index_submission_should_make_progress() {
|
||||
let api = TestPolkadotApi::without_lookup();
|
||||
let pool = pool(&api);
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
pool.import_unchecked_extrinsic(BlockId::number(0), uxt(Alice, 210, false)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 209, true)).unwrap();
|
||||
pool.submit_one(&BlockId::number(0), uxt(Alice, 210, false)).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![
|
||||
(Some(Alice.to_raw_public().into()), 209)
|
||||
]);
|
||||
|
||||
// when
|
||||
api.enable_lookup();
|
||||
pool.retry_verification(BlockId::number(0)).unwrap();
|
||||
pool.retry_verification(&BlockId::number(0), None).unwrap();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(0), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(0), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![
|
||||
(Some(Alice.to_raw_public().into()), 209),
|
||||
(Some(Alice.to_raw_public().into()), 210)
|
||||
@@ -728,10 +548,10 @@ mod tests {
|
||||
let api = TestPolkadotApi::default();
|
||||
let pool = pool(&api);
|
||||
let block = BlockId::number(0);
|
||||
pool.import_unchecked_extrinsic(block, uxt(Alice, 209, false)).unwrap();
|
||||
let hash = *pool.import_unchecked_extrinsic(block, uxt(Alice, 210, false)).unwrap().hash();
|
||||
pool.submit_one(&block, uxt(Alice, 209, false)).unwrap();
|
||||
let hash = *pool.submit_one(&block, uxt(Alice, 210, false)).unwrap().verified.hash();
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(block, |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&block, |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![
|
||||
(Some(Alice.to_raw_public().into()), 209),
|
||||
(Some(Alice.to_raw_public().into()), 210)
|
||||
@@ -745,13 +565,13 @@ mod tests {
|
||||
|
||||
// after this, a re-evaluation of the second's readiness should result in it being thrown
|
||||
// out (or maybe placed in future queue).
|
||||
let err = pool.reverify_transaction(BlockId::number(1), hash).unwrap_err();
|
||||
let err = pool.reverify_transaction(&BlockId::number(1), hash).unwrap_err();
|
||||
match *err.kind() {
|
||||
::error::ErrorKind::Msg(ref m) if m == "bad signature in extrinsic" => {},
|
||||
ref e => assert!(false, "The transaction should be rejected with BadSignature error, got: {:?}", e),
|
||||
}
|
||||
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(BlockId::number(1), |p| p.map(|a| (a.sender(), a.index())).collect()).unwrap();
|
||||
let pending: Vec<_> = pool.cull_and_get_pending(&BlockId::number(1), |p| p.map(|a| (a.verified.sender(), a.verified.index())).collect()).unwrap();
|
||||
assert_eq!(pending, vec![]);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user