Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ Prefer **one high-level scenario** per behavior cluster. Delete lower-level test

| ID | Layer | Description |
|----|-------|-------------|
| `node_cli_and_surface_smoke` | Lifecycle/CLI | Networks, `run_node`, config errors, CLI flags (incl. `--conf`, `--peertimeout=0`, log-level/mempool/electrum/inhibit), help/version |
| `node_cli_and_surface_smoke` | Lifecycle/CLI | Networks, `run_node`, config errors, CLI flags (incl. `--conf`, `--peertimeout=0`, log-level/mempool/electrum/inhibit), help/version. Signet: genesis header plus height-1 BIP325 connect |
| `three_stage_confirm_and_parent_pin_surface` | Consensus+query | Split load→scripts→write; parent pin; load ready timeout/cancel; instance-owned `last_write` / `last_pin` / `take_window` meters |
| `block_cache_and_mempool_hub_surface` | Net | BlockCache locator/eviction + MempoolHub accept/remove/reorg on mature chain. `DEFAULT_BODY_DEPTH == 16` stays a unit. |
| `store_error_and_corrupt_paths` | Store | Error/corrupt surfaces |
Expand All @@ -237,7 +237,7 @@ Prefer **one high-level scenario** per behavior cluster. Delete lower-level test
| `electrum_tweaks_subscribe_streams_then_done` | Electrum | Cake `tweaks.subscribe`: one-height result, per-height notifies, then `done` |
| `electrum_max_connections_rejects_extra_client` | Electrum | TCP cap drops the extra client |
| `electrum_idle_timeout_disconnects_quiet_client` | Electrum | Idle timeout closes a quiet socket |
| `esplora_broadcast_visible_in_rpc_and_electrum` | Node + Electrum + Esplora + RPC | One `run_p2p` datadir: Esplora `POST /tx` parent and mempool child appear in `getrawmempool` and Electrum mempool/history (`fee` on unconfirmed, including child `height = -1`) |
| `esplora_broadcast_visible_in_rpc_and_electrum` | Node + Electrum + Esplora + RPC | One `run_p2p` datadir: HTTP `sendrawtransaction` / `testmempoolaccept` (allowed + missing-or-spent), Esplora `POST /tx` parent and mempool child appear in `getrawmempool` and Electrum mempool/history (`fee` on unconfirmed, including child `height = -1`). Keep `accept.rs` reject units and RPC dry-run orphan-count |
| `two_node_header_and_block_sync` | P2P (**default + multinode CI**) | Seeder → peer 8-block IBD; peer `last_write` meter. **Not** re-run under `coverage.sh`. |
| `p2p_timeout_getaddr_and_keepalive_ping` | P2P (**default**) | One pad: v1-magic inbound drops at `peertimeout=1`, full-relay GetAddr cache 1000, headers-sync stall replace, self-connect refuses, AddrFetch `getaddr`/`addrv2` (no `getheaders`), one keepalive ping/pong. Sole-preferred stall KEEP stays a PeerHub unit. |
| `p2p_compact_hb_getblocktxn_and_orphan` | P2P (**default**) | One mature pad: HB coinbase `cmpctblock`, 2-tx compact → `getblocktxn` + connect, orphan child GetData then parent accept (INV AlreadyHave). Does **not** pin depth-10 full-block serve, tokio-worker lock, or park-not-reject logs |
Expand Down
17 changes: 0 additions & 17 deletions crates/rbitcoin-consensus/src/signet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,23 +400,6 @@ mod tests {
assert_eq!(stripped[0], 0x6a);
}

/// Regression: height-1 global signet block must accept under BIP325.
///
/// Bug class: `to_spend.scriptSig` missing leading `OP_0` before block_data push
/// produced a wrong txid → CHECKMULTISIG failed → tip stuck at 0.
#[test]
fn signet_block_1_solution_valid() {
let raw = include_bytes!("../tests/fixtures/signet_block_1.bin");
let block: Block = deserialize(raw).expect("decode signet block 1");
assert_eq!(
block.header.block_hash().to_string(),
"00000086d6b2636cb2a392d45edc4ec544a10024d30141c9adf4bfd9de533b53"
);
let challenge = default_signet_challenge();
validate_signet_block_solution(&block, challenge.as_script())
.expect("BIP325 solution for real signet height 1");
}

