Skip to content

refactor(chain,testenv): move shared test helpers into testenv - #2177

Open
tvpeter wants to merge 6 commits into
bitcoindevkit:masterfrom
tvpeter:refactor/mv-txtemplate-to-testenv
Open

tvpeter wants to merge 6 commits into
bitcoindevkit:masterfrom
tvpeter:refactor/mv-txtemplate-to-testenv

Conversation

@tvpeter

@tvpeter tvpeter commented Apr 15, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR moves TxTemplate and helper functions from crates/chain/tests/common into crates/testenv so that other BDK libraries and users testing against BDK can build transaction histories the same way.

Key improvements:

  • Replaced &'a [T] fields in TxTemplate with owned Vecs and a fluent builder API (with_inputs, with_outputs, with_anchors, and assume_canonical()), removing local variable binding boilerplate.
  • init_graph now lazily derives script pubkeys from the indices a template actually uses, removing the old 0-9 output limit.
  • TxInTemplate::Bogus prevouts are now deterministically derived from the template and input index instead of a random string.
  • init_graph panics on duplicate tx_name collisions instead of silently overwriting entries and breaking PrevTx references.
  • drops the changeset! macro

Addresses the first and second todos on #1936 and #2275

Note to Reviewers:

  • new_tx: Retains its exact original behavior to prevent unintended txid changes across callers.
  • Standalone Bug Fix: Includes a fix for the reindex_tx_graph benchmark where default null prevouts made transactions look like coinbases carrying seen_at, causing post-fix(chain): skip coinbase txs with a last_seen in canonicalization #2372 canonicalization failures (0 SAT vs 21000 SAT).
  • miniscript feature: bdk_testenv now depends on bdk_chain/miniscript, so test/bench builds always have it enabled.

Changelog notice

  • moved TxTemplate, TxInTemplate, TxOutTemplate and init_graph from bdk_chain's tests into bdk_testenv::tx_template
  • moved new_tx, genesis_block_id, tip_block_id and spk_at_index into bdk_testenv::utils; tip_block_id now takes a height
  • added a builder API to TxTemplate; with_inputs/with_outputs/ with_anchors take impl IntoIterator, and assume_canonical() replaces with_assume_canonical(bool)
  • init_graph now panics on a duplicate tx_name
  • renamed TxTemplateEnv::txid_to_name to txids
  • spk_at_index takes &Descriptor<DescriptorPublicKey> instead of a KeychainTxOutIndex
  • removed the unused changeset! macro
  • bdk_testenv now depends on bdk_chain's miniscript feature
  • DESCRIPTORS is no longer #[doc(hidden)], so its warning against mainnet use is visible in the docs

Checklists

All Submissions:

@oleonardolima oleonardolima left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cACK a5d9f41

You can rebase to get the latest toolchain updated CI. Overall looks good, I only would like to take a deeper look into the last commit.

@oleonardolima

Copy link
Copy Markdown
Contributor

cACK a5d9f41

You can rebase to get the latest toolchain updated CI. Overall looks good, I only would like to take a deeper look into the last commit.

Also, I'd avoid mixing renaming and moving code in the same commits.

@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch from a5d9f41 to 6d5cac1 Compare April 30, 2026 02:58
@oleonardolima

Copy link
Copy Markdown
Contributor

@tvpeter Oh, there are some conflicts 😅

@tvpeter

tvpeter commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

@tvpeter Oh, there are some conflicts 😅

Yeah, from the recent merge. I will rebase and resolve

@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch 2 times, most recently from e381444 to 49ba5a5 Compare May 2, 2026 12:46
@oleonardolima
oleonardolima force-pushed the refactor/mv-txtemplate-to-testenv branch from 49ba5a5 to 067245e Compare June 15, 2026 16:55
@oleonardolima

Copy link
Copy Markdown
Contributor

@tvpeter I've rebased it on top of master to solve the conflicts, let me know if it's all good or I've messed something.

@codecov

codecov Bot commented Jun 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.66%. Comparing base (7b68d28) to head (7088e03).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2177   +/-   ##
=======================================
  Coverage   80.66%   80.66%           
=======================================
  Files          31       31           
  Lines        6236     6236           
  Branches      293      293           
=======================================
  Hits         5030     5030           
  Misses       1136     1136           
  Partials       70       70           
Flag Coverage Δ
rust 80.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tvpeter

tvpeter commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@tvpeter I've rebased it on top of master to solve the conflicts, let me know if it's all good or I've messed something.

At a glance, it looks just fine. Thank you.

@evanlinjin

evanlinjin commented Jun 16, 2026 •

Copy link
Copy Markdown
Member

Remember that PR descriptions need to explain why things are done.

It also updates the TxTemplate to use the Cow smart pointe

What is the rationale for this?

Edit: I see that it is my suggestion in the ticket. I was wrong. Cow only makes sense if the type will start as a reference and may need to be mutated later on.

@tvpeter

tvpeter commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Edit: I see that it is my suggestion in the ticket. I was wrong. Cow only makes sense if the type will start as a reference and may need to be mutated later on.

Alright, should I revert the aspect that updates the TxTemplate struct using Cow?

@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch 3 times, most recently from 5ee920d to ee76753 Compare June 30, 2026 02:46
Comment thread crates/testenv/src/utils.rs
Comment thread crates/testenv/src/tx_template.rs
Comment thread crates/testenv/src/tx_template.rs Outdated
Comment thread crates/testenv/src/tx_template.rs Outdated
Comment thread crates/testenv/src/tx_template.rs
Comment thread crates/testenv/src/lib.rs
@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch from ee76753 to 16619d4 Compare July 21, 2026 15:35

@ValuedMammal ValuedMammal left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK 16619d4

ACK 1b2e2b6

@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch from 16619d4 to 1b2e2b6 Compare July 21, 2026 21:17
@ValuedMammal

Copy link
Copy Markdown
Contributor

@tvpeter any idea when we can expect a 0.14.0 release of testenv?

@tvpeter

tvpeter commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@tvpeter any idea when we can expect a 0.14.0 release of testenv?

I checked and noticed that the last release was nearly a year ago. Therefore, next month should be an ideal time for the next release.

@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch from 1b2e2b6 to f8e51df Compare July 24, 2026 12:09
@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch 2 times, most recently from c93e2ae to d85a806 Compare September 7, 2026 18:34
@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch from d85a806 to 8a43ad7 Compare October 16, 2026 19:28
@tvpeter
tvpeter requested a review from a team as a code owner October 16, 2026 19:28
@tvpeter

tvpeter commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

CI failures issue #2386

@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch from 8a43ad7 to c521f1c Compare October 17, 2026 16:30
Comment thread crates/testenv/src/tx_template.rs Outdated
Comment thread crates/testenv/src/utils.rs Outdated
Comment thread crates/testenv/src/utils.rs Outdated
Comment thread crates/chain/benches/indexer.rs
Comment thread crates/testenv/Cargo.toml
@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch 2 times, most recently from f8b517d to 40f4cd5 Compare October 20, 2026 17:41
@tvpeter tvpeter changed the title refactor(tests): move TxTemplate from chain to testenv refactor(chain,testenv): move shared test helpers into testenv Oct 20, 2026

@evanlinjin evanlinjin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pushing this forward.

Comment thread crates/core/src/lib.rs
Comment on lines +5 to +14
//! - `std` (default): use the standard library, including its `HashMap`/`HashSet`. Without it, this
//! crate is `no_std` and uses `alloc`'s `BTreeMap`/`BTreeSet` instead.
//! - `serde`: implement `serde::Serialize`/`serde::Deserialize` for this crate's types.
//! - `hashbrown`: use [`hashbrown`](https://docs.rs/hashbrown)'s `HashMap`/`HashSet` for this
//! crate's hash collections (e.g. `bdk_chain`'s `TxGraph`), on `std` builds too. These hash
//! with `ahash` using fixed, publicly known keys unless `ahash`'s `runtime-rng` or
//! `compile-time-rng` feature is enabled somewhere in the dependency graph. With fixed keys, a
//! malicious chain source can send transactions crafted to collide, making insertion
//! quadratic. To randomize the keys, depend on
//! `ahash = { version = "0.8", features = ["runtime-rng"] }` (or `compile-time-rng`) yourself.
//! crate's hash collections (e.g. `bdk_chain`'s `TxGraph`), on `std` builds too. These hash with
//! `ahash` using fixed, publicly known keys unless `ahash`'s `runtime-rng` or `compile-time-rng`
//! feature is enabled somewhere in the dependency graph. With fixed keys, a malicious chain
//! source can send transactions crafted to collide, making insertion quadratic. To randomize the
//! keys, depend on `ahash = { version = "0.8", features = ["runtime-rng"] }` (or
//! `compile-time-rng`) yourself.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

