Skip to content

chainntnfs: add a pkscript notifier stream#10807

Open
hieblmi wants to merge 10 commits into
lightningnetwork:masterfrom
hieblmi:address-notifier
Open

chainntnfs: add a pkscript notifier stream#10807
hieblmi wants to merge 10 commits into
lightningnetwork:masterfrom
hieblmi:address-notifier

Conversation

@hieblmi
Copy link
Copy Markdown
Collaborator

@hieblmi hieblmi commented May 15, 2026

This PR introduces chainntnfs.PkScriptNotifier, an interface that allows for flexible listening of confirms and spends over a long-lived stream.

  • Watch multiple pkScripts on one stream.
  • Add and remove pkScripts dynamically.
  • Receive confirmation notifications at a requested confirmation depth.
  • Receive spend notifications for matched watched outputs.
  • Optionally receive partial confirmation progress updates.
  • Optionally include raw transaction and raw block data.
  • Request historical backfill from a specific height.
  • Receive historical scan completion/failure events.
  • Handle reorgs through Disconnected=true invalidation notifications.
  • Enforce resource bounds for registrations, watch counts, queued notifications, and historical scans.

Interfaces

type PkScriptNotifier interface {
	// RegisterPkScriptNotifier creates a new pkScript notification stream.
	// Scripts are added explicitly with AddPkScripts on the returned
	// registration.
	RegisterPkScriptNotifier() (*PkScriptNotificationRegistration, error)
}
type PkScriptNotificationRegistration struct {
	notifications chan *PkScriptNotification

	// Notifications is a receive-only channel that will be sent upon for
	// each matched confirmation/spend event.
	//
	// NOTE: This channel must be buffered.
	Notifications <-chan *PkScriptNotification

	// AddPkScripts adds scripts to this notification stream and returns
	// metadata for any historical scan queued by the mutation. Scripts
	// already watched by this stream are ignored and keep their original
	// options.
	AddPkScripts func(pkScripts [][]byte,
		opts ...NotifierOption) (*PkScriptAddResult, error)

	// RemovePkScripts removes scripts from this notification stream.
	RemovePkScripts func(pkScripts [][]byte) error

	// Err returns the terminal error that closed the notification stream, if
	// known. It returns nil while the stream is active.
	Err func() error

	// Cancel is a closure that should be executed by the caller in the case
	// that they wish to abandon their registered pkScript notification.
	Cancel func()
}
// PkScriptNotification describes a spend or confirmation event for a watched
// pkScript.
type PkScriptNotification struct {
	Type      PkScriptNotificationType
	Height    uint32
	BlockHash *chainhash.Hash
	TxHash    *chainhash.Hash

	// TxIndex is the index of the relevant transaction in the block that
	// mined it. For confirmations, this is the funding transaction's index
	// in the UTXO block. For spends, this is the spending transaction's index
	// in the spend block.
	TxIndex uint32

	// InputIndex is the index of the input that spends the watched UTXO. It
	// is only set for spend notifications.
	InputIndex       uint32
	NumConfirmations uint32
	RequiredConfs    uint32
	Disconnected     bool
	UTXO             *PkScriptUTXO

	// Tx is the transaction relevant to this notification if the caller
	// requested that full transactions be included. For confirmation
	// notifications, this is the transaction that created the watched UTXO.
	// For spend notifications, this is the transaction that spent it.
	Tx *wire.MsgTx

	// Block is the block relevant to this notification if the caller
	// requested that full blocks be included. For confirmation
	// notifications, this is the block at which the watched UTXO reached
	// the required confirmation depth. For spend notifications, this is the
	// block containing the spend.
	Block *wire.MsgBlock

	// HistoricalScan, if non-nil, contains lifecycle details for a
	// historical pkScript scan.
	HistoricalScan *PkScriptHistoricalScan
}

Examples

13819bd

@hieblmi hieblmi self-assigned this May 15, 2026
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, 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 implements a robust, stream-based interface for monitoring Bitcoin script activity. By moving away from single-target notifications to a script-based subscription model, it enables more efficient and flexible chain monitoring. The changes include backend support for bitcoind, btcd, and neutrino, along with a comprehensive gRPC API for external clients to leverage these new capabilities.

Highlights

  • New PkScript Notifier Interface: Introduced chainntnfs.PkScriptNotifier and RegisterPkScriptNotifier to allow for flexible, stream-based monitoring of multiple pkScripts for confirmations and spends.
  • Dynamic Script Management: Added support for dynamically adding and removing pkScripts from an active notification stream, including optional historical backfilling.
  • RPC API Expansion: Exposed the new functionality via a new bidirectional gRPC stream RegisterPkScriptNtfn in the chainrpc subserver, complete with support for historical scans and reorg invalidations.
  • Resource Management: Implemented strict resource bounds for registrations, watch counts, and queued notifications to ensure system stability.