#[test]
fn custom_challenge_derives_expected_wire_magic() {
let challenge = ScriptBuf::from_bytes(vec![0x51]);
Expand Down
80 changes: 73 additions & 7 deletions crates/rbitcoin-test/tests/cross_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

use bitcoin::absolute::LockTime;
use bitcoin::consensus::Encodable;
use bitcoin::hashes::Hash;
use bitcoin::script::ScriptBuf;
use bitcoin::transaction::Version as TxVersion;
use bitcoin::{Amount, OutPoint, Sequence, Transaction, TxIn, TxOut, Witness};
use bitcoin::{Amount, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid, Witness};
use rbitcoin_consensus::{accept_and_connect_block, pad_empty_from, ChainParams, Milestone};
use rbitcoin_electrum::electrum_scripthash_hex;
use rbitcoin_node::{run_p2p, NodeConfig};
Expand Down Expand Up @@ -116,7 +117,7 @@ async fn esplora_broadcast_visible_in_rpc_and_electrum() {
let td = TestDatadir::new().unwrap();
let params = ChainParams::regtest();
let genesis = bitcoin::blockdata::constants::genesis_block(bitcoin::Network::Regtest);
let coinbase_txid = {
let (coinbase_txid, rpc_cb) = {
let q = Query::open_or_create_tiny(td.store_path()).unwrap();
accept_and_connect_block(&q, &params, Height::GENESIS, &genesis, Milestone::NONE).unwrap();
let (_tip, _time, cbs) = pad_empty_from(
Expand All @@ -125,11 +126,11 @@ async fn esplora_broadcast_visible_in_rpc_and_electrum() {
genesis.block_hash(),
genesis.header.time,
1,
101,
1,
102,
2,
);
q.flush().unwrap();
cbs[0]
(cbs[0], cbs[1])
};

let electrum_addr = ephemeral_addr();
Expand All @@ -156,9 +157,68 @@ async fn esplora_broadcast_visible_in_rpc_and_electrum() {

let (st, height) = http_get(esplora_addr, "/blocks/tip/height").await;
assert_eq!(st, 200, "esplora tip height: {height}");
assert_eq!(height, "101");
assert_eq!(height, "102");
let count = jsonrpc(rpc_addr, "getblockcount", json!([])).await;
assert_eq!(count["result"], 101, "{count}");
assert_eq!(count["result"], 102, "{count}");

let rpc_spk = ScriptBuf::from_bytes(vec![0x54]);
let rpc_spend = Transaction {
version: TxVersion::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
txid: rpc_cb,
vout: 0,
},
script_sig: ScriptBuf::new(),
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
witness: Witness::new(),
}],
output: vec![TxOut {
value: Amount::from_sat(50_0000_0000 - 1_000),
script_pubkey: rpc_spk,
}],
};
let mut rpc_raw = Vec::new();
rpc_spend.consensus_encode(&mut rpc_raw).unwrap();
let rpc_hex = rbitcoin_primitives::hex_encode(&rpc_raw);
let rpc_txid = rpc_spend.compute_txid().to_string();
let tma = jsonrpc(rpc_addr, "testmempoolaccept", json!([[rpc_hex.clone()]])).await;
assert_eq!(tma["result"][0]["allowed"], true, "{tma}");
let sent = jsonrpc(rpc_addr, "sendrawtransaction", json!([rpc_hex])).await;
assert_eq!(sent["result"], rpc_txid, "{sent}");
let mem = jsonrpc(rpc_addr, "getrawmempool", json!([])).await;
let ids = mem["result"].as_array().expect("getrawmempool array");
assert!(
ids.iter().any(|v| v.as_str() == Some(rpc_txid.as_str())),
"getrawmempool missing sendraw {rpc_txid}: {mem}"
);
let miss = Transaction {
version: TxVersion::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::from_byte_array([0x11; 32]),
vout: 0,
},
script_sig: ScriptBuf::new(),
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
witness: Witness::new(),
}],
output: vec![TxOut {
value: Amount::from_sat(1),
script_pubkey: ScriptBuf::from_bytes(vec![0x51]),
}],
};
let mut miss_raw = Vec::new();
miss.consensus_encode(&mut miss_raw).unwrap();
let miss_hex = rbitcoin_primitives::hex_encode(&miss_raw);
let tma = jsonrpc(rpc_addr, "testmempoolaccept", json!([[miss_hex]])).await;
assert_eq!(tma["result"][0]["allowed"], false, "{tma}");
assert_eq!(
tma["result"][0]["reject-reason"], "bad-txns-inputs-missingorspent",
"{tma}"
);

