chia-sdk-test 0.36.0

A wallet simulator and related tooling for testing Chia wallet code.
Documentation
use chia_bls::Signature;
use chia_protocol::{Bytes32, Coin, CoinSpend, Program, SpendBundle};
use chia_sdk_types::conditions::{CreateCoin, Memos};
use clvmr::NodePtr;

use crate::{FullNodeSimulator, to_program, to_puzzle};

fn spend_to_child(
    coin: Coin,
    puzzle_reveal: Program,
    puzzle_hash: Bytes32,
    amount: u64,
) -> anyhow::Result<SpendBundle> {
    Ok(SpendBundle::new(
        vec![CoinSpend::new(
            coin,
            puzzle_reveal,
            to_program([CreateCoin::<NodePtr>::new(puzzle_hash, amount, Memos::None)])?,
        )],
        Signature::default(),
    ))
}

#[test]
fn push_tx_rejects_mempool_conflict() -> anyhow::Result<()> {
    let mut sim = FullNodeSimulator::new();
    let (puzzle_hash, puzzle_reveal) = to_puzzle(1)?;
    let coin = sim.new_coin(puzzle_hash, 100);
    let first = spend_to_child(coin, puzzle_reveal.clone(), puzzle_hash, 98)?;
    let conflicting = spend_to_child(coin, puzzle_reveal, puzzle_hash, 99)?;

    assert!(sim.push_tx(first).success);
    let response = sim.push_tx(conflicting);

    assert!(!response.success);
    assert_eq!(
        response.error.as_deref(),
        Some("Validation error: MempoolConflict")
    );
    assert_eq!(
        sim.get_blockchain_state()
            .blockchain_state
            .unwrap()
            .mempool_size,
        1
    );

    Ok(())
}

#[test]
fn push_tx_replaces_mempool_conflict_with_higher_fee_superset() -> anyhow::Result<()> {
    let mut sim = FullNodeSimulator::new();
    let (puzzle_hash, puzzle_reveal) = to_puzzle(1)?;
    let coin = sim.new_coin(puzzle_hash, 100);
    let first = spend_to_child(coin, puzzle_reveal.clone(), puzzle_hash, 99)?;
    let replacement = spend_to_child(coin, puzzle_reveal, puzzle_hash, 98)?;
    let first_tx_id = first.name();
    let replacement_tx_id = replacement.name();

    assert!(sim.push_tx(first).success);
    assert!(sim.push_tx(replacement).success);

    assert!(
        sim.get_mempool_item_by_tx_id(first_tx_id)
            .mempool_item
            .is_none()
    );
    assert!(
        sim.get_mempool_item_by_tx_id(replacement_tx_id)
            .mempool_item
            .is_some()
    );
    assert_eq!(
        sim.get_blockchain_state()
            .blockchain_state
            .unwrap()
            .mempool_size,
        1
    );

    Ok(())
}

#[test]
fn push_tx_does_not_replace_conflict_that_is_not_a_superset() -> anyhow::Result<()> {
    let mut sim = FullNodeSimulator::new();
    let (puzzle_hash, puzzle_reveal) = to_puzzle(1)?;
    let coin_a = sim.new_coin(puzzle_hash, 100);
    let coin_b = sim.new_coin(puzzle_hash, 100);
    let first_spend_a = spend_to_child(coin_a, puzzle_reveal.clone(), puzzle_hash, 99)?;
    let first_spend_b = spend_to_child(coin_b, puzzle_reveal.clone(), puzzle_hash, 99)?;
    let first = SpendBundle::new(
        vec![
            first_spend_a.coin_spends[0].clone(),
            first_spend_b.coin_spends[0].clone(),
        ],
        Signature::default(),
    );
    let conflicting = spend_to_child(coin_a, puzzle_reveal, puzzle_hash, 50)?;

    assert!(sim.push_tx(first).success);
    let response = sim.push_tx(conflicting);

    assert!(!response.success);
    assert_eq!(
        response.error.as_deref(),
        Some("Validation error: MempoolConflict")
    );
    assert_eq!(
        sim.get_blockchain_state()
            .blockchain_state
            .unwrap()
            .mempool_size,
        1
    );

    Ok(())
}

#[test]
fn push_tx_allows_dedup_compatible_mempool_overlap() -> anyhow::Result<()> {
    let mut sim = FullNodeSimulator::new();
    let (puzzle_hash, puzzle_reveal) = to_puzzle(1)?;
    let shared_coin = sim.new_coin(puzzle_hash, 100);
    let extra_coin = sim.new_coin(puzzle_hash, 100);
    let shared_spend = spend_to_child(shared_coin, puzzle_reveal.clone(), puzzle_hash, 100)?;
    let extra_spend = spend_to_child(extra_coin, puzzle_reveal, puzzle_hash, 99)?;
    let second_bundle = SpendBundle::new(
        vec![
            shared_spend.coin_spends[0].clone(),
            extra_spend.coin_spends[0].clone(),
        ],
        Signature::default(),
    );

    assert!(sim.push_tx(shared_spend).success);
    assert!(sim.push_tx(second_bundle).success);
    assert_eq!(
        sim.get_blockchain_state()
            .blockchain_state
            .unwrap()
            .mempool_size,
        2
    );

    sim.farm_block(1);
    let spends = sim
        .get_block_spends(sim.header_hash())
        .block_spends
        .unwrap();
    assert_eq!(spends.len(), 2);
    assert_eq!(
        spends
            .iter()
            .filter(|spend| spend.coin.coin_id() == shared_coin.coin_id())
            .count(),
        1
    );

    Ok(())
}