New Features

🧠 You can now enable Memory (public preview) 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.

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 the 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 counterproductive. 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.

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
Copy Markdown

@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 new PkScriptNotifier interface and implementation, allowing clients to watch for confirmation and spend events on specific output scripts. The changes include support for historical scans, resource limiting, and a new bidirectional gRPC stream in chainrpc. The reviewer suggested refactoring the duplicated historical dispatch logic across the bitcoind, btcd, and neutrino backends into a shared helper and noted that an error in completeHistoricalPkScriptDispatchLocked should be explicitly handled.

Comment on lines +944 to +1031
// queueHistoricalPkScriptDispatch reserves capacity and queues a historical
// pkScript scan for the bitcoind backend.
func (b *BitcoindNotifier) queueHistoricalPkScriptDispatch(
msg *chainntnfs.HistoricalPkScriptDispatch) error {

if msg == nil {
return nil
}

err := b.reserveHistoricalPkScriptDispatchSlot()
if err != nil {
return err
}

return b.queueReservedHistoricalPkScriptDispatch(msg)
}

// reserveHistoricalPkScriptDispatchSlot reserves one pending historical pkScript
// scan slot.
func (b *BitcoindNotifier) reserveHistoricalPkScriptDispatchSlot() error {
select {
case <-b.quit:
return chainntnfs.ErrChainNotifierShuttingDown
default:
}

select {
case b.historicalPkScriptDispatchSlots <- struct{}{}:
return nil

case <-b.quit:
return chainntnfs.ErrChainNotifierShuttingDown

default:
return fmt.Errorf("%w: pending scans %d exceeds limit %d",
chainntnfs.ErrTooManyHistoricalPkScriptScans,
len(b.historicalPkScriptDispatchSlots),
chainntnfs.MaxPkScriptHistoricalDispatchQueueSize)
}
}

// releaseHistoricalPkScriptDispatchSlot releases one pending historical pkScript
// scan slot.
func (b *BitcoindNotifier) releaseHistoricalPkScriptDispatchSlot() {
select {
case <-b.historicalPkScriptDispatchSlots:
default:
}
}

// queueReservedHistoricalPkScriptDispatch queues a dispatch after capacity has
// already been reserved.
func (b *BitcoindNotifier) queueReservedHistoricalPkScriptDispatch(
msg *chainntnfs.HistoricalPkScriptDispatch) error {

select {
case b.historicalPkScriptDispatches <- msg:
return nil

case <-b.quit:
b.releaseHistoricalPkScriptDispatchSlot()

return chainntnfs.ErrChainNotifierShuttingDown
}
}

