Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
792a12d
chore(gitignore): ignore default node data dir
metaphorics May 21, 2026
1c0c9e5
feat(node): bootstrap peers through bounded admission
metaphorics May 21, 2026
be9267d
chore(p2p): bump version to 0.1.1 for SystemDnsResolver API
metaphorics May 21, 2026
0849aea
feat(rpc): scan addr utxos via scantxoutset
metaphorics May 21, 2026
fc79174
fix(node): keep applied sync tip contiguous
metaphorics May 21, 2026
9e434d2
fix(node): buffer out-of-order sync blocks
metaphorics May 21, 2026
d4673ee
fix(consensus): encode BIP34 heights like Core
metaphorics May 21, 2026
edfd149
test(consensus): pin signet BIP34 height-one bytes
metaphorics May 21, 2026
733c14c
fix(consensus): remove unreachable kernel network fallback
metaphorics May 21, 2026
fc688a3
fix(node): wire mdbx feature through runtime indexes
metaphorics May 21, 2026
a6b78ec
test(gates): enforce G14 evidence budgets
metaphorics May 22, 2026
8c6213f
test(node): cover contextual nBits rejection
metaphorics May 22, 2026
21aa952
test(rpc): pin empty muhash oracle
metaphorics May 22, 2026
f65c593
test(gates): enforce G2 muhash evidence
metaphorics May 22, 2026
1176cd6
test(node): assert sync coinstats plumbing
metaphorics May 22, 2026
33408cd
test(coinstats): cover muhash removal trailer
metaphorics May 22, 2026
314046e
test(node): cover apply spend coinstats
metaphorics May 22, 2026
40f8f61
feat(node): emit G2 muhash samples
metaphorics May 22, 2026
80b8d12
feat(gates): collect Core G2 muhash samples
metaphorics May 22, 2026
e461d94
fix(node): narrow mdbx feature forwarding
metaphorics May 22, 2026
7225f97
fix(node): create data dir before G2 sampler
metaphorics May 22, 2026
1adcfc4
fix(node): validate same-block UTXO spends
metaphorics May 22, 2026
a9681bd
fix(consensus): enforce coinbase scriptsig size
metaphorics May 22, 2026
45facf7
fix(p2p): reject oversized headers messages
metaphorics May 22, 2026
ef89f30
fix(node): restore coinbase overlay visibility
metaphorics May 22, 2026
461396a
fix(consensus): reject merkle-mutated blocks
metaphorics May 22, 2026
1429493
fix(p2p): reject oversized inventory vectors
metaphorics May 22, 2026
0cfa321
fix(p2p): reject oversized getblocks locators
metaphorics May 22, 2026
f1d85bd
fix(p2p): reject oversized locator payloads
metaphorics May 22, 2026
331c7a5
fix(p2p): reject oversized address messages
metaphorics May 22, 2026
57f3a58
fix(node): skip invalid BIP158 filter rows
metaphorics May 22, 2026
fd10227
fix(p2p): track sendcmpct negotiation
metaphorics May 22, 2026
6082045
fix(node): preserve getdata retry after send failure
metaphorics May 22, 2026
5fea2b4
fix: address PR review comments
metaphorics May 23, 2026
53fa4ed
fix(node): forward mdbx storage feature
metaphorics May 23, 2026
4ef4007
fix(rpc): require scantxoutset scanobjects
metaphorics May 23, 2026
ff754c9
chore(deps): drop unused skiplist workspace dep
metaphorics May 23, 2026
39aa8c1
fix(node): remove compile_error! that breaks fresh virtual-workspace …
Copilot May 23, 2026
1016336
fix(utxo): preserve high-vout records
metaphorics May 24, 2026
1ddf203
test(script): gate consensus proptest helpers
metaphorics May 24, 2026
db680b3
fix(node): exclude genesis coinbase from live UTXO state
metaphorics May 24, 2026
751968c
test(utxo): remove infallible snapshot result
metaphorics May 25, 2026
d5c01f6
fix(utxo): notify listeners on duplicate output overwrite
metaphorics May 25, 2026
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
14 changes: 11 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ jobs:
with:
components: clippy
- uses: Swatinem/rust-cache@v2
# In a virtual workspace (no root [package]), `--features` flags are not
# forwarded to library crates; use `-p bitcoin-rs` so the binary's feature
# table propagates all backends to bitcoin-rs-node and its deps.
- run: |
cargo clippy --workspace --all-targets \
cargo clippy -p bitcoin-rs --all-targets \
--no-default-features --features "${PORTABLE_FEATURES}" \
-- -D warnings

