Skip to content

lntest: add bitcoind miner backend#10481

Merged
ziggie1984 merged 4 commits intolightningnetwork:masterfrom
bhandras:lntest-bitcoind-miner
Mar 23, 2026
Merged

lntest: add bitcoind miner backend#10481
ziggie1984 merged 4 commits intolightningnetwork:masterfrom
bhandras:lntest-bitcoind-miner

Conversation

@bhandras
Copy link
Collaborator

@bhandras bhandras commented Jan 6, 2026

Summary

This PR extends lntest with a bitcoind-backed miner implementation. Until now, lntest assumed a btcd miner (rpctest harness) even when the chain backend was bitcoind, which made it hard to exercise Bitcoin Core-specific
mining and mempool behavior.

A key motivation for this work is enabling lntest to support mining and validating v3 packages (and other Core policy/package-relay behaviors) in integration tests. Those workflows are most realistically exercised with Bitcoin Core as the miner.

What’s included

  • New miner abstraction in lntest/miner with two concrete backends:
    • btcd backend (wrapping existing rpctest.Harness usage)
    • bitcoind backend (spawns a local bitcoind on regtest and implements the miner APIs needed by lntest)
  • Harness updates to avoid btcd-only assumptions:
    • Miner lifecycle is routed through the backend interface.
    • Miner RPC calls (e.g. raw tx submission) go through the miner backend.
    • Funding-shim output index handling is made robust across backends.
  • Test runner support:
    • Add -minerbackend itest flag so the miner backend can be selected independently from the chain backend.
    • Add a build-tag default for the miner backend.
  • CI coverage:
    • Add a CI itest matrix entry that runs with backend=bitcoind and minerbackend=bitcoind to ensure we exercise the new path.

Why this matters for v3 packages

Package relay / v3 transaction workflows are fundamentally Bitcoin Core policy and mining driven. Having lntest able to:

  • talk to a Core-backed miner,
  • submit transactions via Core,
  • mine blocks via Core,

is a prerequisite for writing reliable integration coverage around v3 package construction, acceptance, and mining behavior.

How to run

Locally:

  • make itest backend=bitcoind minerbackend=bitcoind

CI will run an additional job (matrix entry) with the same arguments.

Notes

  • Default behavior remains unchanged (btcd miner unless explicitly selected).
  • make lint passes.

@gemini-code-assist
Copy link

Summary of Changes

Hello @bhandras, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the lntest framework by introducing a bitcoind-backed miner implementation. Previously, lntest relied solely on btcd for mining, even when bitcoind was used as the chain backend. This change provides a more realistic testing environment for Bitcoin Core-specific behaviors, such as those related to v3 transaction packages and mempool policies. By abstracting the miner backend, the framework becomes more flexible and robust, enabling comprehensive integration testing for advanced Bitcoin features.

Highlights

  • Bitcoind Miner Backend: Introduced a new bitcoind-backed miner implementation for lntest, allowing integration tests to realistically exercise Bitcoin Core-specific mining and mempool behavior, especially for v3 packages.
  • Miner Abstraction: A new lntest/miner abstraction was added, supporting both btcd and bitcoind as concrete miner backends, enhancing flexibility and future extensibility.
  • Harness Updates: The lntest.Harness was updated to route miner lifecycle and RPC calls through the new MinerBackend interface, removing btcd-only assumptions and standardizing interactions.
  • Configurable Miner Backend: Added a -minerbackend flag for integration tests, enabling independent selection of the miner backend from the chain backend, with build-tag defaults for convenience.
  • CI Coverage: Expanded the CI matrix to include tests with backend=bitcoind and minerbackend=bitcoind to ensure comprehensive coverage of the new bitcoind miner path.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/main.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a bitcoind-backed miner implementation for lntest, which is a significant and well-executed enhancement. It allows for more realistic integration testing of Bitcoin Core-specific behaviors. The new miner abstraction is clean, and the refactoring of existing code to use it is thorough. My review includes one suggestion to improve maintainability by reducing code duplication in one of the modified files.

@bhandras bhandras force-pushed the lntest-bitcoind-miner branch from 524274d to 0872272 Compare January 6, 2026 15:32
@bhandras bhandras marked this pull request as draft January 6, 2026 17:35
@bhandras bhandras force-pushed the lntest-bitcoind-miner branch from 2a17ef9 to a8a24ed Compare January 6, 2026 17:39
@saubyk saubyk added this to v0.21 Jan 6, 2026
@saubyk saubyk moved this to In progress in v0.21 Jan 6, 2026
@bhandras bhandras force-pushed the lntest-bitcoind-miner branch from a8a24ed to d2b5d3c Compare January 6, 2026 19:09
@bhandras bhandras marked this pull request as ready for review January 6, 2026 20:55
Comment on lines +96 to +97
// miner while there are already blocks present, which will take a bit
// longer than the 1 second the default settings amount to. Doubling
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: the sentence “which will take a bit longer than the 1 second the default settings amount to” is a bit unclear.
Maybe rephrase to something like “which can take longer than the default 1-second timeout”.

