Skip to content
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
693a502
Define canonical state and query boundaries
metaphorics Oct 26, 2026
aaf2d2f
Bound prefix scans and account index rows
metaphorics Oct 25, 2026
bab2ce0
Import and export portable index bundles
metaphorics Oct 25, 2026
c203a4e
Add queries over a verified historical prefix
metaphorics Oct 26, 2026
2300344
Provide Tx/script retained-heap byte accounting for payload owner (mi…
metaphorics Oct 27, 2026
10efbce
Introduce the exact block payload owner
metaphorics Oct 27, 2026
c570eb5
Share the witness-fill predicate
metaphorics Oct 27, 2026
f3feea0
Add witnessed snapshot artifacts and seed ownership
metaphorics Oct 27, 2026
a571345
Unify block payloads and snapshot seed recovery
metaphorics Oct 27, 2026
4670ffd
Publish implemented payload and seed architecture
metaphorics Oct 27, 2026
a103487
Prove snapshot seed acceptance transitions
metaphorics Oct 27, 2026
ce71123
Format RPC payload compatibility test
metaphorics Oct 27, 2026
7b87ed9
Move query API tests behind test boundary
metaphorics Oct 27, 2026
a1b3f2c
Eliminate hot-path BlockPayload clones with borrowed slices
metaphorics Oct 27, 2026
8a123cf
Drop phantom metrics; keep portable bundle scope (bundle-inclusive do…
metaphorics Oct 27, 2026
39c4337
Document submitblock prefix buffer ownership
metaphorics Oct 27, 2026
00154f7
Keep staging byte metrics test-local
metaphorics Oct 27, 2026
b80300e
Format payload P2P consumers
metaphorics Oct 27, 2026
423790d
Extract journal checks and drop the stale snapshot import
metaphorics Oct 27, 2026
ae092d2
Keep portable manifests and artifacts consistent
metaphorics Oct 25, 2026
b6c4ca5
Restore bundles below recovered receiver tips
metaphorics Oct 26, 2026
00525bc
Keep portable chain capture cancellable
metaphorics Oct 26, 2026
4a9743e
Document portable bundle lifecycle proofs
metaphorics Oct 26, 2026
7a06a02
Validate portable bundle boundaries and cleanup
metaphorics Oct 27, 2026
2d25e85
fix(index): verify prefix anchor height, hold one seed lease, refresh…
metaphorics Oct 27, 2026
376313a
Resolve index runtime and portable lint findings
metaphorics Oct 27, 2026
20d7abf
Restore seed lease and reset invariants
metaphorics Oct 27, 2026
6355966
Attach executable proofs to index and sync contracts
metaphorics Oct 27, 2026
576ebc6
Preserve seed lease handoff on lower-leg stalls
metaphorics Oct 27, 2026
554fd54
Export Core v2 UTXO snapshots
metaphorics Oct 27, 2026
b315ddc
Seal verified snapshot ownership through activation
metaphorics Oct 27, 2026
7080d29
Refuse nonregular committed snapshot sources
metaphorics Oct 27, 2026
1b18364
Name typed reads in embedding errors
metaphorics Oct 27, 2026
424715c
Describe the portable restore completeness check
gormes-agent[bot] Oct 28, 2026
1db4e69
Fix AssumeUTXO activation, replay, and bundle IO
metaphorics Oct 28, 2026
f553dab
Bound restore locator memory
metaphorics Oct 28, 2026
3e26c10
Match export and restore bundle position limits
metaphorics Oct 28, 2026
a74ef07
Merge main into stack/08-assumeutxo
metaphorics Oct 28, 2026
acd663b
Merge main into stack/08-assumeutxo
metaphorics Oct 28, 2026
297f73f
Adapt merged sync tests to branch ownership API
metaphorics Oct 28, 2026
3f7e344
Order coins within a txid by Core VARINT key
metaphorics Oct 28, 2026
33871fd
Make staged dumptxoutset temp unique per attempt
metaphorics Oct 28, 2026
75ca5f7
Merge remote-tracking branch 'origin/main' into stack/08-assumeutxo
metaphorics Oct 28, 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## Unreleased

- Verified snapshot state for AssumeUTXO import (`crates/utxo/src/core_snapshot.rs`, `crates/chainstate/src/assumeutxo.rs`, `crates/node/src/snapshot.rs`). `read_verified` seals the decoded portable set with its base hash, height, commitment and coin statistics, so portable activation performs no second commitment pass or statistics scan. Sets that arrive without proof (native v4 input) are verified exactly once under the lifecycle lock. Caller digests and snapshot trailers still establish no trust. Recorded compatibility boundary: files repeating txid groups are refused (`TransactionOrder`, RPC `-22`); Core 31.1 accepts such a file. No performance gain is claimed.
4 changes: 4 additions & 0 deletions Cargo.lock

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

84 changes: 84 additions & 0 deletions bin/bitcoin-rs/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,3 +501,87 @@ assumevalid=0000000000000000000000000000000000000000000000000000000000000000
}
}
}

