Skip to content
Closed
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
26 changes: 25 additions & 1 deletion crates/rbitcoin-rpc/src/methods/mempool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,31 @@ pub(crate) fn testmempoolaccept(ctx: &RpcContext, params: &RpcParams) -> Result<
}
let mut out = Vec::new();
for tx in decoded {
let txid = hash_hex_display(&tx.compute_txid().to_byte_array());
let raw_txid = tx.compute_txid();
let txid_bytes = raw_txid.to_byte_array();
let txid = hash_hex_display(&txid_bytes);
let wtxid_str = hash_hex_display(&tx.compute_wtxid().to_byte_array());

// #628: active-chain confirmed -> txn-already-known (reorg-safe)
let confirmed_active = match ctx.query.tx_fk_by_txid_tip(&txid_bytes) {
Ok(Some(fk)) => ctx
.query
.store()
.is_confirmed_strong(fk)
.map_err(|e| rpc_error(ERR_MISC, format!("query failed: {e}")))?,
Ok(None) => false,
Err(e) => return Err(rpc_error(ERR_MISC, format!("query failed: {e}"))),
};
if confirmed_active {
out.push(serde_json::json!({
"txid": txid,
"wtxid": wtxid_str,
"allowed": false,
"reject-reason": "txn-already-known",
}));
continue;
}

match mp.test_accept(&tx) {
Ok(r) => {
let wtxid = hash_hex_display(&tx.compute_wtxid().to_byte_array());
Expand Down
64 changes: 64 additions & 0 deletions crates/rbitcoin-rpc/src/methods_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4670,3 +4670,67 @@ fn dispatch_wrong_json_types_are_param_errors() {
}
let _ = std::fs::remove_dir_all(&dir);
}

// #628: active-chain vs mempool distinction - shipped JSON asserts
#[test]
fn testmempoolaccept_628_confirmed_is_already_known() {
let (ctx, _dir, hub) = ctx_regtest_hub();
let miner = TestMiner(hub);
for _ in 0..101 {
miner
.generate_to_script(1, ScriptBuf::from_bytes(vec![0x51]), vec![])
.unwrap();
}
let (hex, tx) = mature_coinbase_spend_hex(&ctx, generated_coinbase_value(&ctx, 100) - 1000);
miner
.generate_to_script(1, ScriptBuf::from_bytes(vec![0x51]), vec![tx])
.unwrap();
let res = dispatch(&ctx, "testmempoolaccept", vec![json!([hex]), json!(0)]).unwrap();
assert_eq!(res[0]["allowed"], json!(false));
assert_eq!(res[0]["reject-reason"], json!("txn-already-known"));
}
#[test]
fn testmempoolaccept_628_mempool_duplicate_is_already_in_mempool() {
let (ctx2, _dir2, hub) = ctx_regtest_hub();
let miner = TestMiner(hub);
for _ in 0..101 {
miner
.generate_to_script(1, ScriptBuf::from_bytes(vec![0x51]), vec![])
.unwrap();
}
let (hex, tx) = mature_coinbase_spend_hex(&ctx2, generated_coinbase_value(&ctx2, 100) - 1000);
let mp = ctx2.mempool.as_ref().unwrap();
let _ = mp.accept_tx_from(&tx, None).unwrap();
let res = dispatch(&ctx2, "testmempoolaccept", vec![json!([hex]), json!(0)]).unwrap();
assert_eq!(res[0]["allowed"], json!(false));
assert_eq!(res[0]["reject-reason"], json!("txn-already-in-mempool"));
}
#[test]
fn testmempoolaccept_628_reorged_archive_not_already_known() {
let (ctx, _dir, hub) = ctx_regtest_hub();
let miner = TestMiner(hub);
for _ in 0..101 {
miner
.generate_to_script(1, ScriptBuf::from_bytes(vec![0x51]), vec![])
.unwrap();
}
let (hex, tx) = mature_coinbase_spend_hex(&ctx, generated_coinbase_value(&ctx, 100) - 1000);
miner
.generate_to_script(1, ScriptBuf::from_bytes(vec![0x51]), vec![tx])
.unwrap();
let tip_hash = dispatch(&ctx, "getbestblockhash", vec![]).unwrap();
let _ = dispatch(&ctx, "invalidateblock", vec![tip_hash.clone()]).unwrap();
let res = dispatch(
&ctx,
"testmempoolaccept",
vec![json!([hex.clone()]), json!(0)],
)
.unwrap();
assert_eq!(res[0]["allowed"], json!(true), "{res}");
assert_ne!(
res[0]["reject-reason"],
json!("txn-already-known"),
"archive-only must not be known"
);
let _ = dispatch(&ctx, "reconsiderblock", vec![tip_hash]).unwrap();
}
Loading