#![warn(missing_docs)]
pub mod error;
use async_trait::async_trait;
use futures::{Future, Stream};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Member, NumberFor},
};
use std::{collections::HashMap, hash::Hash, pin::Pin, sync::Arc};
const LOG_TARGET: &str = "txpool::api";
pub use sp_runtime::transaction_validity::{
TransactionLongevity, TransactionPriority, TransactionSource, TransactionTag,
};
#[derive(Debug)]
pub struct PoolStatus {
pub ready: usize,
pub ready_bytes: usize,
pub future: usize,
pub future_bytes: usize,
}
impl PoolStatus {
pub fn is_empty(&self) -> bool {
self.ready == 0 && self.future == 0
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TransactionStatus<Hash, BlockHash> {
Future,
Ready,
Broadcast(Vec<String>),
#[serde(with = "v1_compatible")]
InBlock((BlockHash, TxIndex)),
Retracted(BlockHash),
FinalityTimeout(BlockHash),
#[serde(with = "v1_compatible")]
Finalized((BlockHash, TxIndex)),
Usurped(Hash),
Dropped,
Invalid,
}
pub type TransactionStatusStream<Hash, BlockHash> =
dyn Stream<Item = TransactionStatus<Hash, BlockHash>> + Send;
pub type ImportNotificationStream<H> = futures::channel::mpsc::Receiver<H>;
pub type TxHash<P> = <P as TransactionPool>::Hash;
pub type BlockHash<P> = <<P as TransactionPool>::Block as BlockT>::Hash;
pub type TransactionFor<P> = <<P as TransactionPool>::Block as BlockT>::Extrinsic;
pub type TransactionStatusStreamFor<P> = TransactionStatusStream<TxHash<P>, BlockHash<P>>;
pub type LocalTransactionFor<P> = <<P as LocalTransactionPool>::Block as BlockT>::Extrinsic;
pub type TxIndex = usize;
pub type PoolFuture<T, E> = std::pin::Pin<Box<dyn Future<Output = Result<T, E>> + Send>>;
pub trait InPoolTransaction {
type Transaction;
type Hash;
fn data(&self) -> &Self::Transaction;
fn hash(&self) -> &Self::Hash;
fn priority(&self) -> &TransactionPriority;
fn longevity(&self) -> &TransactionLongevity;
fn requires(&self) -> &[TransactionTag];
fn provides(&self) -> &[TransactionTag];
fn is_propagable(&self) -> bool;
}
pub trait TransactionPool: Send + Sync {
type Block: BlockT;
type Hash: Hash + Eq + Member + Serialize + DeserializeOwned;
type InPoolTransaction: InPoolTransaction<
Transaction = TransactionFor<Self>,
Hash = TxHash<Self>,
>;
type Error: From<crate::error::Error> + crate::error::IntoPoolError;
fn submit_at(
&self,
at: &BlockId<Self::Block>,
source: TransactionSource,
xts: Vec<TransactionFor<Self>>,
) -> PoolFuture<Vec<Result<TxHash<Self>, Self::Error>>, Self::Error>;
fn submit_one(
&self,
at: &BlockId<Self::Block>,
source: TransactionSource,
xt: TransactionFor<Self>,
) -> PoolFuture<TxHash<Self>, Self::Error>;
fn submit_and_watch(
&self,
at: &BlockId<Self::Block>,
source: TransactionSource,
xt: TransactionFor<Self>,
) -> PoolFuture<Pin<Box<TransactionStatusStreamFor<Self>>>, Self::Error>;
fn ready_at(
&self,
at: NumberFor<Self::Block>,
) -> Pin<
Box<
dyn Future<
Output = Box<dyn ReadyTransactions<Item = Arc<Self::InPoolTransaction>> + Send>,
> + Send,
>,
>;
fn ready(&self) -> Box<dyn ReadyTransactions<Item = Arc<Self::InPoolTransaction>> + Send>;
fn remove_invalid(&self, hashes: &[TxHash<Self>]) -> Vec<Arc<Self::InPoolTransaction>>;
fn status(&self) -> PoolStatus;
fn import_notification_stream(&self) -> ImportNotificationStream<TxHash<Self>>;
fn on_broadcasted(&self, propagations: HashMap<TxHash<Self>, Vec<String>>);
fn hash_of(&self, xt: &TransactionFor<Self>) -> TxHash<Self>;
fn ready_transaction(&self, hash: &TxHash<Self>) -> Option<Arc<Self::InPoolTransaction>>;
}
pub trait ReadyTransactions: Iterator {
fn report_invalid(&mut self, _tx: &Self::Item);
}
impl<T> ReadyTransactions for std::iter::Empty<T> {
fn report_invalid(&mut self, _tx: &T) {}
}
pub enum ChainEvent<B: BlockT> {
NewBestBlock {
hash: B::Hash,
tree_route: Option<Arc<sp_blockchain::TreeRoute<B>>>,
},
Finalized {
hash: B::Hash,
tree_route: Arc<[B::Hash]>,
},
}
#[async_trait]
pub trait MaintainedTransactionPool: TransactionPool {
async fn maintain(&self, event: ChainEvent<Self::Block>);
}
pub trait LocalTransactionPool: Send + Sync {
type Block: BlockT;
type Hash: Hash + Eq + Member + Serialize;
type Error: From<crate::error::Error> + crate::error::IntoPoolError;
fn submit_local(
&self,
at: &BlockId<Self::Block>,
xt: LocalTransactionFor<Self>,
) -> Result<Self::Hash, Self::Error>;
}
pub trait OffchainSubmitTransaction<Block: BlockT>: Send + Sync {
fn submit_at(&self, at: &BlockId<Block>, extrinsic: Block::Extrinsic) -> Result<(), ()>;
}
impl<TPool: LocalTransactionPool> OffchainSubmitTransaction<TPool::Block> for TPool {
fn submit_at(
&self,
at: &BlockId<TPool::Block>,
extrinsic: <TPool::Block as BlockT>::Extrinsic,
) -> Result<(), ()> {
log::debug!(
target: LOG_TARGET,
"(offchain call) Submitting a transaction to the pool: {:?}",
extrinsic
);
let result = self.submit_local(at, extrinsic);
result.map(|_| ()).map_err(|e| {
log::warn!(
target: LOG_TARGET,
"(offchain call) Error submitting a transaction to the pool: {}",
e
)
})
}
}
mod v1_compatible {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S, H>(data: &(H, usize), serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
H: Serialize,
{
let (hash, _) = data;
serde::Serialize::serialize(&hash, serializer)
}
pub fn deserialize<'de, D, H>(deserializer: D) -> Result<(H, usize), D::Error>
where
D: Deserializer<'de>,
H: Deserialize<'de>,
{
let hash: H = serde::Deserialize::deserialize(deserializer)?;
Ok((hash, 0))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tx_status_compatibility() {
let event: TransactionStatus<u8, u8> = TransactionStatus::InBlock((1, 2));
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"inBlock":1}"#;
assert_eq!(ser, exp);
let event_dec: TransactionStatus<u8, u8> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, TransactionStatus::InBlock((1, 0)));
let event: TransactionStatus<u8, u8> = TransactionStatus::Finalized((1, 2));
let ser = serde_json::to_string(&event).unwrap();
let exp = r#"{"finalized":1}"#;
assert_eq!(ser, exp);
let event_dec: TransactionStatus<u8, u8> = serde_json::from_str(exp).unwrap();
assert_eq!(event_dec, TransactionStatus::Finalized((1, 0)));
}
}