// historicalPkScriptDispatcher serially executes queued historical pkScript
// scans.
func (b *BitcoindNotifier) historicalPkScriptDispatcher() {
defer b.wg.Done()

for {
select {
case msg := <-b.historicalPkScriptDispatches:
err := b.historicalPkScriptDispatch(msg)
b.releaseHistoricalPkScriptDispatchSlot()
if err != nil {
chainntnfs.Log.Errorf("Historical pkScript dispatch "+
"for sub=%d within range %d-%d failed: %v",
msg.SubscriptionID, msg.StartHeight,
msg.EndHeight, err)
}

case <-b.quit:
return
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

There's significant code duplication for the historical pkScript dispatch logic across bitcoindnotify, btcdnotify, and neutrinonotify backends. The functions queueHistoricalPkScriptDispatch, reserveHistoricalPkScriptDispatchSlot, releaseHistoricalPkScriptDispatchSlot, queueReservedHistoricalPkScriptDispatch, and historicalPkScriptDispatcher are nearly identical in all three files.

Consider refactoring this logic into a shared helper struct that can be embedded by each notifier. This would improve maintainability and reduce redundancy.

Comment thread chainntnfs/pkscriptnotifier.go Outdated
Comment on lines +2228 to +2230
_ = n.completeHistoricalPkScriptDispatchLocked(
sub, dispatch, completedHeight, err,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The error returned by completeHistoricalPkScriptDispatchLocked is being ignored. This function can return an error if sending the notification fails, and ignoring it could mask problems. The error should be handled, for example by logging it.

Suggested change
_ = n.completeHistoricalPkScriptDispatchLocked(
sub, dispatch, completedHeight, err,
)
if complErr := n.completeHistoricalPkScriptDispatchLocked(
sub, dispatch, completedHeight, err,
); complErr != nil {
Log.Errorf("Unable to send historical pkScript dispatch completion for sub=%d: %v", dispatch.SubscriptionID, complErr)
}

@github-actions github-actions Bot added the severity-critical Requires expert review - security/consensus critical label May 15, 2026
@github-actions
Copy link
Copy Markdown

🔴 PR Severity: CRITICAL

🤖 Automated severity classification | 16 non-test files | ~4,938 lines changed (excluding tests and auto-generated)

🟠 High (9 files — base severity)
  • chainntnfs/bitcoindnotify/bitcoind.go - Chain notification backend (bitcoind), part of chainntnfs/*
  • chainntnfs/btcdnotify/btcd.go - Chain notification backend (btcd), part of chainntnfs/*
  • chainntnfs/interface.go - Core chain notifications interface, part of chainntnfs/*
  • chainntnfs/neutrinonotify/neutrino.go - Chain notification backend (neutrino), part of chainntnfs/*
  • chainntnfs/pkscriptnotifier.go - New pkscript notifier (2379 lines, new file), part of chainntnfs/*
  • chainntnfs/txnotifier.go - Transaction notifier, part of chainntnfs/*
  • lnrpc/chainrpc/chain_server.go - Chain RPC server (587 lines added), part of lnrpc/*
  • lnrpc/chainrpc/config_active.go - Chain RPC configuration, part of lnrpc/*
  • lnrpc/chainrpc/driver.go - Chain RPC driver, part of lnrpc/*
🟡 Medium (5 files)
  • chainreg/no_chain_backend.go - Chain registry backend stub
  • lnrpc/chainrpc/chainnotifier.proto - Protocol buffer API definition (*.proto)
  • lnrpc/chainrpc/chainnotifier.swagger.json - API swagger spec
  • lnrpc/chainrpc/chainnotifier.yaml - API YAML config
  • subrpcserver_config.go - Sub-RPC server configuration
🟢 Low (2 files — excluded from count)
  • docs/pkscriptnotifier.md - Documentation
  • docs/release-notes/release-notes-0.22.0.md - Release notes
Excluded from counting (test/auto-generated files)
  • chainntnfs/neutrinonotify/neutrino_test.go — test file
  • chainntnfs/pkscriptnotifier_internal_test.go — test file
  • chainntnfs/pkscriptnotifier_test.go — test file
  • chainntnfs/test/test_interface.go — test support directory
  • itest/list_on_test.go — itest/*
  • itest/lnd_chain_notifier_pkscript_test.go — itest/*
  • lnrpc/chainrpc/chain_server_test.go — test file
  • lnrpc/chainrpc/chainnotifier.pb.go — auto-generated (*.pb.go)
  • lnrpc/chainrpc/chainnotifier.pb.gw.go — auto-generated (*.pb.gw.go)
  • lnrpc/chainrpc/chainnotifier_grpc.pb.go — auto-generated (*.pb.go)
  • lntest/rpc/chain_notifier.go — lntest/*

Analysis

Base severity: HIGH — The PR touches chainntnfs/* (chain notification backends and interfaces) and lnrpc/* (RPC/API definitions), both classified as HIGH severity packages.

Bumped to CRITICAL — The PR has approximately 4,938 lines changed in non-test, non-auto-generated files, well exceeding the 500-line threshold that triggers a one-level severity bump.

The core of this PR introduces a new pkscriptnotifier.go (2,379 lines) implementing a PkScript-based chain notification interface, along with modifications to all three chain backends (bitcoind, btcd, neutrino) to wire in the new notifier, extensive changes to chainntnfs/interface.go, and 587 lines added to the chain RPC server to expose the new functionality via gRPC. This is a substantial new feature touching critical chain notification infrastructure and requires expert review.


To override, add a severity-override-{critical,high,medium,low} label.
<!-- pr-severity-bot -->

@hieblmi hieblmi requested a review from starius May 15, 2026 13:28
@github-actions github-actions Bot added severity-critical Requires expert review - security/consensus critical and removed severity-critical Requires expert review - security/consensus critical labels May 15, 2026
hieblmi added 10 commits May 15, 2026 17:25
Add a TxNotifier-backed stream for watching pkScripts with confirmation, spend, partial confirmation, historical scan, and reorg notifications.
Support pkScript streams in bitcoind, btcd, and neutrino, including historical scan dispatch and neutrino compact-filter updates.
Expose RegisterPkScriptNtfn with register/add/remove mutations, acknowledgements, historical scan events, and pkScript notification conversion.
Add shared notifier tests and RPC integration scenarios for historical scans, removals, confirmation modes, multi-stream watching, validation, and reorg handling.
Document RegisterPkScriptNtfn capabilities, historical scan semantics, notification behavior, resource bounds, and backend support.
Show how to register a pkScript notification stream, defer cancellation, add scripts with all notifier options, and handle stream events.
Show how clients should apply Disconnected pkScript notifications for confirmation updates, final confirmations, and spends.
Mention the new RegisterPkScriptNtfn stream and supporting documentation in the 0.22.0 release notes.
@hieblmi hieblmi force-pushed the address-notifier branch from 0eef6cc to 5aa4f31 Compare May 15, 2026 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

notifications severity-critical Requires expert review - security/consensus critical

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant