mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 01:11:10 +00:00
Improve transaction submission (#6599)
* Improve transaction submission Before this pr the transaction pool validated each transaction, even if the transaction was already known to the pool. This pr changes the behavior to first check if we are already aware of a transaction and thus, to only validate them if we don't know them yet. However, there is still the possibility that a given transaction is validated multiple times. This can happen if the transaction is added the first time, but is not yet validated and added to the validated pool. Besides that, this pr fixes the wrong metrics of gossiped transactions in the network. It also moves some metrics to the transaction pool api, to better track when a transaction actually is scheduled for validation. * Make sure we don't submit the same transaction twice from the network concurrently * Remove added listener call * Feedback * Ignore banned on resubmit
This commit is contained in:
@@ -261,6 +261,11 @@ impl<Hash: hash::Hash + Member + Serialize, Ex: std::fmt::Debug> BasePool<Hash,
|
||||
return_value
|
||||
}
|
||||
|
||||
/// Returns if the transaction for the given hash is already imported.
|
||||
pub fn is_imported(&self, tx_hash: &Hash) -> bool {
|
||||
self.future.contains(tx_hash) || self.ready.contains(tx_hash)
|
||||
}
|
||||
|
||||
/// Imports transaction to the pool.
|
||||
///
|
||||
/// The pool consists of two parts: Future and Ready.
|
||||
@@ -272,7 +277,7 @@ impl<Hash: hash::Hash + Member + Serialize, Ex: std::fmt::Debug> BasePool<Hash,
|
||||
&mut self,
|
||||
tx: Transaction<Hash, Ex>,
|
||||
) -> error::Result<Imported<Hash, Ex>> {
|
||||
if self.future.contains(&tx.hash) || self.ready.contains(&tx.hash) {
|
||||
if self.is_imported(&tx.hash) {
|
||||
return Err(error::Error::AlreadyImported(Box::new(tx.hash.clone())))
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::{
|
||||
|
||||
use crate::{base_pool as base, watcher::Watcher};
|
||||
|
||||
use futures::{Future, FutureExt};
|
||||
use futures::Future;
|
||||
use sp_runtime::{
|
||||
generic::BlockId,
|
||||
traits::{self, SaturatedConversion, Block as BlockT},
|
||||
@@ -125,6 +125,14 @@ impl Default for Options {
|
||||
}
|
||||
}
|
||||
|
||||
/// Should we check that the transaction is banned
|
||||
/// in the pool, before we verify it?
|
||||
#[derive(Copy, Clone)]
|
||||
enum CheckBannedBeforeVerify {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// Extrinsics pool that performs validation.
|
||||
pub struct Pool<B: ChainApi> {
|
||||
validated_pool: Arc<ValidatedPool<B>>,
|
||||
@@ -149,23 +157,29 @@ impl<B: ChainApi> Pool<B> {
|
||||
}
|
||||
|
||||
/// Imports a bunch of unverified extrinsics to the pool
|
||||
pub async fn submit_at<T>(
|
||||
pub async fn submit_at(
|
||||
&self,
|
||||
at: &BlockId<B::Block>,
|
||||
source: TransactionSource,
|
||||
xts: T,
|
||||
force: bool,
|
||||
) -> Result<Vec<Result<ExtrinsicHash<B>, B::Error>>, B::Error> where
|
||||
T: IntoIterator<Item=ExtrinsicFor<B>>,
|
||||
{
|
||||
let validated_pool = self.validated_pool.clone();
|
||||
xts: impl IntoIterator<Item=ExtrinsicFor<B>>,
|
||||
) -> Result<Vec<Result<ExtrinsicHash<B>, B::Error>>, B::Error> {
|
||||
let xts = xts.into_iter().map(|xt| (source, xt));
|
||||
self.verify(at, xts, force)
|
||||
.map(move |validated_transactions| validated_transactions
|
||||
.map(|validated_transactions| validated_pool.submit(validated_transactions
|
||||
.into_iter()
|
||||
.map(|(_, tx)| tx))))
|
||||
.await
|
||||
let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::Yes).await?;
|
||||
Ok(self.validated_pool.submit(validated_transactions.into_iter().map(|(_, tx)| tx)))
|
||||
}
|
||||
|
||||
/// Resubmit the given extrinsics to the pool.
|
||||
///
|
||||
/// This does not check if a transaction is banned, before we verify it again.
|
||||
pub async fn resubmit_at(
|
||||
&self,
|
||||
at: &BlockId<B::Block>,
|
||||
source: TransactionSource,
|
||||
xts: impl IntoIterator<Item=ExtrinsicFor<B>>,
|
||||
) -> Result<Vec<Result<ExtrinsicHash<B>, B::Error>>, B::Error> {
|
||||
let xts = xts.into_iter().map(|xt| (source, xt));
|
||||
let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::No).await?;
|
||||
Ok(self.validated_pool.submit(validated_transactions.into_iter().map(|(_, tx)| tx)))
|
||||
}
|
||||
|
||||
/// Imports one unverified extrinsic to the pool
|
||||
@@ -175,12 +189,8 @@ impl<B: ChainApi> Pool<B> {
|
||||
source: TransactionSource,
|
||||
xt: ExtrinsicFor<B>,
|
||||
) -> Result<ExtrinsicHash<B>, B::Error> {
|
||||
self.submit_at(at, source, std::iter::once(xt), false)
|
||||
.map(|import_result| import_result.and_then(|mut import_result| import_result
|
||||
.pop()
|
||||
.expect("One extrinsic passed; one result returned; qed")
|
||||
))
|
||||
.await
|
||||
let res = self.submit_at(at, source, std::iter::once(xt)).await?.pop();
|
||||
res.expect("One extrinsic passed; one result returned; qed")
|
||||
}
|
||||
|
||||
/// Import a single extrinsic and starts to watch their progress in the pool.
|
||||
@@ -192,7 +202,11 @@ impl<B: ChainApi> Pool<B> {
|
||||
) -> Result<Watcher<ExtrinsicHash<B>, ExtrinsicHash<B>>, B::Error> {
|
||||
let block_number = self.resolve_block_number(at)?;
|
||||
let (_, tx) = self.verify_one(
|
||||
at, block_number, source, xt, false
|
||||
at,
|
||||
block_number,
|
||||
source,
|
||||
xt,
|
||||
CheckBannedBeforeVerify::Yes,
|
||||
).await;
|
||||
self.validated_pool.submit_and_watch(tx)
|
||||
}
|
||||
@@ -328,7 +342,11 @@ impl<B: ChainApi> Pool<B> {
|
||||
.into_iter()
|
||||
.map(|tx| (tx.source, tx.data.clone()));
|
||||
|
||||
let reverified_transactions = self.verify(at, pruned_transactions, false).await?;
|
||||
let reverified_transactions = self.verify(
|
||||
at,
|
||||
pruned_transactions,
|
||||
CheckBannedBeforeVerify::Yes,
|
||||
).await?;
|
||||
|
||||
log::trace!(target: "txpool", "Pruning at {:?}. Resubmitting transactions.", at);
|
||||
// And finally - submit reverified transactions back to the pool
|
||||
@@ -358,23 +376,17 @@ impl<B: ChainApi> Pool<B> {
|
||||
&self,
|
||||
at: &BlockId<B::Block>,
|
||||
xts: impl IntoIterator<Item=(TransactionSource, ExtrinsicFor<B>)>,
|
||||
force: bool,
|
||||
check: CheckBannedBeforeVerify,
|
||||
) -> Result<HashMap<ExtrinsicHash<B>, ValidatedTransactionFor<B>>, B::Error> {
|
||||
// we need a block number to compute tx validity
|
||||
let block_number = self.resolve_block_number(at)?;
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for (hash, validated_tx) in
|
||||
futures::future::join_all(
|
||||
xts.into_iter()
|
||||
.map(|(source, xt)| self.verify_one(at, block_number, source, xt, force))
|
||||
)
|
||||
.await
|
||||
{
|
||||
result.insert(hash, validated_tx);
|
||||
}
|
||||
let res = futures::future::join_all(
|
||||
xts.into_iter()
|
||||
.map(|(source, xt)| self.verify_one(at, block_number, source, xt, check))
|
||||
).await.into_iter().collect::<HashMap<_, _>>();
|
||||
|
||||
Ok(result)
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Returns future that validates single transaction at given block.
|
||||
@@ -384,14 +396,13 @@ impl<B: ChainApi> Pool<B> {
|
||||
block_number: NumberFor<B>,
|
||||
source: TransactionSource,
|
||||
xt: ExtrinsicFor<B>,
|
||||
force: bool,
|
||||
check: CheckBannedBeforeVerify,
|
||||
) -> (ExtrinsicHash<B>, ValidatedTransactionFor<B>) {
|
||||
let (hash, bytes) = self.validated_pool.api().hash_and_length(&xt);
|
||||
if !force && self.validated_pool.is_banned(&hash) {
|
||||
return (
|
||||
hash.clone(),
|
||||
ValidatedTransaction::Invalid(hash, error::Error::TemporarilyBanned.into()),
|
||||
)
|
||||
|
||||
let ignore_banned = matches!(check, CheckBannedBeforeVerify::No);
|
||||
if let Err(err) = self.validated_pool.check_is_known(&hash, ignore_banned) {
|
||||
return (hash.clone(), ValidatedTransaction::Invalid(hash, err.into()))
|
||||
}
|
||||
|
||||
let validation_result = self.validated_pool.api().validate_transaction(
|
||||
|
||||
@@ -137,10 +137,30 @@ impl<B: ChainApi> ValidatedPool<B> {
|
||||
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<B>,
|
||||
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.clone())).into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Imports a bunch of pre-validated transactions to the pool.
|
||||
pub fn submit<T>(&self, txs: T) -> Vec<Result<ExtrinsicHash<B>, B::Error>> where
|
||||
T: IntoIterator<Item=ValidatedTransactionFor<B>>
|
||||
{
|
||||
pub fn submit(
|
||||
&self,
|
||||
txs: impl IntoIterator<Item=ValidatedTransactionFor<B>>,
|
||||
) -> Vec<Result<ExtrinsicHash<B>, B::Error>> {
|
||||
let results = txs.into_iter()
|
||||
.map(|validated_tx| self.submit_one(validated_tx))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Reference in New Issue
Block a user