use super::*;
use crate::{
mock_db::MockDBProvider,
ports::{
BlockImporter,
MockConsensusParametersProvider,
},
MockDb,
};
use fuel_core_services::{
stream::BoxStream,
Service as ServiceTrait,
};
use fuel_core_txpool::types::GasPrice;
use fuel_core_types::{
blockchain::SealedBlock,
entities::coins::coin::Coin,
fuel_crypto::rand::{
rngs::StdRng,
SeedableRng,
},
fuel_tx::{
Cacheable,
Input,
Transaction,
TransactionBuilder,
Word,
},
services::{
block_importer::ImportResult,
p2p::GossipsubMessageAcceptance,
},
};
use std::cell::RefCell;
type GossipedTransaction = GossipData<Transaction>;
pub struct TestContext {
pub(crate) service: Service<
MockP2P,
MockDBProvider,
MockTxPoolGasPrice,
MockConsensusParametersProvider,
>,
mock_db: MockDb,
rng: RefCell<StdRng>,
}
#[derive(Debug, Clone)]
pub struct MockTxPoolGasPrice {
pub gas_price: Option<GasPrice>,
}
impl MockTxPoolGasPrice {
pub fn new(gas_price: GasPrice) -> Self {
Self {
gas_price: Some(gas_price),
}
}
pub fn new_none() -> Self {
Self { gas_price: None }
}
}
impl GasPriceProviderConstraint for MockTxPoolGasPrice {
fn gas_price(&self, _block_height: BlockHeight) -> Option<GasPrice> {
self.gas_price
}
}
impl TestContext {
pub async fn new() -> Self {
TestContextBuilder::new().build_and_start().await
}
pub fn service(
&self,
) -> &Service<
MockP2P,
MockDBProvider,
MockTxPoolGasPrice,
MockConsensusParametersProvider,
> {
&self.service
}
pub fn setup_script_tx(&self, tip: Word) -> Transaction {
let (_, gas_coin) = self.setup_coin();
let mut tx = TransactionBuilder::script(vec![], vec![])
.max_fee_limit(tip)
.tip(tip)
.script_gas_limit(1000)
.add_input(gas_coin)
.finalize_as_transaction();
tx.precompute(&Default::default())
.expect("Should be able to cache");
tx
}
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
}
}
mockall::mock! {
pub Importer {}
impl BlockImporter for Importer {
fn block_events(&self) -> BoxStream<SharedImportResult>;
}
}
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: SharedImportResult = Arc::new(
ImportResult::new_from_local(sealed_block, vec![], vec![]),
);
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, tip: Word) -> Transaction {
let (_, gas_coin) = self.setup_coin();
TransactionBuilder::script(vec![], vec![])
.tip(tip)
.max_fee_limit(tip)
.script_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 gas_price = 0;
let config = self.config.unwrap_or_default();
let mock_db = self.mock_db;
let mut p2p = self.p2p.unwrap_or_else(|| MockP2P::new_with_txs(vec![]));
p2p.expect_notify_gossip_transaction_validity()
.returning(move |_, _| Ok(()));
p2p.expect_broadcast_transaction()
.returning(move |_| Ok(()));
let importer = self
.importer
.unwrap_or_else(|| MockImporter::with_blocks(vec![]));
let gas_price_provider = MockTxPoolGasPrice::new(gas_price);
let mut consensus_parameters_provider =
MockConsensusParametersProvider::default();
consensus_parameters_provider
.expect_latest_consensus_parameters()
.returning(|| Arc::new(Default::default()));
let service = new_service(
config,
MockDBProvider(mock_db.clone()),
importer,
p2p,
Default::default(),
gas_price_provider,
consensus_parameters_provider,
);
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
}
}