Expand All @@ -49,8 +52,13 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.95.0
- uses: Swatinem/rust-cache@v2
# Library unit tests run with each crate's own default features.
- run: cargo test --workspace --no-fail-fast
# Node / binary tests run with the full portable feature set. -p is
# required because `--workspace --features` silently ignores --features in
# a virtual workspace (no root [package] section in the top-level Cargo.toml).
- run: |
cargo test --workspace --no-fail-fast \
cargo test -p bitcoin-rs --no-fail-fast \
--no-default-features --features "${PORTABLE_FEATURES}"

bench-smoke:
Expand All @@ -60,7 +68,7 @@ jobs:
- uses: dtolnay/rust-toolchain@1.95.0
- uses: Swatinem/rust-cache@v2
- run: |
cargo bench --workspace --no-run \
cargo bench -p bitcoin-rs --no-run \
--no-default-features --features "${PORTABLE_FEATURES}"

deny:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ flamegraph.svg
heap-profile-*
core.*
/tmp/
/.bitcoin-rs/

# Re-include project plan tracked in-repo (global ~/.gitignore drops PLAN.md).
!PLAN.md
Expand Down
21 changes: 15 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,27 @@ protects. Robustness is not added later — handle all valid inputs and failure

## Build, test, lint

Cargo workspace, Rust 2024 edition, toolchain pinned to 1.95.0 via `rust-toolchain.toml`. The binary's
default features are NOT what CI validates. Always use the portable feature set so local results match CI:
Cargo workspace, Rust 2024 edition, toolchain pinned to 1.95.0 via `rust-toolchain.toml`. This is a
**virtual workspace** (no root `[package]` section in `Cargo.toml`), so `--workspace --features "..."`
silently ignores the `--features` flag — it has no root package to apply them to. Always target
`-p bitcoin-rs` when you need storage-backend features to reach `bitcoin-rs-node`:

```sh
FEATURES="rocksdb,fjall,redb,mdbx,bitcoinconsensus"
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --no-default-features --features "$FEATURES" -- -D warnings
cargo test --workspace --no-fail-fast --no-default-features --features "$FEATURES"
# Clippy: -p bitcoin-rs propagates features through bin/bitcoin-rs's feature table to node
cargo clippy -p bitcoin-rs --all-targets --no-default-features --features "$FEATURES" -- -D warnings
# Library unit tests run with each crate's own default features
cargo test --workspace --no-fail-fast
# Node/binary tests with the full portable feature set
cargo test -p bitcoin-rs --no-fail-fast --no-default-features --features "$FEATURES"
```

