fuel-core-txpool 0.17.5

Transaction pool
Documentation
use super::*;
use crate::{
    ports::BlockImporter,
    MockDb,
};
use fuel_core_services::{
    stream::BoxStream,
    Service as ServiceTrait,
};
use fuel_core_types::{
    blockchain::SealedBlock,
    entities::coin::Coin,
    fuel_crypto::rand::{
        rngs::StdRng,
        SeedableRng,
    },
    fuel_tx::{
        Input,
        Transaction,
        TransactionBuilder,
        Word,
    },
    services::p2p::GossipsubMessageAcceptance,
};
use std::cell::RefCell;

type GossipedTransaction = GossipData<Transaction>;

pub struct TestContext {
    pub(crate) service: Service<MockP2P, MockDb>,
    mock_db: MockDb,
    rng: RefCell<StdRng>,
}

impl TestContext {
    pub async fn new() -> Self {
        TestContextBuilder::new().build_and_start().await
    }

    pub fn service(&self) -> &Service<MockP2P, MockDb> {
        &self.service
    }

    pub fn setup_script_tx(&self, gas_price: Word) -> Transaction {
        let (_, gas_coin) = self.setup_coin();
        TransactionBuilder::script(vec![], vec![])
            .gas_price(gas_price)
            .gas_limit(1000)
            .add_input(gas_coin)
            .finalize_as_transaction()
    }

    pub fn setup_coin(&self) -> (Coin, Input) {
        crate::test_helpers::setup_coin(&mut self.rng.borrow_mut(), Some(&self.mock_db))
    }
}

mockall::mock! {
    pub P2P {}

    impl PeerToPeer for P2P {
        type GossipedTransaction = GossipedTransaction;

        fn broadcast_transaction(&self, transaction: Arc<Transaction>) -> anyhow::Result<()>;

        fn gossiped_transaction_events(&self) -> BoxStream<GossipedTransaction>;

        fn notify_gossip_transaction_validity(
            &self,
            message_info: GossipsubMessageInfo,
            validity: GossipsubMessageAcceptance,
        ) -> anyhow::Result<()>;
    }
}

impl MockP2P {
    pub fn new_with_txs(txs: Vec<Transaction>) -> Self {
        let mut p2p = MockP2P::default();
        p2p.expect_gossiped_transaction_events().returning(move || {
            let txs_clone = txs.clone();
            let stream = fuel_core_services::stream::unfold(txs_clone, |mut txs| async {
                let tx = txs.pop();
                if let Some(tx) = tx {
                    Some((GossipData::new(tx, vec![], vec![]), txs))
                } else {
                    core::future::pending().await
                }
            });
            Box::pin(stream)
        });
        p2p.expect_broadcast_transaction()
            .returning(move |_| Ok(()));
        p2p
    }
}

mockall::mock! {
    pub Importer {}

    impl BlockImporter for Importer {
        fn block_events(&self) -> BoxStream<Arc<ImportResult>>;
    }
}

impl MockImporter {
    fn with_blocks(blocks: Vec<SealedBlock>) -> Self {
        let mut importer = MockImporter::default();
        importer.expect_block_events().returning(move || {
            let blocks = blocks.clone();
            let stream = fuel_core_services::stream::unfold(blocks, |mut blocks| async {
                let block = blocks.pop();
                if let Some(sealed_block) = block {
                    let result = ImportResult {
                        sealed_block,
                        tx_status: vec![],
                    };
                    let result = Arc::new(result);
                    Some((result, blocks))
                } else {
                    core::future::pending().await
                }
            });
            Box::pin(stream)
        });
        importer
    }
}

pub struct TestContextBuilder {
    config: Option<Config>,
    mock_db: MockDb,
    rng: StdRng,
    p2p: Option<MockP2P>,
    importer: Option<MockImporter>,
}

impl Default for TestContextBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl TestContextBuilder {
    pub fn new() -> Self {
        Self {
            config: None,
            mock_db: MockDb::default(),
            rng: StdRng::seed_from_u64(10),
            p2p: None,
            importer: None,
        }
    }

    pub fn with_config(mut self, config: Config) -> Self {
        self.config = Some(config);
        self
    }

    pub fn with_importer(&mut self, importer: MockImporter) {
        self.importer = Some(importer)
    }

    pub fn with_p2p(&mut self, p2p: MockP2P) {
        self.p2p = Some(p2p)
    }

    pub fn setup_script_tx(&mut self, gas_price: Word) -> Transaction {
        let (_, gas_coin) = self.setup_coin();
        TransactionBuilder::script(vec![], vec![])
            .gas_price(gas_price)
            .gas_limit(1000)
            .add_input(gas_coin)
            .finalize_as_transaction()
    }

    pub fn setup_coin(&mut self) -> (Coin, Input) {
        crate::test_helpers::setup_coin(&mut self.rng, Some(&self.mock_db))
    }

    pub fn build(self) -> TestContext {
        let rng = RefCell::new(self.rng);
        let config = self.config.unwrap_or_default();
        let mock_db = self.mock_db;

        let p2p = self.p2p.unwrap_or_else(|| MockP2P::new_with_txs(vec![]));
        let importer = self
            .importer
            .unwrap_or_else(|| MockImporter::with_blocks(vec![]));

        let service = new_service(config, mock_db.clone(), importer, p2p);

        TestContext {
            service,
            mock_db,
            rng,
        }
    }

    pub async fn build_and_start(self) -> TestContext {
        let context = self.build();
        context.service.start_and_await().await.unwrap();
        context
    }
}