#[cfg(test)]
mod feature_option_tests {
use std::ffi::OsString;
use std::path::Path;

use anyhow::Result;
use bitcoin_rs_node::{Network, NodeConfig};
use clap::Parser as _;

use super::{CliArgs, parse_for_network, resolve};

fn load(args: &[&str], vars: &[(&str, &str)]) -> Result<NodeConfig> {
let argv = std::iter::once("bitcoin-rs").chain(args.iter().copied());
let cli = CliArgs::try_parse_from(argv)?;
resolve(
cli,
vars.iter()
.map(|(key, value)| (OsString::from(*key), OsString::from(*value))),
)
}

fn refusal(args: &[&str], vars: &[(&str, &str)]) -> String {
match load(args, vars) {
Ok(_) => panic!("configuration must be refused"),
Err(error) => error.to_string(),
}
}

#[test]
fn index_bundle_directories_resolve_from_the_command_line_and_stay_exclusive() -> Result<()> {
let import = load(&["--txindex=true", "--index-bundle-import", "/srv/in"], &[])?.indexes;
assert_eq!(import.bundle_import.as_deref(), Some(Path::new("/srv/in")));
assert_eq!(import.bundle_export, None);

let export = load(
&["--txindex=true"],
&[("BITCOIN_RS_INDEX_BUNDLE_EXPORT", "/srv/out")],
)?
.indexes;
assert_eq!(export.bundle_export.as_deref(), Some(Path::new("/srv/out")));

// A command-line request never overrides a conflicting environment one.
let conflict = refusal(
&["--txindex=true", "--index-bundle-import", "/srv/in"],
&[("BITCOIN_RS_INDEX_BUNDLE_EXPORT", "/srv/out")],
);
assert!(conflict.contains("mutually exclusive"));
assert!(refusal(&["--index-bundle-export", "/srv/out"], &[]).contains("--txindex"));
Ok(())
}

#[test]
fn empty_index_bundle_directories_are_refused_by_clap_and_the_environment() {
for flag in ["--index-bundle-import", "--index-bundle-export"] {
assert!(CliArgs::try_parse_from(["bitcoin-rs", flag, ""]).is_err());
assert!(CliArgs::try_parse_from(["bitcoin-rs", flag, " "]).is_err());
assert!(CliArgs::try_parse_from(["bitcoin-rs", flag]).is_err());
}
for key in [
"BITCOIN_RS_INDEX_BUNDLE_IMPORT",
"BITCOIN_RS_INDEX_BUNDLE_EXPORT",
] {
assert!(load(&["--txindex=true"], &[(key, "")]).is_err());
}
}

/// A one-shot restore or export is operator opt-in on the options the
/// table names; the Core-compatible file has no spelling for it.
#[test]
fn bitcoin_conf_cannot_request_a_bundle() {
let layers = parse_for_network(
"
index-bundle-import=/srv/in
indexbundleexport=/srv/out
",
Network::Regtest,
);
for layer in &layers {
assert_eq!(layer.indexes.bundle_import, None);
assert_eq!(layer.indexes.bundle_export, None);
}
}
}
49 changes: 49 additions & 0 deletions bin/bitcoin-rs/tests/gates/g17_dependency_direction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,55 @@ pub fn observe_sync(chain: &dyn SyncChain) -> Option<Arc<TipSnapshot>> {
}
";

/// Approved exact-payload edge: `primitives` owns `BlockPayload` on the
/// workspace `bytes` buffer; chainstate/p2p/node/mining consume it.
/// The gate pins the approved direction without forbidding the `bytes` edge
/// and without reintroducing a cycle.
#[test]
fn primitives_owns_block_payload_with_approved_bytes_edge() -> Result<(), Box<dyn std::error::Error>>
{
let manifest = dependency_graph::workspace_root_manifest();
let root = manifest.parent().ok_or("workspace root")?.canonicalize()?;
let read_manifest = |relative: &str| {
std::fs::read_to_string(root.join(relative))
.unwrap_or_else(|error| panic!("read {relative}: {error}"))
};
// Approved producer edge: primitives names the workspace `bytes`
// dependency that backs `BlockPayload::from_raw/from_local/from_prefix`.
let primitives = read_manifest("crates/primitives/Cargo.toml");
assert!(
primitives.contains("bytes"),
"primitives must own the approved bytes edge for BlockPayload"
);
let lib = read_manifest("crates/primitives/src/lib.rs");
assert!(
lib.contains("BlockPayload"),
"primitives must re-export the BlockPayload owner type"
);
// Approved consumer direction: every payload consumer depends on
// primitives; primitives never depends back (no cycle reintroduced).
for consumer in [
"crates/chainstate/Cargo.toml",
"crates/p2p/Cargo.toml",
"crates/node/Cargo.toml",
"crates/mining/Cargo.toml",
] {
let text = read_manifest(consumer);
assert!(
text.contains("bitcoin-rs-primitives"),
"{consumer} must consume the primitives BlockPayload owner"
);
}
assert!(
!primitives.contains("bitcoin-rs-chainstate")
&& !primitives.contains("bitcoin-rs-p2p")
&& !primitives.contains("bitcoin-rs-node")
&& !primitives.contains("bitcoin-rs-mining"),
"primitives must not depend back on its payload consumers"
);
Ok(())
}

#[test]
fn workspace_dependency_direction_is_one_way() {
let graph = WorkspaceGraph::from_cargo_metadata();
Expand Down
Loading
Loading