// This file is part of Substrate.
// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see .
use std::{
collections::{HashSet, HashMap},
hash,
sync::Arc,
};
use serde::Serialize;
use parking_lot::{Mutex, RwLock};
use sp_runtime::{
generic::BlockId,
traits::{self, SaturatedConversion},
transaction_validity::{TransactionTag as Tag, ValidTransaction, TransactionSource},
};
use sp_transaction_pool::{error, PoolStatus};
use wasm_timer::Instant;
use futures::channel::mpsc::{channel, Sender};
use retain_mut::RetainMut;
use crate::base_pool::{self as base, PruneStatus};
use crate::listener::Listener;
use crate::rotator::PoolRotator;
use crate::watcher::Watcher;
use crate::pool::{
EventStream, Options, ChainApi, BlockHash, ExtrinsicHash, ExtrinsicFor, TransactionFor,
};
/// Pre-validated transaction. Validated pool only accepts transactions wrapped in this enum.
#[derive(Debug)]
pub enum ValidatedTransaction {
/// Transaction that has been validated successfully.
Valid(base::Transaction),
/// Transaction that is invalid.
Invalid(Hash, Error),
/// Transaction which validity can't be determined.
///
/// We're notifying watchers about failure, if 'unknown' transaction is submitted.
Unknown(Hash, Error),
}
impl ValidatedTransaction {
/// Consume validity result, transaction data and produce ValidTransaction.
pub fn valid_at(
at: u64,
hash: Hash,
source: TransactionSource,
data: Ex,
bytes: usize,
validity: ValidTransaction,
) -> Self {
Self::Valid(base::Transaction {
data,
bytes,
hash,
source,
priority: validity.priority,
requires: validity.requires,
provides: validity.provides,
propagate: validity.propagate,
valid_till: at
.saturated_into::()
.saturating_add(validity.longevity),
})
}
}
/// A type of validated transaction stored in the pool.
pub type ValidatedTransactionFor = ValidatedTransaction<
ExtrinsicHash,
ExtrinsicFor,
::Error,
>;
/// A closure that returns true if the local node is a validator that can author blocks.
pub struct IsValidator(Box bool + Send + Sync>);
impl From for IsValidator {
fn from(is_validator: bool) -> Self {
Self(Box::new(move || is_validator))
}
}
impl From bool + Send + Sync>> for IsValidator {
fn from(is_validator: Box bool + Send + Sync>) -> Self {
Self(is_validator)
}
}
/// Pool that deals with validated transactions.
pub struct ValidatedPool {
api: Arc,
is_validator: IsValidator,
options: Options,
listener: RwLock, B>>,
pool: RwLock,
ExtrinsicFor,
>>,
import_notification_sinks: Mutex>>>,
rotator: PoolRotator>,
}
#[cfg(not(target_os = "unknown"))]
impl parity_util_mem::MallocSizeOf for ValidatedPool
where
ExtrinsicFor: parity_util_mem::MallocSizeOf,
{
fn size_of(&self, ops: &mut parity_util_mem::MallocSizeOfOps) -> usize {
// other entries insignificant or non-primary references
self.pool.size_of(ops)
}
}
impl ValidatedPool {
/// Create a new transaction pool.
pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self {
let base_pool = base::BasePool::new(options.reject_future_transactions);
Self {
is_validator,
options,
listener: Default::default(),
api,
pool: RwLock::new(base_pool),
import_notification_sinks: Default::default(),
rotator: Default::default(),
}
}
/// Bans given set of hashes.
pub fn ban(&self, now: &Instant, hashes: impl IntoIterator>) {
self.rotator.ban(now, hashes)
}
/// Returns true if transaction with given hash is currently banned from the pool.
pub fn is_banned(&self, hash: &ExtrinsicHash) -> bool {
self.rotator.is_banned(hash)
}
/// A fast check before doing any further processing of a transaction, like validation.
///
/// If `ingore_banned` is `true`, it will not check if the transaction is banned.
///
/// It checks if the transaction is already imported or banned. If so, it returns an error.
pub fn check_is_known(
&self,
tx_hash: &ExtrinsicHash,
ignore_banned: bool,
) -> Result<(), B::Error> {
if !ignore_banned && self.is_banned(tx_hash) {
Err(error::Error::TemporarilyBanned.into())
} else if self.pool.read().is_imported(tx_hash) {
Err(error::Error::AlreadyImported(Box::new(*tx_hash)).into())
} else {
Ok(())
}
}
/// Imports a bunch of pre-validated transactions to the pool.
pub fn submit(
&self,
txs: impl IntoIterator>,
) -> Vec, B::Error>> {
let results = txs.into_iter()
.map(|validated_tx| self.submit_one(validated_tx))
.collect::>();
// only enforce limits if there is at least one imported transaction
let removed = if results.iter().any(|res| res.is_ok()) {
self.enforce_limits()
} else {
Default::default()
};
results.into_iter().map(|res| match res {
Ok(ref hash) if removed.contains(hash) => Err(error::Error::ImmediatelyDropped.into()),
other => other,
}).collect()
}
/// Submit single pre-validated transaction to the pool.
fn submit_one(&self, tx: ValidatedTransactionFor) -> Result, B::Error> {
match tx {
ValidatedTransaction::Valid(tx) => {
if !tx.propagate && !(self.is_validator.0)() {
return Err(error::Error::Unactionable.into());
}
let imported = self.pool.write().import(tx)?;
if let base::Imported::Ready { ref hash, .. } = imported {
self.import_notification_sinks.lock()
.retain_mut(|sink| {
match sink.try_send(*hash) {
Ok(()) => true,
Err(e) => {
if e.is_full() {
log::warn!(target: "txpool", "[{:?}] Trying to notify an import but the channel is full", hash);
true
} else {
false
}
},
}
});
}
let mut listener = self.listener.write();
fire_events(&mut *listener, &imported);
Ok(*imported.hash())
},
ValidatedTransaction::Invalid(hash, err) => {
self.rotator.ban(&Instant::now(), std::iter::once(hash));
Err(err)
},
ValidatedTransaction::Unknown(hash, err) => {
self.listener.write().invalid(&hash, false);
Err(err)
},
}
}
fn enforce_limits(&self) -> HashSet> {
let status = self.pool.read().status();
let ready_limit = &self.options.ready;
let future_limit = &self.options.future;
log::debug!(target: "txpool", "Pool Status: {:?}", status);
if ready_limit.is_exceeded(status.ready, status.ready_bytes)
|| future_limit.is_exceeded(status.future, status.future_bytes)
{
log::debug!(
target: "txpool",
"Enforcing limits ({}/{}kB ready, {}/{}kB future",
ready_limit.count, ready_limit.total_bytes / 1024,
future_limit.count, future_limit.total_bytes / 1024,
);
// clean up the pool
let removed = {
let mut pool = self.pool.write();
let removed = pool.enforce_limits(ready_limit, future_limit)
.into_iter().map(|x| x.hash).collect::>();
// ban all removed transactions
self.rotator.ban(&Instant::now(), removed.iter().copied());
removed
};
if !removed.is_empty() {
log::debug!(target: "txpool", "Enforcing limits: {} dropped", removed.len());
}
// run notifications
let mut listener = self.listener.write();
for h in &removed {
listener.dropped(h, None);
}
removed
} else {
Default::default()
}
}
/// Import a single extrinsic and starts to watch their progress in the pool.
pub fn submit_and_watch(
&self,
tx: ValidatedTransactionFor,
) -> Result, ExtrinsicHash>, B::Error> {
match tx {
ValidatedTransaction::Valid(tx) => {
let hash = self.api.hash_and_length(&tx.data).0;
let watcher = self.listener.write().create_watcher(hash);
self.submit(std::iter::once(ValidatedTransaction::Valid(tx)))
.pop()
.expect("One extrinsic passed; one result returned; qed")
.map(|_| watcher)
},
ValidatedTransaction::Invalid(hash, err) => {
self.rotator.ban(&Instant::now(), std::iter::once(hash));
Err(err)
},
ValidatedTransaction::Unknown(_, err) => Err(err),
}
}
/// Resubmits revalidated transactions back to the pool.
///
/// Removes and then submits passed transactions and all dependent transactions.
/// Transactions that are missing from the pool are not submitted.
pub fn resubmit(&self, mut updated_transactions: HashMap, ValidatedTransactionFor>) {
#[derive(Debug, Clone, Copy, PartialEq)]
enum Status { Future, Ready, Failed, Dropped }
let (mut initial_statuses, final_statuses) = {
let mut pool = self.pool.write();
// remove all passed transactions from the ready/future queues
// (this may remove additional transactions as well)
//
// for every transaction that has an entry in the `updated_transactions`,
// we store updated validation result in txs_to_resubmit
// for every transaction that has no entry in the `updated_transactions`,
// we store last validation result (i.e. the pool entry) in txs_to_resubmit
let mut initial_statuses = HashMap::new();
let mut txs_to_resubmit = Vec::with_capacity(updated_transactions.len());
while !updated_transactions.is_empty() {
let hash = updated_transactions.keys().next().cloned().expect("transactions is not empty; qed");
// note we are not considering tx with hash invalid here - we just want
// to remove it along with dependent transactions and `remove_subtree()`
// does exactly what we need
let removed = pool.remove_subtree(&[hash]);
for removed_tx in removed {
let removed_hash = removed_tx.hash;
let updated_transaction = updated_transactions.remove(&removed_hash);
let tx_to_resubmit = if let Some(updated_tx) = updated_transaction {
updated_tx
} else {
// in most cases we'll end up in successful `try_unwrap`, but if not
// we still need to reinsert transaction back to the pool => duplicate call
let transaction = match Arc::try_unwrap(removed_tx) {
Ok(transaction) => transaction,
Err(transaction) => transaction.duplicate(),
};
ValidatedTransaction::Valid(transaction)
};
initial_statuses.insert(removed_hash, Status::Ready);
txs_to_resubmit.push((removed_hash, tx_to_resubmit));
}
// make sure to remove the hash even if it's not present in the pool any more.
updated_transactions.remove(&hash);
}
// if we're rejecting future transactions, then insertion order matters here:
// if tx1 depends on tx2, then if tx1 is inserted before tx2, then it goes
// to the future queue and gets rejected immediately
// => let's temporary stop rejection and clear future queue before return
pool.with_futures_enabled(|pool, reject_future_transactions| {
// now resubmit all removed transactions back to the pool
let mut final_statuses = HashMap::new();
for (hash, tx_to_resubmit) in txs_to_resubmit {
match tx_to_resubmit {
ValidatedTransaction::Valid(tx) => match pool.import(tx) {
Ok(imported) => match imported {
base::Imported::Ready { promoted, failed, removed, .. } => {
final_statuses.insert(hash, Status::Ready);
for hash in promoted {
final_statuses.insert(hash, Status::Ready);
}
for hash in failed {
final_statuses.insert(hash, Status::Failed);
}
for tx in removed {
final_statuses.insert(tx.hash, Status::Dropped);
}
},
base::Imported::Future { .. } => {
final_statuses.insert(hash, Status::Future);
},
},
Err(err) => {
// we do not want to fail if single transaction import has failed
// nor we do want to propagate this error, because it could tx unknown to caller
// => let's just notify listeners (and issue debug message)
log::warn!(
target: "txpool",
"[{:?}] Removing invalid transaction from update: {}",
hash,
err,
);
final_statuses.insert(hash, Status::Failed);
},
},
ValidatedTransaction::Invalid(_, _) | ValidatedTransaction::Unknown(_, _) => {
final_statuses.insert(hash, Status::Failed);
},
}
}
// if the pool is configured to reject future transactions, let's clear the future
// queue, updating final statuses as required
if reject_future_transactions {
for future_tx in pool.clear_future() {
final_statuses.insert(future_tx.hash, Status::Dropped);
}
}
(initial_statuses, final_statuses)
})
};
// and now let's notify listeners about status changes
let mut listener = self.listener.write();
for (hash, final_status) in final_statuses {
let initial_status = initial_statuses.remove(&hash);
if initial_status.is_none() || Some(final_status) != initial_status {
match final_status {
Status::Future => listener.future(&hash),
Status::Ready => listener.ready(&hash, None),
Status::Dropped => listener.dropped(&hash, None),
Status::Failed => listener.invalid(&hash, initial_status.is_some()),
}
}
}
}
/// For each extrinsic, returns tags that it provides (if known), or None (if it is unknown).
pub fn extrinsics_tags(&self, hashes: &[ExtrinsicHash]) -> Vec