design: Master #2405 (f97885f) made this same reflow, so this hunk is redundant now. Drop it from 40f4cd5 when rebasing; it doesn't belong in a testenv fix commit anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebasing onto master dropped the changes.

Comment thread crates/testenv/src/lib.rs Outdated
Comment on lines +3 to +23
//! Testing utilities for building and testing Bitcoin applications against a local
//! regtest network. The struct [`TestEnv`], manages a single `bitcoind` node
//! with a connected `electrs` instance and drives them through common test scenarios.
//!
//! ## Features
//!
//! * **Node management** — spin up and configure `bitcoind` and `electrs` via [`TestEnv`] and
//! [`Config`]. The default configuration enables HTTP on `electrsd`, as required by `bdk_esplora`
//! tests.
//! * **Block mining** — generate blocks with [`TestEnv::mine_blocks`], or shape individual blocks
//! with [`TestEnv::mine_block`] and [`MineParams`] to produce empty blocks, set custom
//! timestamps, or choose the coinbase output script.
//! * **Chain reorganizations** — fork and reorg the chain with [`TestEnv::invalidate_blocks`],
//! [`TestEnv::reorg`], and [`TestEnv::reorg_empty_blocks`] to exercise how an application handles
//! conflicting chain states.
//! * **Transactions & sync** — broadcast via Bitcoin Core RPC ([`TestEnv::send`]) and block on the
//! Electrum server with [`TestEnv::wait_until_electrum_tip_syncs_with_bitcoind`],
//! [`TestEnv::wait_until_electrum_sees_block`], and [`TestEnv::wait_until_electrum_sees_txid`] to
//! keep components in sync during tests.
//! * **Transaction-graph templates** — build deterministic transaction histories and conflict
//! scenarios with the [`tx_template`] module, without a running node.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The Features list uses em dashes, but doc comments are kept ASCII in this repo. It also repeats most method names, and line 4 has a stray comma ("The struct [TestEnv], manages"). A shorter ASCII version:

Suggested change
//! Testing utilities for building and testing Bitcoin applications against a local
//! regtest network. The struct [`TestEnv`], manages a single `bitcoind` node
//! with a connected `electrs` instance and drives them through common test scenarios.
//!
//! ## Features
//!
//! * **Node management** — spin up and configure `bitcoind` and `electrs` via [`TestEnv`] and
//! [`Config`]. The default configuration enables HTTP on `electrsd`, as required by `bdk_esplora`
//! tests.
//! * **Block mining** — generate blocks with [`TestEnv::mine_blocks`], or shape individual blocks
//! with [`TestEnv::mine_block`] and [`MineParams`] to produce empty blocks, set custom
//! timestamps, or choose the coinbase output script.
//! * **Chain reorganizations** — fork and reorg the chain with [`TestEnv::invalidate_blocks`],
//! [`TestEnv::reorg`], and [`TestEnv::reorg_empty_blocks`] to exercise how an application handles
//! conflicting chain states.
//! * **Transactions & sync** — broadcast via Bitcoin Core RPC ([`TestEnv::send`]) and block on the
//! Electrum server with [`TestEnv::wait_until_electrum_tip_syncs_with_bitcoind`],
//! [`TestEnv::wait_until_electrum_sees_block`], and [`TestEnv::wait_until_electrum_sees_txid`] to
//! keep components in sync during tests.
//! * **Transaction-graph templates** — build deterministic transaction histories and conflict
//! scenarios with the [`tx_template`] module, without a running node.
//! Testing utilities for building and testing Bitcoin applications against a local
//! regtest network.
//!
//! * [`TestEnv`] runs a `bitcoind` node with a connected `electrs` (HTTP enabled, for
//! `bdk_esplora`), and mines blocks, reorgs the chain, broadcasts transactions and waits for
//! `electrs` to catch up.
//! * [`tx_template`] builds deterministic transaction graphs, without a running node.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied. Thank you.

Comment thread crates/testenv/src/tx_template.rs Outdated
Comment on lines +69 to +89
impl<A> Default for TxTemplate<A> {
fn default() -> Self {
Self {
tx_name: "",
inputs: Vec::new(),
outputs: Vec::new(),
anchors: Vec::new(),
last_seen: None,
assume_canonical: false,
}
}
}

impl<A> TxTemplate<A> {
/// Create a new template with a name.
pub fn new(tx_name: &'static str) -> Self {
Self {
tx_name,
..Default::default()
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

design: Drop the Default impl. Only new uses it. It's left over from the struct-literal style (..Default::default()) that the builder replaced. It also lets callers build a template whose tx_name is "", so two such templates hit the duplicate-name panic. Without it, every template has to be named:

Suggested change
impl<A> Default for TxTemplate<A> {
fn default() -> Self {
Self {
tx_name: "",
inputs: Vec::new(),
outputs: Vec::new(),
anchors: Vec::new(),
last_seen: None,
assume_canonical: false,
}
}
}
impl<A> TxTemplate<A> {
/// Create a new template with a name.
pub fn new(tx_name: &'static str) -> Self {
Self {
tx_name,
..Default::default()
}
}
impl<A> TxTemplate<A> {
/// Create a new template with a name.
pub fn new(tx_name: &'static str) -> Self {
Self {
tx_name,
inputs: Vec::new(),
outputs: Vec::new(),
anchors: Vec::new(),
last_seen: None,
assume_canonical: false,
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped. Also makes empty tx_name no longer representable.

- Move `TxTemplate`, `TxInTemplate`, `TxOutTemplate`, `init_graph`
 `new_tx`, `genesis_block_id`, `tip_block_id` and `spk_at_index` into
 `bdk_testenv`.
- Replace `TxTemplate`'s borrowed `&'a [T]` fields with owned `Vec`s and
add a builder API.
- Update `tip_block_id` to height.
- `bdk_testenv` enables the `miniscript` feature in `bdk_chain`. Because
`bdk_chain` has a dev-dependency on `bdk_testenv`, all `bdk_chain` test
and benchmark builds run with `miniscript` enabled. This includes CI's
 `cargo build --workspace --all-targets --no-default-features` step,
 rendering the `#![cfg(feature = "miniscript")]` gates in
`crates/chain/tests/*` always true during these runs.
- Derive output script pubkeys in `init_graph` from the indices a
  template actually uses, replacing the static loop that pre-populated
  the `SpkTxOutIndex`. This lifts the 0-9 output limit and stops
  generating spks that are never used.
- Derive `TxInTemplate::Bogus` prevouts from the template and input
  index instead of a random string, so graphs built from templates are
  reproducible across runs.
- Remove the `changeset!` macro.
- Drop redundant `Clone` bound on `init_graph`.
The benchmark constructed txs with `TxIn::default()`. Because a default
`TxIn` has a null previous output, these txs were classified as coinbases.
When combined with recorded `seen_at` timestamps, this broke canonicalization:

- Since bitcoindevkit#2372, canonicalization skips coinbases with a `last_seen`
 timestamp. This resulted in 0 canonical txs and a balance assertion
 failure (`0 SAT` vs expected `21000 SAT`).
- Prior to bitcoindevkit#2372, this invalid test data triggered a `debug_assert!`.

Fix this by assigning each benchmark transaction a distinct, non-null
`previous_output`.
- Add crate-level docs covering `TestEnv`, block mining, reorgs and the
transaction-graph templates, and document all macros, functions and
constants in `utils`.
- Add an explicit warning that `DESCRIPTORS` embeds well-known test
keys and must never be used on mainnet.
- Problem: `TxTemplateEnv::txids` silently overwrote txids when multiple
  templates shared a `tx_name` including default unnamed templates where
  `tx_name` is empty. This caused `PrevTx` references to bind to the
  wrong transactions.
- fix: Panic on `tx_name` collisions during `init_graph` initiali-
zation to ensure stricter safety in `init_graph` as a public API.
@tvpeter
tvpeter force-pushed the refactor/mv-txtemplate-to-testenv branch from 40f4cd5 to 7088e03 Compare October 22, 2026 21:33
@tvpeter tvpeter mentioned this pull request Oct 24, 2026
5 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

5 participants