- Plain `cargo test`/`cargo clippy` (without `--no-default-features --features "$FEATURES"`) tests the
wrong configuration. Clippy CI runs `-D warnings` — any warning fails the build.
- `cargo clippy --workspace --all-targets --no-default-features --features "$FEATURES"` appears correct
but silently uses **zero** storage features for `bitcoin-rs-node` (virtual workspace + `default-features
= false` in the workspace dep declaration). The CI cache masks this; a cold build would fail. Use
`-p bitcoin-rs` as shown above.
- Clippy CI runs `-D warnings` — any warning fails the build.
- **`kernel` and `bitcoinconsensus` cannot be enabled in the same binary** (overlapping native Core
symbols). The kernel path is tested in isolation and needs system `libboost-dev` + `cmake`:
```sh
Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ bitcoin-rs-filters = { path = "crates/filters", version = "0.1.0" }
bitcoin-rs-coinstats = { path = "crates/coinstats", version = "0.1.0" }
bitcoin-rs-pruning = { path = "crates/pruning", version = "0.1.0" }
bitcoin-rs-mempool = { path = "crates/mempool", version = "0.1.0" }
bitcoin-rs-p2p = { path = "crates/p2p", version = "0.1.0" }
bitcoin-rs-p2p = { path = "crates/p2p", version = "0.1.1" }
bitcoin-rs-wallet = { path = "crates/wallet", version = "0.1.0" }
bitcoin-rs-mining = { path = "crates/mining", version = "0.1.0" }
bitcoin-rs-rpc = { path = "crates/rpc", version = "0.1.0" }
Expand Down Expand Up @@ -148,10 +148,9 @@ parking_lot = { version = ">=0.12.5, <0.13", default-features = false, features
"arc_lock", "send_guard",
] }

# Crossbeam (event-loop + utilities + mempool fallback path).
# Crossbeam (event-loop + utilities).
crossbeam-channel = { version = ">=0.5.15, <0.6", default-features = false, features = ["std"] }
crossbeam-utils = { version = ">=0.8.21, <0.9", default-features = false, features = ["std"] }
crossbeam-skiplist = { version = ">=0.1.3, <0.2", default-features = false, features = ["std", "alloc"] }

# Rayon — no default features at all on 1.12; nothing to disable/add.
rayon = { version = ">=1.12, <2" }
Expand Down
3 changes: 3 additions & 0 deletions bin/bitcoin-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ mimalloc.workspace = true
anyhow.workspace = true
bitcoin-rs-node.workspace = true

[dev-dependencies]
tempfile = "3"

[features]
default = ["rocksdb", "fjall", "redb", "mdbx", "bitcoinconsensus"]
rocksdb = ["bitcoin-rs-node/rocksdb"]
Expand Down
101 changes: 101 additions & 0 deletions bin/bitcoin-rs/tests/g2_bitcoind_muhash_script.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! Smoke tests for the G2 Bitcoin Core `MuHash` collection helper.
#![cfg(unix)]

use std::fs;
use std::os::unix::fs::PermissionsExt as _;
use std::path::{Path, PathBuf};
use std::process::Command;
Comment thread
metaphorics marked this conversation as resolved.

#[test]
fn script_prints_required_g2_heights() -> Result<(), Box<dyn std::error::Error>> {
let output = Command::new("bash")
.arg(script_path())
.args(["--print-heights", "20001"])
.output()?;

assert_success(&output);
assert_eq!(
String::from_utf8(output.stdout)?,
"0\n10000\n20000\n20001\n"
);
Ok(())
}

#[test]
fn script_normalizes_bitcoind_muhash_responses() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let bitcoin_cli = fake_bitcoin_cli(temp.path(), false)?;
let output = Command::new("bash")
.arg(script_path())
.arg("20001")
.env("BITCOIN_CLI", bitcoin_cli)
.output()?;

assert_success(&output);
assert_eq!(
String::from_utf8(output.stdout)?,
expected_samples([0, 10_000, 20_000, 20_001])
);
Ok(())
}

#[test]
fn script_rejects_bitcoind_height_mismatch() -> Result<(), Box<dyn std::error::Error>> {
let temp = tempfile::tempdir()?;
let bitcoin_cli = fake_bitcoin_cli(temp.path(), true)?;
let output = Command::new("bash")
.arg(script_path())
.arg("1")
.env("BITCOIN_CLI", bitcoin_cli)
.output()?;

assert!(!output.status.success());
assert!(String::from_utf8_lossy(&output.stderr).contains("expected 0"));
Ok(())
}

fn script_path() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../scripts/collect-g2-bitcoind-muhash-samples.sh")
}

fn fake_bitcoin_cli(dir: &Path, wrong_height: bool) -> Result<PathBuf, Box<dyn std::error::Error>> {
let path = dir.join("bitcoin-cli");
let height_expr = if wrong_height { "height + 1" } else { "height" };
fs::write(
&path,
format!(
r#"#!/usr/bin/env python3
import json
import sys

if len(sys.argv) != 5 or sys.argv[1] != "gettxoutsetinfo" or sys.argv[2] != "muhash" or sys.argv[4] != "true":
raise SystemExit(f"unexpected arguments: {{sys.argv[1:]!r}}")

height = int(sys.argv[3])
print(json.dumps({{"height": {height_expr}, "muhash": f"{{height:064x}}"}}))
"#,
),
)?;
let mut permissions = fs::metadata(&path)?.permissions();
permissions.set_mode(0o755);
fs::set_permissions(&path, permissions)?;
Ok(path)
}

fn expected_samples<const N: usize>(heights: [u32; N]) -> String {
let body = heights
.into_iter()
.map(|height| format!("{height}:{height:064x}"))
.collect::<Vec<_>>()
.join(",");
format!("{body}\n")
}

fn assert_success(output: &std::process::Output) {
assert!(
output.status.success(),
"script failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
Loading
Loading