The interceptor exposes its derived auto-fail height as an int32. Calculate
the height in int64 and fail forwards whose deadline cannot be represented
with expiry_too_far.
Add coverage for the range check and subsequent forward handling.
(cherry picked from commit ae3f4aff48)
Forward the supplied incoming HTLC expiry from the outgoing contest resolver to its embedded timeout resolver. This keeps the deadline available when resolution transitions after the outgoing HTLC expires.
(cherry picked from commit f77606851f)
In this commit, we hold off on recording the remote party's close output until
we've decided we can act on their Shutdown. ReceiveShutdown wrote the field
before it looked at the state, so a Shutdown that arrives at a point where we
have nothing to do with it, say once we've already finished the negotiation,
would still overwrite the output we settled on before being turned away with
ErrInvalidState. The output we report for the close then describes a message we
rejected.
Nothing acts on this today, as we hand the outputs to the caller only after
ClosingTx tells it the negotiation finished, but the field is what we report to
the party that asked for the close, so we may as well only fill it in from a
message we accepted.
(cherry picked from commit fb89732d24)
In this commit, we give the legacy ChanCloser a single owner, rather than
letting two goroutines advance it. The peer's channelManager drives the state
machine for the Shutdown and ClosingSigned messages that come off the wire, and
for local close requests. The link drives it as well: while we wait for the
channel to drain we register a flush hook, and the link invokes that hook from
its own goroutine, where it called BeginNegotiation directly. Nothing kept the
two apart, so the state field, the priorFeeOffers map, and the signing step
could all be touched at once. Under `go test -race` this shows up as a data race
on the state field.
Rather than reach for a lock, we route the flush through the channelManager. The
hook now only reports the channel ID over a new chanCloseFlushed channel, and
handleChanFlushed picks it up next to the close messages. Every transition, the
cached offer processing, the fee map, and the signing then happen on the one
goroutine, so the closer needs no synchronization of its own. We spell that out
on the type, since it's an invariant a new caller can break from the outside.
The report goes out from a fresh goroutine, which matters more than it looks.
The link may well be holding its own lock while it invokes the hook, and
channelManager reaches for that same lock in DisableAdds, so blocking on the
handoff would trade the race for a deadlock. The `go` in front of RemoveLink
just above it is there for the same reason.
We look the closer up with a plain map load rather than through
fetchActiveChanCloser, as that one builds a fresh closer when it doesn't find
an existing one, and a flush that lands after the negotiation was torn down has
no business starting a new negotiation.
One behavior change falls out of the move: the flush path now runs the same
finalization tail as the message path. It skipped that before, so a responder
that drained a cached offer would reach closeFinished and broadcast, but nothing
ran finalizeChanClosure until the next close message showed up, and having
already sent its final signature, there may not be one. The link == nil path
already ran the tail, so this makes all three paths agree.
The new test drives a close with a link that hands us the flush hook instead of
running it inline, so we can check that negotiation waits on the report, and
that a report for a channel we have no closer for is dropped.
(cherry picked from commit e5e134ddac)
In this commit, we make the RBF co-op closer validate the remote party's
delivery script in all cases, matching what the negotiation closer already does.
Previously we only ran the check when we had an upfront shutdown script on record
for the peer, so a peer that never committed to an upfront script could hand us a
delivery script that we'd stash and carry through the rest of the close flow
without ever looking at it.
We now always call validateShutdownScript with the (possibly nil) upfront
script: a nil upfront script still runs the well-formedness check on the peer's
script, and a non-nil one additionally enforces the exact match, same as before.
We also require the script to be present. The wire format puts no lower bound on
the address length, and validateShutdownScript treats an absent peer script as
nothing to check, so an empty one passed validation by default rather than on its
merits. Both entry points now go through one helper that insists on a script
before running the usual checks over it, which also covers a CloserScript
swapped in mid-negotiation via ClosingComplete rather than letting that one go
unchecked.
The delivery-form coverage is spelled out in the tests: the spec dropped p2pkh
and p2sh for co-op closes to keep the dust calculations uniform, and we don't
implement the OP_RETURN form that option_simple_close allows, so all of those are
rejected along with an empty or malformed script.
(cherry picked from commit a8e2a0f7fa)
In this commit, we have DustLimitForSize fall back to the generic witness dust
threshold for any script size that doesn't match one of the well-known
templates.
The size switch covered P2WPKH, P2WSH, P2SH, P2PKH, and the explicit
unknown-witness size, and treated every other length as unreachable. That's a
narrower assumption than the callers can actually make good on: a witness
program for versions 1 through 16 carries a program of anywhere from 2 to 40
bytes, so its serialized length won't always land on one of those exact values.
The dust calculation only needs a representative output of roughly the right
shape, and the unknown-witness pricing is the conservative choice among the
ones we have, so we make it the default. That leaves the helper well defined
across the whole range of sizes callers can pass it, including scripts carrying
witness versions we don't know about yet.
(cherry picked from commit f80f92dc04)
Document the new WalletKit.SubmitPackage RPC and the lncli wallet
submitpackage command in the 0.22.0 release notes.
(cherry picked from commit 68c47407d7)
Add an integration test that exercises WalletKit.SubmitPackage: it builds
a zero-fee v3 (TRUC) parent that a standalone broadcast would reject,
pairs it with a fee-paying v3 CPFP child, and asserts the package is
accepted. A zero-fee transaction can only enter the mempool via package
evaluation, so this proves the CPFP package path end to end.
submitpackage is a bitcoind RPC, so the test skips on the btcd and
neutrino backends. Also adds the SubmitPackage wrapper to the
integration-test RPC harness.
(cherry picked from commit 47661cea86)
Add a `wallet submitpackage` command that takes one or more hex-encoded
raw transactions (topologically sorted, parents first and the child
last) and an optional --max_fee_rate, and submits them as a package via
the WalletKit.SubmitPackage RPC.
(cherry picked from commit ea88a26870)
Add SubmitPackage to the lnwallet.WalletController interface and a new
WalletKit.SubmitPackage RPC, so a client of lnd can relay a package of
related transactions (parents first, child last) through lnd's own chain
connection. This lets a zero-fee v3/TRUC parent be accepted via its
fee-paying CPFP child without the caller needing a separate connection to
the chain backend.
BtcWallet.SubmitPackage forwards to the chain backend's submitpackage for
bitcoind/btcd, and broadcasts each transaction individually for neutrino
(no mempool; relies on the peer's 1p1c package relay). The WalletKit
handler maps the proto request/response to the btcjson result and is
gated by the onchain:write macaroon permission. Mock controllers and the
no-chain backend gain trivial implementations.
(cherry picked from commit f55c0565d0)
Backport note: the original bumps btcwallet to v0.18.0, which sits on top
of the btcd v2 module migration. This branch tracks the pre-v2 btcd
layout, so it pins btcwallet v0.16.19 instead: the same chain.Interface
SubmitPackage backported onto the v0.16 line. The lnd-side change is
otherwise unmodified.
In this commit, we cap each QueryChannelRange response at 100,000 SCIDs
across all streamed replies. The existing reply-count limit did not track
the aggregate decoded working set, so memory use varied with the encoding
and composition of the reply stream.
We count raw SCIDs before timestamp filtering, charge replies using the
received encoding type, and release all accumulated range state on any
error. This bounds both memory and CPU work while still leaving headroom
above the current graph.
(cherry picked from commit ceff94fadd)
In this commit, we cap each decompressed short channel ID set at 100,000
entries, matching the aggregate range reply budget. The old zlib reader
bounded compressed input rather than decoded output, so the two working-set
limits could drift apart.
We retain compatibility with protocol-valid compressed replies, reject
truncated or corrupt zlib streams, and close the reader on every exit.
Boundary, compatibility, corruption, and property tests cover the
decoder.
(cherry picked from commit d162291941)
Add integration tests for an lnd introduction node forwarding a blinded
payment whose non-final hops identify the next hop by node ID (next_node_id)
rather than a short channel ID, as produced by other implementations:
- testBlindedRouteNextNodeID: the outgoing channel is public.
- testBlindedRouteNextNodeIDPrivateChannel: the outgoing channel is
private, so the node ID resolves to an SCID alias.
- testBlindedRouteNextNodeIDRestart: the introduction node is restarted
while the HTLC is in flight, exercising forwarding-package replay and
re-decode of the node-ID blinded hop.
(cherry picked from commit da6a40c01d)
Extend the on-chain interceptor path in the witness beacon to expose a
node-ID next hop, mirroring the off-chain path. A node-ID next hop has no
outgoing channel of its own, so the beacon reports hop.Exit as the outgoing
channel (via ForwardingInfo.NextHopChannel().UnwrapOr) and the requested
next node's public key. The RPC boundary maps that to the NodeIDForwardSCID
sentinel so the forward is not misread as a final receive.
This is the requested next hop, not the channel eventually selected by
non-strict forwarding, so the beacon deliberately does not resolve it
against the circuit map.
(cherry picked from commit 9c4b8bfec2)
When the switch forwards a blinded hop identified by node ID, it has not
yet resolved a concrete outgoing channel at interception time. Expose the
next hop to the interceptor: InterceptedForward.Packet() reports the
packet's outgoing channel as-is (hop.Exit, since none is selected yet) and
carries the requested pubkey in OutgoingNodeID.
At the RPC boundary, forwardInterceptor.onIntercept maps a node-ID hop to
the reserved NodeIDForwardSCID sentinel in outgoing_requested_chan_id and
the pubkey in outgoing_requested_node_id, so a client switching on a zero
channel ID to detect the exit hop does not misread the forward as a final
receive. The sentinel is a wire-only concern, applied where the request is
built rather than in the switch's internal InterceptedPacket, which stays
truthful (OutgoingNodeID.IsSome() is the node-ID discriminator).
(cherry picked from commit 32373b76c7)
Now that the switch forwards blinded hops identified by node ID, a new
problem surfaces in the HTLC event stream. A node-ID next hop has no
outgoing short channel ID until non-strict forwarding selects one, so a
forward that fails before selection still carries outgoingChanID ==
hop.Exit. getEventType keys the exit hop off that sentinel, so it
misclassifies such a failed node-ID forward as a receive, mislabeling the
event streamed via SubscribeHtlcEvents (a forwarding failure reported as
a receive failure).
Two paths reach getEventType before an SCID is selected: the fail packet
built by failAddPacket and the resolution packet built by resolve, both
of which dropped the decoded next hop. Carry outgoingHop into both, and
classify a Right (node-ID) outgoingHop as a forward before the hop.Exit
check. A node-ID next hop is always a forward, never the exit hop.
(cherry picked from commit a4844ef522)
Fixeslightningnetwork/lnd#10937: forward a blinded-route payment when the
recipient identifies the next hop by node ID rather than a short channel
ID. The htlcPacket carries the decoded next hop to the switch, whose
handlePacketAdd resolves the pubkey to the peer's links via getLinks() and
lets the existing non-strict forwarding logic load-balance across the
peer's channels.
outgoingChanID stays a ShortChannelID. It is the persisted CircuitKey and
is set to the selected channel after non-strict selection. The circular
route check filters candidate channels before selection.
(cherry picked from commit dbc5704070)
Some implementations (e.g. Core Lightning) identify the next hop in a
blinded route by the next node's ID (next_node_id) instead of a short
channel ID. Decode such a hop into a node-ID next hop, the Right of
ForwardingInfo.NextHop, holding the next node's public key. The switch
resolves that key to one of our channels with the peer in a later commit.
BOLT 4 requires a non-final blinded hop to carry exactly one of
short_channel_id or next_node_id, so a hop that sets both is rejected.
(cherry picked from commit 4fd4289a08)
The forwarding next hop is currently always a short channel ID. To allow a
blinded route to identify the next hop by node ID instead, change
ForwardingInfo.NextHop to fn.Either[lnwire.ShortChannelID, [33]byte], where
the Left is the outgoing channel ID and the Right (wired up in a follow-up
commit) is the next node's public key.
This commit is a pure representational change with no behavioural effect:
every next hop is still a channel ID. The Either is encapsulated behind
ForwardingInfo methods so callers never destructure it directly: IsExit()
is the single source of truth for exit-hop detection (used by the link and
the contract court) and NextHopChannel() yields the outgoing SCID.
(cherry picked from commit d28a71765b)
A blinded route may identify the next hop by node ID (next_node_id) rather
than by channel, in which case there is no sender-specified outgoing channel
to report to an HTLC interceptor. Add an outgoing_node_id field to
ForwardHtlcInterceptRequest to carry the next hop's public key for these
forwards, and document that outgoing_requested_chan_id then holds a reserved
sentinel value so that clients switching on a zero channel ID to detect the
exit hop do not misclassify the forward as a final receive.
This commit only adds the schema and regenerated stubs; the fields are
populated by later commits.
(cherry picked from commit 14640a5016)
During non-strict forwarding, handlePacketAdd evaluates every candidate
channel to the next peer and calls CheckHtlcForward with the sender-requested
outgoing SCID (originalOutgoingChanID) for each candidate. That SCID flowed
through canSendHtlc into AuxTrafficShaper.ShouldHandleTraffic, so a
channel-keyed shaper was asked about the requested channel rather than the
candidate actually being evaluated. With parallel channels to a peer this
inspects the wrong channel.
Key the shaper on l.ShortChanID() (the channel under evaluation) instead.
originalScid is retained solely for createFailureWithUpdate / FailAliasUpdate,
so the alias-aware channel_update returned to the sender is unchanged and the
real SCID handed to the shaper never leaks onto the wire.
(cherry picked from commit b166780015)
Use strict metadata reads during migration selection so a metadata bucket with a
missing metadata/dbp key is not interpreted as the latest DB version.
Recover this state from mandatory DB version 33, the last mandatory version
before the v0.20.x releases that could initialize a DB without writing the DB
version key. This runs migration 35 without replaying migrations 0 through 33
against a DB that was already created by a modern schema/code path.
After the selected migrations complete, syncVersions writes the latest DB
version as usual.
(cherry picked from commit 68264c70d9)
Allow migration 35 to skip records that are already keyed using the typed
waiting proof format. This lets the missing-version recovery path safely run
migration 35 on DBs that were created directly by v0.21 and may already contain
typed waiting proofs.
Legacy 9-byte records are still migrated and unexpected key shapes still fail.
(cherry picked from commit 3aff61aebe)
Keep the top-level bucket creation introduced by PR #9653, since initialized
DBs can still be missing newer buckets such as the historical channel bucket.
Do not let the metadata bucket created during init make a fresh DB look
initialized. Use strict metadata reads so a missing metadata/dbp key is distinct
from a present DB version, then write metadata/dbp for genuinely fresh DBs.
Existing DBs with a metadata bucket but missing metadata/dbp are left for the
migration recovery path instead of being treated as latest.
(cherry picked from commit 619913bfb2)
Document the handling of historical total-only blinded route data and the
new SendToRouteV2 validation that prevents creating such records.
(cherry picked from commit 17ee638e37)