Comment on lines +241 to +242
// 100 + numMatureOutputs
// blocks.
Copy link
Contributor

@MPins MPins Jan 15, 2026

Choose a reason for hiding this comment

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

Nit: minor wording/style suggestion — consider writing “we mine 100 + numMatureOutputs blocks” inline.

@saubyk saubyk removed this from v0.21 Mar 5, 2026
@saubyk saubyk added this to the v0.22.0 milestone Mar 5, 2026
@lightninglabs-deploy
Copy link
Collaborator

@Roasbeef: review reminder
@yyforyongyu: review reminder
@bhandras, remember to re-request review from reviewers when ready

@saubyk saubyk modified the milestones: v0.22.0, v0.21.0 Mar 17, 2026
@saubyk saubyk added this to v0.21 Mar 17, 2026
@saubyk saubyk moved this to In review in v0.21 Mar 17, 2026
@saubyk saubyk requested review from ellemouton and ziggie1984 and removed request for Roasbeef and yyforyongyu March 17, 2026 16:44
Copy link
Collaborator

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

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

niiiice!

Comment on lines +217 to +218

if err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

dont think it is possible to get here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah good catch, retry loop is now cleaned up a bit.

return fmt.Errorf("unable to create wallet: %w", err)
}

if setupChain {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit:

if !setupChain {
          return
}

bloop blaap

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Indeed! done

ITEST_FLAGS += -dbbackend=$(dbbackend)
endif

# Select miner backend independently from chain backend.
Copy link
Collaborator

Choose a reason for hiding this comment

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

noice

Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe add a comment that the default is always btcd ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added that note here too: the make comment now says the miner backend selection is independent from the chain backend and still defaults to btcd.

feeRate btcutil.Amount) (*wire.MsgTx, error) {

// Extract destinations (address -> amount) in BTC.
destinations := make(map[string]json.RawMessage, len(outputs))
Copy link
Collaborator

Choose a reason for hiding this comment

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

think there is risk of override here if an output has same address

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah yes this was pretty bad, some early claude creation. Fixed it up a bit.


// Harness is the btcd-specific harness. This is only set when using
// the btcd backend, and is nil when using bitcoind.
*rpctest.Harness
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we make this named & unexported now? else it exposes all btcd backend calls even when nil

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@bhandras bhandras force-pushed the lntest-bitcoind-miner branch 3 times, most recently from 7974fba to f242f96 Compare March 18, 2026 13:47
@bhandras bhandras force-pushed the lntest-bitcoind-miner branch from f242f96 to 052ec49 Compare March 18, 2026 16:18
Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

Thank you for adding this functionality.

Had some suggestions and minor comments.

)

return err
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

ConnectMiner: addnode onetry doesn't wait for peer establishment

addnode onetry returns immediately — the TCP handshake may not complete for another few hundred milliseconds. By contrast, BitcoindBackendConfig.ConnectMiner in bitcoind_common.go (added in this same PR) correctly polls getpeerinfo until the peer appears.

In SpawnTempMiner, if both miners are at the same genesis height (temp miner started with setupChain=false), the subsequent AssertMinerBlockHeightDelta(0) trivially passes without any actual P2P connection having succeeded. The DisconnectMiner that follows may then fail or silently no-op on a peer that was never connected, leaving the miners accidentally in sync during the reorg test.

Could you mirror the wait.NoError(getpeerinfo, ...) polling pattern from BitcoindBackendConfig.ConnectMiner here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. The bitcoind miner path now waits for the peer to show up in getpeerinfo after addnode onetry, matching the polling we already use for the bitcoind chain backend. That closes the gap where the height check could pass before the p2p connection was actually established.

height, err := b.rpcClient.GetBlockCount()
if err != nil {
return nil, 0, err
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

GetBestBlock: two RPC calls are not atomic

GetBestBlockHash and GetBlockCount are separate round-trips. If a block is mined between the two calls the function returns the hash of block N but the height of block N+1. The btcd backend gets both in a single atomic call.

getblockchaininfo returns bestblockhash and blocks in one response and would fix this. In practice this race is unlikely in tests, but it's easy to fix.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. GetBestBlock now uses getblockchaininfo so the hash and height come back from a single RPC response instead of two separate round-trips.

err = rpctest.JoinNodes(nodeSlice, rpctest.Blocks)
require.NoError(err, "unable to join node on blocks")
} else {
h.AssertMinerBlockHeightDelta(tempMiner, 0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Redundant AssertMinerBlockHeightDelta call

When the bitcoind backend is active, h.harness and tempMiner.harness are both nil, so the else branch here runs AssertMinerBlockHeightDelta(tempMiner, 0), and then line 717 runs it again unconditionally. The inner call (this line) is redundant — the outer one on line 717 is sufficient.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. I removed the inner AssertMinerBlockHeightDelta(tempMiner, 0) and keep only the unconditional check after the backend-specific sync path.

//
// For the btcd backend, these parameters map to rpctest.Harness.SetUp.
// For other backends, these parameters can be ignored.
Start(setupChain bool, numMatureOutputs uint32) error
Copy link
Collaborator

Choose a reason for hiding this comment

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

The comment says the parameters can be ignored by non-btcd backends, but the bitcoind implementation actually uses both — setupChain (line 230 of bitcoind_miner.go) and numMatureOutputs (line 272, mines 100 + numMatureOutputs blocks). The comment was likely written before the bitcoind backend was fully implemented and never updated. Suggest replacing with a description of what the parameters mean rather than how each backend handles them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. I updated the Start comment to describe what setupChain and numMatureOutputs mean instead of implying non-btcd backends can ignore them.

// for compatibility with existing patterns in lntest.
//nolint:containedctx
runCtx context.Context
cancel context.CancelFunc
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Both backends create a child context with context.WithCancel but never actually read runCtx back — only cancel() is called in Stop(). The context is stored purely so HarnessMiner can copy it and pass it to temp miner constructors in SpawnTempMiner.

A simpler design would be to not store runCtx in the backends at all, have HarnessMiner hold the parent context directly, and let each backend derive its own cancel internally. Not blocking, just worth considering in a follow-up cleanup.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agree this can be simplified further. I left it out of this PR to keep the review fixes scoped to behavior and documentation changes, but it would make sense as follow-up cleanup.

// NewMinerWithConfig creates a new miner with the specified configuration.
// The Backend field in the config determines which backend to use ("btcd" or
// "bitcoind").
func NewMinerWithConfig(ctxt context.Context, t *testing.T,
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: The WithConfig suffix feels redundant on a constructor — if config is needed, you just pass it. NewMinerWithConfig could simply be NewMiner, with the current no-arg NewMiner becoming NewBtcdMiner for symmetry with NewBitcoindMiner.

Related: newBitcoindMiner is a trivial wrapper that just builds a MinerConfig and immediately delegates to newBitcoindMinerWithConfig. The private WithConfig layer could be collapsed — one private constructor per backend that takes a config, no intermediate wrapper needed. Only two call sites outside the package so the rename is a small change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree there is room to simplify the constructor naming. I left that out of this PR so I wouldn't mix a broader API rename into the bitcoind miner change set, but I'm happy to do it as follow-up cleanup.


// GetRawTransactionVerbose makes a RPC call to the miner's
// GetRawTransactionVerbose and asserts.
func (h *HarnessMiner) GetRawTransactionVerbose(
Copy link
Collaborator

Choose a reason for hiding this comment

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

does the getrawtransaction bitcoind call not support like some function arguments to return the transaction in verbose mode ? I remember bitcoin-cli getrawtransaction 1 txid

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, good point. The shared rpcclient supports the verbose getrawtransaction call for bitcoind as well, so I wired GetRawTransactionVerbose through the miner backend instead of special-casing btcd.

ITEST_FLAGS += -dbbackend=$(dbbackend)
endif

# Select miner backend independently from chain backend.
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe add a comment that the default is always btcd ?

)

// minerBackendFlag selects which miner backend to use. If not set, a
// default miner is used.
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe add a comment which the default miner is, its btcd ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added that clarification in the flag comment as well. If -minerbackend is not set, the default miner remains btcd.

- name: bitcoind
args: backend=bitcoind cover=1
- name: bitcoind-miner
args: backend=bitcoind minerbackend=bitcoind cover=1
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit (non-blocking): The bitcoind-miner CI job still builds and passes -btcdexec even though minerbackend=bitcoind never uses it. The btcd binary is built and linked unconditionally for all itest runs regardless of miner backend. Could be worth skipping the btcd build step when minerbackend=bitcoind to save a bit of CI time, but fine to address in a follow-up.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. I left the btcd build optimization out of this PR because it changes the itest job shape rather than the miner behavior itself, but it seems like a reasonable follow-up CI cleanup.

@bhandras bhandras force-pushed the lntest-bitcoind-miner branch from 052ec49 to 53831f0 Compare March 23, 2026 14:04
Introduce a miner backend interface and implement both btcd and
bitcoind-backed miners for lntest. This lets the harness drive mining
and mempool assertions using bitcoind in addition to btcd.

Also update harness helpers to avoid btcd-only assumptions (network
params, raw tx submission, funding shim output index lookup) and make
bitcoind miner disconnects more reliable.
Add an itest flag to choose the miner backend (btcd vs bitcoind) and
provide a build-tag default so that `-tags=bitcoind` naturally uses a
bitcoind miner.

Wire the flag through `make testing_flags.mk` so callers can set
`minerbackend=bitcoind` independently of the chain backend.
Add a basic itest matrix entry that sets minerbackend=bitcoind alongside
backend=bitcoind, ensuring CI covers the bitcoind miner path.
@bhandras bhandras force-pushed the lntest-bitcoind-miner branch from 53831f0 to ad62f5f Compare March 23, 2026 14:47
@ziggie1984 ziggie1984 merged commit db765a7 into lightningnetwork:master Mar 23, 2026
36 of 37 checks passed
@github-project-automation github-project-automation bot moved this from In review to Done in v0.21 Mar 23, 2026
@bhandras bhandras deleted the lntest-bitcoind-miner branch March 23, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants