We add a test to demonstrate that the second chance penalty can be
effective. In this test an initially cheap route is attempted but errors
with a new returned channel policy. After applying that policy we should
pick the cheaper route again, but the second-chance penalty will prevent
us from doing so. Instead an alternative route succeeds.
Previously our mission control would not punish a route if a policy
failure was provided to us, in the form of a "second chance". For
various reasons outlined in
https://github.com/lightningnetwork/lnd/issues/6883 we may not want to
blindly retry a route if that case is true.
With this commit we change the effect of the second chance to instead
apply penalties with half the value. This way we do not exclude this
route from future attempts and also encourage alternative route
selection.
Note the performance improvement from dropping the unnecessary address
load on the SQL backend and letting the kvdb in-memory graph cache
serve autopilot's scoring traversal.
ForEachNodesChannels is a topology traversal: its callers only need
the node identity plus channel edges. After removing address loading
from ForEachNodeCached, constructing a Node for this callback is
misleading because Addrs is either unused or empty.
Pass NodeID directly through the interface and update the scoring
and simple graph callers to use that pubkey. This keeps the
address-bearing Node interface on ForEachNode, where autopilot
gathers connectable candidates and their addresses before dialing.
Add a new chanstate package containing the Store interface plus a
package logger. The interface mirrors the public surface of
*channeldb.ChannelStateDB so the compile-time assertion
var _ ChannelStore = (*channeldb.ChannelStateDB)(nil)
No consumer migrates in this commit.
All four call sites pass nil for tx today (server.go, two in
channeldb/db_test.go, funding/manager_test.go). The internal
channelScanner(nil, selector) call inside FetchChannelByID is preserved
verbatim, so runtime behavior is unchanged.
This is a prerequisite for the upcoming chanstate.ChannelStore
interface: keeping the parameter would leak kvdb into a domain
interface.
Reject incoming OpenChannel messages where push_msat exceeds
1000 * funding_satoshis, as required by BOLT-02. The existing RejectPush
flag only gates on push_msat > 0 and does not cover the spec bound.
An over-sized push is eventually caught downstream in reservation.go when
theirBalance = capacity - fee - push_msat goes negative and
ErrFunderBalanceDust is returned. Rejecting it up front produces a
clearer, spec-aligned error and avoids the chanacceptor and commitment
type negotiation round-trips for a channel we will refuse anyway.
ForEachNodeCached is now only used for topology-oriented traversal,
so the address-loading option forced one autopilot scoring path to
bypass the in-memory graph cache for data it did not consume. Remove
the withAddrs parameter and the associated SQL/KV address plumbing
so cached node iteration can consistently use the graph cache when
it is loaded.
Autopilot still requires peer addresses before opening channels.
That filtering remains in Agent.openChans via ForEachNode, where the
selected candidates' addresses are collected for ConnectToPeer. The
trade-off is that ForEachNodesChannels no longer excludes
addressless nodes from graph-wide scoring inputs such as median
channel size or centrality, which also feed lncli getnetworkinfo
statistics like graph diameter. In practice the only addressless
nodes our local view tends to know about are nodes with no public
channels (e.g. our own node or peers we share only private channels
with), so the impact on the reported stats should be negligible.
Active channel candidates remain address-filtered before dialing.
When updating the Go version in kvdb to >= 1.24, schema.go no
longer compiles due to non-constant format strings. This is
invisible until:
- a new version of kvdb is tagged and imported in consumers
- kvdb is redirected to the local copy
This commit fixes the bug.
Document the tombstone close path that #10780 wires onto sqlite/postgres,
the operator-visible iteration-cost growth that comes from leaving closed
chanBuckets on disk, and the NumForwardingPackages divergence that the
preserved forwarding-package bucket produces in PendingChannels.
Wire OptionTombstoneClosedChannels for sqlite and postgres backends in
config_builder.go. bbolt keeps the synchronous one-shot close path
unchanged.
Add UsesClosedChanTombstones() to the integration test harness so
backend-symmetric tests can skip post-close assertions about deleted
forwarding-package or revocation-log state — that state is intentionally
preserved on tombstone backends until the upcoming native-SQL
channel-state migration reclaims it. Update testWipeForwardingPackages
to honor the new predicate while still exercising the close flow on
both backend families.
Wire every reader of openChannelBucket to consult isOutpointClosed
before treating a chanKey as open. Without this commit the previous one
flips the outpoint index but FetchAllChannels and friends still surface
the channel as if it were open — that intermediate state is fine for
tests because OptionTombstoneClosedChannels stays off until the multi:
commit flips it on for sqlite/postgres, but the readers must be wired
before that lands.
Audit covers all six call sites that descend into chanBucket:
- fetchChanBucket / fetchChanBucketRw — direct lookup paths used by
Refresh, MarkBorked, and the rest of OpenChannel's read/write
methods. Single-call sites; the tx.ReadBucket(outpointBucket)
lookup is inlined into the isOutpointClosed call.
- fetchNodeChannels — per-node ForEach iteration; tx threaded
through and the outpoint-bucket lookup is hoisted above the loop
so the closed-channel check is a per-iteration map probe rather
than a tx-level bucket resolve.
- FetchPermAndTempPeers — cross-node ForEach; same hoisting pattern.
The closed peer's protected status is still established by the
historical-channel second pass that runs after the open-channel
pass.
- channelScanner — single-channel-selector iteration site reached by
FetchChannel and FetchChannelByID; outpoint-bucket lookup hoisted
inside chanScan so a single visit pays the bucket-resolve cost
once.
The redundant-close guard added in the previous commit lives in
locateOpenChannel and is unchanged here.
Tests:
- TestCloseChannelTombstoneRemovesFromOpenScans: end-to-end —
FetchAllChannels, FetchOpenChannels, and FetchPermAndTempPeers all
behave as if the closed channel is gone, while the historical pass
still marks the peer as having a closed channel.
- TestClosedChannelHiddenFromFetchChannel: channelScanner path.
- TestClosedChannelHiddenFromDirectMethods: fetchChanBucket /
fetchChanBucketRw via Refresh and MarkBorked.
Wire the tombstone close path on backends that opted in via
OptionTombstoneClosedChannels:
- ChannelStateDB.CloseChannel branches on tombstoneClosedChannels.
The default path remains closeChannelSync; closeChannelTombstone
runs on tombstone-enabled backends.
- closeChannelTombstone leaves every byte of the channel's nested
state in place — chanBucket, revocation log, per-channel
forwarding-package bucket, commitment heads — and relies on the
outpointBucket flip from outpointOpen to outpointClosed (already
performed by the shared updateClosedOutpointIndex helper) as the
authoritative closed-channel marker. The historical-channel and
close-summary archival use the same archiveClosedChannel helper as
the synchronous path so closed-channel and historical readers see
uniform records regardless of backend.
- locateOpenChannel rejects already-closed chanKeys (outpointClosed
in the index) with ErrChannelNotFound so a redundant CloseChannel
is a no-op rather than a re-archive.
Open-channel-bucket readers still surface tombstoned channels — that
audit lands in the next commit. The tests added here only assert the
writer's on-disk artefacts and the redundant-close guard.
Tests:
- TestCloseChannelTombstoneWritePath: outpoint flipped, historical
record, close summary, revlog/fwd-pkgs preserved.
- TestCloseChannelTombstoneRedundantClose: second CloseChannel
returns ErrChannelNotFound.
- TestCloseChannelSync: regression test for the synchronous path —
chanBucket and fwd-pkgs gone, outpoint flipped.
Add the option, field, and reader helper that the tombstone close path
will consume in the next commit. Nothing is wired yet:
- OptionTombstoneClosedChannels — option modifier that sets the new
Options.tombstoneClosedChannels field. Defaults to off.
- ChannelStateDB.tombstoneClosedChannels — the decision-bit, set at
construction from the option.
- isOutpointClosed(opBucket, chanKey) — decodes the indexStatus TLV
stored under outpointBucket and reports true for entries flipped to
outpointClosed by updateClosedOutpointIndex (called from both close
paths). The helper accepts the bucket directly so loop-style
callers can hoist the tx.ReadBucket(outpointBucket) lookup out of
the inner loop and pay it once per iteration set.
Reusing outpointBucket as the "logically closed" signal avoids a
dedicated tombstone bucket. The flip from outpointOpen to outpointClosed
is already performed by the existing close path, so the signal exists on
both backends — readers just need to consult it.
Behavior is unchanged. The option and helper have no callers yet; the
close-path branch and reader audit land in the next two commits.
Move the body of OpenChannel.CloseChannel into ChannelStateDB.CloseChannel
(which dispatches to a new closeChannelSync method), and split the close
logic into three free helpers:
- locateOpenChannel: descends the open-channel bucket tree and returns the
chain bucket, channel bucket, and serialized chanKey for an OpenChannel.
- updateClosedOutpointIndex: flips the outpoint index entry for a chanKey
from open to closed.
- archiveClosedChannel: writes the historical-channel record and the close
summary that survive the close.
Behavior is preserved: closeChannelSync runs the same sequence of mutations
(packager wipe, chanBucket delete, log-bucket delete, outpoint flip,
historical archive, close summary) that the inline body did, just composed
out of the new helpers. No callers, options, or readers change.
This is preparation for adding a tombstone close strategy on KV-SQL
backends; the helpers will be shared between the synchronous and tombstone
paths so historical and closed-channel records remain identical across
backends.
In this commit we add FindPath, a BFS-based shsortest-path
algorithm that finds routes through the channel graph for
onion messages. The search filters nodes by the
OnionMessage feature bits (38/39).
We also add a unit tests covering: direct neighbor routing,
multi-hop paths, feature-bit filtering, missing destination
nodes, destination without onion support, max hop limits,
cycle handling, and shortest-path selection.
choice of BFS is because there isn't any weight involve.
The TOC link for the Contributors section pointed to `#contributors`,
but GitHub generates the anchor for `# Contributors (Alphabetical
Order)` as `#contributors-alphabetical-order`, leaving the link
broken when the rendered file is viewed on GitHub. Update the TOC
to use the working anchor (matching the form already used in
release-notes-0.18.0.md).
Scaffold release-notes-0.22.0.md with the same section structure as
the 0.21.0 file so contributors have a place to land entries during
the v0.22 cycle.
waitForWalletSync used time.Tick inside the poll loop, leaking a new
goroutine on every iteration. Over 5 reorg cycles with ~300 polls each
this accumulated up to 1500 leaked goroutines, adding measurable system
load that made the 30s timeout too tight, especially when running against
a postgres backend where block-processing writes carry more overhead.
Fix the leak by using a single time.NewTicker (deferred Stop), and raise
the timeout to 2 minutes to give the neutrino P2P layer and the
address-manager transaction walk enough headroom under load.
Also improve the timeout error messages to identify which of the two
sync layers was stuck:
- Layer 1 (header/P2P): ChainIO.GetBestBlock height has not yet caught
up to the miner tip — neutrino is still fetching headers.
- Layer 2 (transaction walk): heights matched but IsSynced() never
returned true — the chain-sync notification or the address-manager
DB write (undo+redo on reorg) did not complete in time.
Add a detailed doc comment to waitForWalletSync explaining the three
pipeline stages (header sync, compact-filter/block fetch, transaction
walk) and why each stage is relevant, so a future timeout can be
diagnosed from the error message alone.
Bitcoind v30 lowered the default minrelaytxfee and incrementalrelayfee
from 1000 sat/kvB (1 sat/vB) to 100 sat/kvB. The itest suite was
written against the old defaults and the lower values cascade into:
- integer sat/vByte assertions losing precision below 1 sat/vB, and
- RBF bump thresholds that alter sweeper/bumpfee replacement timing.
Pin the old defaults in the itest bitcoind backend so the existing
tests keep passing without per-test adaptation. Running against the
new defaults is still worth doing, but that is a separate exercise
that should not be bundled with the v30 version bump.
This can be used to allow any system to send a message to the RBF chan
closer if it knows the proper service key. In the future, we can use
this to redo the msgmux.Router in terms of the new actor abstractions.
In this commit, we implement the actor.ActorBehavior interface for
StateMachine. This enables the state machine executor to be registered
as an actor, and have messages be sent to it via a unique ServiceKey
that a concrete instance will set.
In this commit, we now register the rbfCloseActor when we create the rbf
chan closer state machine. Now the RPC server no longer neesd to
traverse a series of maps and pointers (rpcServer -> server -> peer ->
activeCloseMap -> rbf chan closer) to trigger a new fee bump.
Instead, it just creates the service key that it knows that the closer
can be reached at, and sends a message to it using the returned
actorRef/router. We also hide additional details re the various methods
in play, as we only care about the type of message we expect to send and
receive.
In this commit, we create a new rbfCloseActor wrapper struct. This will
wrap the RPC operations to trigger a new RBF close bump within a new
actor. In the next commit, we'll now register this actor, and clean up
the call graph from the rpc server to this actor.
An earlier commit added an auto-enable that forces RbfCoopClose=true
whenever either taproot channel flag is set. This breaks
taproot-overlay channels, because the RBF coop close state machine in
lnwallet/chancloser/rbf_coop_*.go does not integrate the AuxCloser
(or any other aux) hook that overlay channels depend on to build
aux-aware close transactions. A node that enables
--protocol.simple-taproot-overlay-chans ends up with RBF force-on and
its overlay channel closes silently fail, leaving the aux closer
unable to finalize on-chain.
Narrow the auto-enable so it only fires for TaprootChans (staging /
final taproot) and explicitly skips it when TaprootOverlayChans is set.
Operators that positively want RBF can still opt in via
--protocol.rbf-coop-close; this change only removes the forced path
that silently breaks overlay closes.
The feature-bits-to-lnrpc-enum switch in sendAcceptRequests covered
every commitment type the RPC acceptor can be asked about, except the
production taproot variant introduced alongside the prod-taproot-chans
work. For a channel open using SimpleTaprootChannelsRequiredFinal (with
any combination of the scid-alias / zero-conf modifiers), the switch
fell through to the default branch, which logs a warning and leaves
commitmentType at its zero value -- lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE.
External acceptor clients then see UNKNOWN rather than the actual
commitment type and either reject or misclassify the channel.
Add the four missing cases so the new commitment type is reported to
acceptor clients correctly.