soil-txpool 0.2.0

Soil transaction pool implementation
Documentation
// This file is part of Soil.

// Copyright (C) Soil contributors.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

//! Testing related primitives for internal usage in this crate.

use crate::{
	graph::{BlockHash, ChainApi, ExtrinsicFor, NumberFor, RawExtrinsicFor},
	ValidateTransactionPriority,
};
use async_trait::async_trait;
use codec::Encode;
use parking_lot::Mutex;
use soil_client::blockchain::{HashAndNumber, TreeRoute};
use soil_client::transaction_pool::error;
use std::{collections::HashSet, sync::Arc};
use subsoil::runtime::{
	generic::BlockId,
	traits::{Block as BlockT, Hash},
	transaction_validity::{
		InvalidTransaction, TransactionSource, TransactionValidity, ValidTransaction,
	},
};
use soil_test_node_runtime::{
	substrate_test_pallet::pallet::Call as PalletCall, BalancesCall, Block, BlockNumber, Extrinsic,
	ExtrinsicBuilder, Hashing, RuntimeCall, Transfer, TransferData, H256,
};

type Pool<Api> = crate::graph::Pool<Api, ()>;

pub(crate) const INVALID_NONCE: u64 = 254;

/// Test api that implements [`ChainApi`].
#[derive(Clone, Debug, Default)]
pub(crate) struct TestApi {
	pub delay: Arc<Mutex<Option<std::sync::mpsc::Receiver<()>>>>,
	pub invalidate: Arc<Mutex<HashSet<H256>>>,
	pub clear_requirements: Arc<Mutex<HashSet<H256>>>,
	pub add_requirements: Arc<Mutex<HashSet<H256>>>,
	pub validation_requests: Arc<Mutex<Vec<Extrinsic>>>,
}

impl TestApi {
	/// Query validation requests received.
	pub fn validation_requests(&self) -> Vec<Extrinsic> {
		self.validation_requests.lock().clone()
	}

	/// Helper function for mapping block number to hash. Use if mapping shall not fail.
	pub fn expect_hash_from_number(&self, n: BlockNumber) -> H256 {
		self.block_id_to_hash(&BlockId::Number(n)).unwrap().unwrap()
	}

	pub fn expect_hash_and_number(&self, n: BlockNumber) -> HashAndNumber<Block> {
		HashAndNumber { hash: self.expect_hash_from_number(n), number: n }
	}
}

#[async_trait]
impl ChainApi for TestApi {
	type Block = Block;
	type Error = error::Error;

	/// Verify extrinsic at given block.
	async fn validate_transaction(
		&self,
		at: <Self::Block as BlockT>::Hash,
		_source: TransactionSource,
		uxt: ExtrinsicFor<Self>,
		_: ValidateTransactionPriority,
	) -> Result<TransactionValidity, Self::Error> {
		let uxt = (*uxt).clone();
		self.validation_requests.lock().push(uxt.clone());
		let hash = self.hash_and_length(&uxt).0;
		let block_number = self.block_id_to_number(&BlockId::Hash(at)).unwrap().unwrap();

		let res = match uxt {
			Extrinsic {
				function: RuntimeCall::Balances(BalancesCall::transfer_allow_death { .. }),
				..
			} => {
				let TransferData { nonce, .. } = (&uxt).try_into().unwrap();
				// This is used to control the test flow.
				if nonce > 0 {
					let opt = self.delay.lock().take();
					if let Some(delay) = opt {
						if delay.recv().is_err() {
							println!("Error waiting for delay!");
						}
					}
				}

				if self.invalidate.lock().contains(&hash) {
					InvalidTransaction::Custom(0).into()
				} else if nonce < block_number {
					InvalidTransaction::Stale.into()
				} else {
					let mut transaction = ValidTransaction {
						priority: 4,
						requires: if nonce > block_number {
							vec![vec![nonce as u8 - 1]]
						} else {
							vec![]
						},
						provides: if nonce == INVALID_NONCE {
							vec![]
						} else {
							vec![vec![nonce as u8]]
						},
						longevity: 3,
						propagate: true,
					};

					if self.clear_requirements.lock().contains(&hash) {
						transaction.requires.clear();
					}

					if self.add_requirements.lock().contains(&hash) {
						transaction.requires.push(vec![128]);
					}

					Ok(transaction)
				}
			},
			Extrinsic {
				function: RuntimeCall::SubstrateTest(PalletCall::include_data { .. }),
				..
			} => Ok(ValidTransaction {
				priority: 9001,
				requires: vec![],
				provides: vec![vec![42]],
				longevity: 9001,
				propagate: false,
			}),
			Extrinsic {
				function: RuntimeCall::SubstrateTest(PalletCall::indexed_call { .. }),
				..
			} => Ok(ValidTransaction {
				priority: 9001,
				requires: vec![],
				provides: vec![vec![43]],
				longevity: 9001,
				propagate: false,
			}),
			_ => unimplemented!(),
		};

		Ok(res)
	}

	fn validate_transaction_blocking(
		&self,
		_at: <Self::Block as BlockT>::Hash,
		_source: TransactionSource,
		_uxt: Arc<<Self::Block as BlockT>::Extrinsic>,
	) -> Result<TransactionValidity, Self::Error> {
		unimplemented!();
	}

	/// Returns a block number given the block id.
	fn block_id_to_number(
		&self,
		at: &BlockId<Self::Block>,
	) -> Result<Option<NumberFor<Self>>, Self::Error> {
		Ok(match at {
			BlockId::Number(num) => Some(*num),
			BlockId::Hash(hash) if *hash == H256::from_low_u64_be(hash.to_low_u64_be()) => {
				Some(hash.to_low_u64_be())
			},
			BlockId::Hash(_) => None,
		})
	}

	/// Returns a block hash given the block id.
	fn block_id_to_hash(
		&self,
		at: &BlockId<Self::Block>,
	) -> Result<Option<<Self::Block as BlockT>::Hash>, Self::Error> {
		Ok(match at {
			BlockId::Number(num) => Some(H256::from_low_u64_be(*num)).into(),
			BlockId::Hash(hash) => Some(*hash),
		})
	}

	/// Hash the extrinsic.
	fn hash_and_length(&self, uxt: &RawExtrinsicFor<Self>) -> (BlockHash<Self>, usize) {
		let encoded = uxt.encode();
		let len = encoded.len();
		(Hashing::hash(&encoded), len)
	}

	async fn block_body(
		&self,
		_id: <Self::Block as BlockT>::Hash,
	) -> Result<Option<Vec<<Self::Block as BlockT>::Extrinsic>>, Self::Error> {
		Ok(None)
	}

	fn block_header(
		&self,
		_: <Self::Block as BlockT>::Hash,
	) -> Result<Option<<Self::Block as BlockT>::Header>, Self::Error> {
		Ok(None)
	}

	fn tree_route(
		&self,
		_from: <Self::Block as BlockT>::Hash,
		_to: <Self::Block as BlockT>::Hash,
	) -> Result<TreeRoute<Self::Block>, Self::Error> {
		unimplemented!()
	}
}

pub(crate) fn uxt(transfer: Transfer) -> Extrinsic {
	ExtrinsicBuilder::new_transfer(transfer).build()
}

pub(crate) fn pool() -> (Pool<TestApi>, Arc<TestApi>) {
	let api = Arc::new(TestApi::default());
	(Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()), api)
}