let spk = ScriptBuf::from_bytes(vec![0x52]);
let spend = Transaction {
Expand Down Expand Up @@ -186,6 +246,8 @@ async fn esplora_broadcast_visible_in_rpc_and_electrum() {
let (st, body) = http_post(esplora_addr, "/tx", &hex).await;
assert_eq!(st, 200, "POST /tx: {body}");
assert_eq!(body, txid_hex);
let dup = jsonrpc(rpc_addr, "sendrawtransaction", json!([hex.clone()])).await;
assert_eq!(dup["result"], txid_hex, "sendraw of live mempool tx: {dup}");

let (st, status) = http_get(esplora_addr, &format!("/tx/{txid_hex}/status")).await;
assert_eq!(st, 200, "GET /tx status: {status}");
Expand All @@ -198,6 +260,10 @@ async fn esplora_broadcast_visible_in_rpc_and_electrum() {
ids.iter().any(|v| v.as_str() == Some(txid_hex.as_str())),
"getrawmempool missing {txid_hex}: {mem}"
);
assert!(
ids.iter().any(|v| v.as_str() == Some(rpc_txid.as_str())),
"getrawmempool dropped sendraw {rpc_txid}: {mem}"
);

let sh = electrum_scripthash_hex(spk.as_bytes());
let mut el = TcpStream::connect(electrum_addr).await.unwrap();
Expand Down
39 changes: 37 additions & 2 deletions crates/rbitcoin-test/tests/scenarios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
//! Prefer fewer tests at the highest layer that still hit production paths.
//! Mature regtest chains are built once per test that needs them (not thrice).

use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::Hash;
use bitcoin::{Amount, BlockHash};
use bitcoin::{Amount, Block, BlockHash};
use rbitcoin_cli::cli_main as cli_cli_main;
use rbitcoin_consensus::{accept_and_connect_block, ChainParams, Milestone};
use rbitcoin_consensus::{accept_and_connect_block, genesis_block, ChainParams, Milestone};
use rbitcoin_node::{cli_main as node_cli_main, run_node, NodeConfig};
use rbitcoin_primitives::{Fk, Height, Network, VERSION};
use rbitcoin_query::testutil::FixtureChain;
Expand All @@ -16,6 +17,7 @@ use rbitcoin_test::mine::{mine_regtest_block, regtest_genesis, spend_anyone_can_
use rbitcoin_test::{
assert_reconstruct_eq, build_mature_regtest_with_spend, pad_empty_from, TestDatadir,
};
use std::path::PathBuf;
use std::process::{Command, ExitCode};

/// This toolchain's `ExitCode` lacks `PartialEq`; compare via Debug.
Expand All @@ -42,6 +44,39 @@ fn node_cli_and_surface_smoke() {
.with_tiny_heads();
let handle = run_node(cfg).unwrap();
assert_eq!(handle.network_name(), net.as_str());
if net == Network::Signet {
let params = ChainParams::signet();
let genesis = genesis_block(&params);
handle.query.enter_direct_index_mode().unwrap();
accept_and_connect_block(
&handle.query,
&params,
Height::GENESIS,
&genesis,
Milestone::NONE,
)
.unwrap();
let (_fk, rec) = handle
.query
.header_at_height(Height::GENESIS)
.unwrap()
.expect("signet genesis header");
assert_eq!(rec.hash, genesis.block_hash().to_byte_array());
assert_eq!(rec.hash, params.genesis_hash.to_byte_array());
let raw = std::fs::read(
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../rbitcoin-consensus/tests/fixtures/signet_block_1.bin"),
)
.expect("signet_block_1.bin");
let block1: Block = deserialize(&raw).expect("signet height 1");
assert_eq!(
block1.block_hash().to_string(),
"00000086d6b2636cb2a392d45edc4ec544a10024d30141c9adf4bfd9de533b53"
);
accept_and_connect_block(&handle.query, &params, Height(1), &block1, Milestone::NONE)
.unwrap();
assert_eq!(handle.query.tip_height(), Some(Height(1)));
}
handle.shutdown().unwrap();
}
assert!(Network::parse("nope").is_err());
Expand Down