Commit graph

20913 commits

Author SHA1 Message Date
ziggieXXX
54a2b5a2dc
Merge pull request #11043 from ziggie1984/bump-v0.21.2
build: bump version to v0.21.2-beta.rc1
2026-08-07 13:01:00 -03:00
ziggie
75ab74b9f8
build: bump version to v0.21.2-beta.rc1 2026-08-07 09:38:57 -03:00
ziggieXXX
88ea937d82
Merge pull request #11039 from lightningnetwork/backport-11028-to-v0.21.x-branch
[v0.21.x-branch] Backport #11028: htlcswitch: validate intercepted auto-fail height
2026-08-06 23:59:03 -03:00
ziggie
0c0e970b1c docs: add interceptor auto-fail height release note
(cherry picked from commit 4ccb2aa94a)
2026-08-07 02:54:19 +00:00
ziggie
b021eb0270 htlcswitch: bound intercepted auto-fail height
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)
2026-08-07 02:54:18 +00:00
ziggieXXX
77f060e570
Merge pull request #11034 from lightningnetwork/backport-11032-to-v0.21.x-branch
[v0.21.x-branch] Backport #11032: contractcourt: retain deadline across contest resolution
2026-08-06 08:35:29 -03:00
ziggie
0cdbe350e3 docs: update 0.21.2 release notes
(cherry picked from commit 5b4c5067db)
2026-08-06 11:24:15 +00:00
ziggie
20313a32c0 contractcourt: retain deadline across contest resolution
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)
2026-08-06 11:24:15 +00:00
Yong
9be6159da6
Merge pull request #11020 from lightningnetwork/backport-10900-to-v0.21.x-branch
[v0.21.x-branch] Backport #10900: lnwallet+walletrpc: add WalletKit.SubmitPackage for v3 CPFP package relay
2026-08-06 10:59:07 +08:00
ziggieXXX
37ed47427c
Merge pull request #11031 from lightningnetwork/backport-11019-to-v0.21.x-branch
[v0.21.x-branch] Backport #11019: lnwallet/chancloser: fix data race in the legacy coop close state machine
2026-08-05 22:50:09 -03:00
Olaoluwa Osuntokun
c0b2d83621 docs: add release notes entry for the coop close fixes
(cherry picked from commit 4944bb0794)
2026-08-06 01:28:17 +00:00
Olaoluwa Osuntokun
aa4f341280 lnwallet/chancloser: record the remote close output only when accepted
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)
2026-08-06 01:28:17 +00:00
Olaoluwa Osuntokun
1447027fb8 peer+lnwallet/chancloser: advance the legacy closer from one goroutine
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)
2026-08-06 01:28:16 +00:00
Olaoluwa Osuntokun
2ab90c2bc8 lnwallet/chancloser: validate delivery scripts in the RBF closer
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)
2026-08-06 01:28:16 +00:00
Olaoluwa Osuntokun
a020064b19 lnwallet: make DustLimitForSize total over the sizes it can be handed
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)
2026-08-06 01:28:16 +00:00
Elle Mouton
0f45c5c83c docs: add release note for WalletKit.SubmitPackage
Document the new WalletKit.SubmitPackage RPC and the lncli wallet
submitpackage command in the 0.22.0 release notes.

(cherry picked from commit 68c47407d7)
2026-08-05 17:58:31 -07:00
Elle Mouton
d02701d82f itest: add SubmitPackage integration test
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)
2026-08-05 15:57:36 -07:00
Elle Mouton
c2ba961ab4 lncli: add wallet submitpackage command
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)
2026-08-05 15:57:36 -07:00
Elle Mouton
ce104114bc lnwallet+walletrpc: add SubmitPackage for v3 CPFP package relay
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.
2026-08-05 15:57:36 -07:00
ziggieXXX
795692c8f6
Merge pull request #11027 from lightningnetwork/backport-11024-to-v0.21.x-branch
[v0.21.x-branch] Backport #11024: invoices: refine update handling
2026-08-05 15:28:32 -03:00
ziggie
94a7851109 docs: update 0.21.2 release notes
(cherry picked from commit 758bbb8e52)
2026-08-05 18:15:15 +00:00
ziggie
8ca9e1310d invoices: refine update handling
(cherry picked from commit 6be6350ec4)
2026-08-05 18:15:15 +00:00
Olaoluwa Osuntokun
a2e95f3364
Merge pull request #11017 from lightningnetwork/backport-10992-to-v0.21.x-branch
[v0.21.x-branch] Backport #10992: discovery: bound channel range reply memory usage
2026-08-03 16:26:59 -07:00
Olaoluwa Osuntokun
2bcb7572de
discovery: bound channel range reply buffering
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)
2026-08-03 18:57:23 -03:00
Olaoluwa Osuntokun
cd468e5876
lnwire: cap decoded short channel IDs
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)
2026-08-03 18:57:13 -03:00
Olaoluwa Osuntokun
4381c486a1
Merge pull request #11010 from lightningnetwork/backport-10942-to-v0.21.x-branch
[v0.21.x-branch] Backport #10942: htlcswitch: forward blinded payments addressed by node_id
2026-08-03 13:46:09 -07:00
ziggieXXX
672a8b092b
Merge pull request #11012 from ziggie1984/fix-v021-kvdb-tidy
[v0.21.x] kvdb: mark pgx/v5 as direct dependency
2026-07-31 13:55:40 -03:00
ziggie
2e3baa842e
kvdb: mark pgx/v5 as direct dependency
Backport PR #10977 added direct pgx/v5 imports in commit e7f9a9220 without updating kvdb/go.mod. Regenerate the module metadata with go mod tidy.
2026-07-30 12:11:53 -03:00
bitromortac
843665312b
docs: update release notes
(cherry picked from commit f42b429899)
2026-07-30 11:47:14 -03:00
bitromortac
b70cb21486
itest: cover blinded route next_node_id forwarding
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)
2026-07-30 11:47:14 -03:00
bitromortac
4b9f2f0f6a
witness beacon: report node-ID next hop to the on-chain HTLC interceptor
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)
2026-07-30 11:47:14 -03:00
bitromortac
fa55f44367
htlcswitch+lnrpc: report node-ID next hop to the off-chain HTLC interceptor
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)
2026-07-30 11:17:31 -03:00
bitromortac
dc59a72a84
htlcswitch: classify a node-ID forward as a forward event
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)
2026-07-30 11:17:31 -03:00
bitromortac
f681f0043b
htlcswitch: forward node-ID blinded hops via non-strict forwarding
Fixes lightningnetwork/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)
2026-07-30 11:17:31 -03:00
bitromortac
0bbacacab3
htlcswitch/hop: decode next_node_id blinded hops
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)
2026-07-30 11:17:30 -03:00
bitromortac
f7e745c113
multi: represent the blinded forwarding next hop as an fn.Either
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)
2026-07-30 11:17:29 -03:00
bitromortac
17a3fe4d1b lnrpc/routerrpc: add outgoing_node_id to HTLC intercept request
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)
2026-07-30 12:31:33 +00:00
bitromortac
1521208359 htlcswitch: key the aux traffic shaper on the evaluated channel
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)
2026-07-30 12:31:33 +00:00
ziggieXXX
524599908e
Merge pull request #10988 from lightningnetwork/backport-10985-to-v0.21.x-branch
[v0.21.x-branch] Backport #10985: channeldb: recover missing db version key
2026-07-22 18:16:15 -03:00
Elle Mouton
71b6194eac channeldb: recover missing db version
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)
2026-07-22 20:46:55 +00:00
Elle Mouton
47728c41dd channeldb: make waiting proof migration repeatable
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)
2026-07-22 20:46:54 +00:00
Elle Mouton
d3796fd48b channeldb: preserve db version during init
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)
2026-07-22 20:46:54 +00:00
ziggieXXX
2804a6121b
Merge pull request #10986 from lightningnetwork/backport-10982-to-v0.21.x-branch
[v0.21.x-branch] Backport #10982: paymentsdb: normalize orphaned blinded total
2026-07-22 15:11:30 -03:00
ziggie
ea6b8a40ce docs: add payment migration release note
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)
2026-07-22 16:56:26 +00:00
ziggie
97eca77a13 paymentsdb: normalize orphaned blinded total
The KV route format stores blinded fields independently. Routes accepted
through SendToRouteV2 could therefore contain a blinded total amount
without encrypted recipient data. The SQL migration treated the total as
proof of a blinded hop and bound nil to the required encrypted-data
column, preventing LND from starting.

Use encrypted recipient data as the blinded-hop discriminator and
normalize only the known total-only case. Reject blinding-point-only
records with payment, attempt and hop context instead of exposing an
opaque SQL constraint error. Log normalized totals, account for them
during migration validation, and cover both cases with regression tests.

(cherry picked from commit 04da2fa583)
2026-07-22 16:56:25 +00:00
ziggie
ab68cab3a5 routerrpc: require encrypted blinded hop data
SendToRouteV2 accepts caller-provided routes. It already required
recipient-encrypted data when a blinding point was present. However, it
copied a blinded total amount independently. This allowed a total-only
hop to enter the payment database even though LND did not classify it
as blinded.

Require encrypted data when either blinded field is supplied. Cover the
rejected combination, a valid blinded total and a regular hop.

(cherry picked from commit ffc62509b6)
2026-07-22 16:56:25 +00:00
ziggieXXX
52281b5715
Merge pull request #10977 from lightningnetwork/backport-10965-to-v0.21.x-branch
[v0.21.x-branch] Backport #10965: kvdb: add Postgres migration bulk support
2026-07-16 23:07:24 -03:00
ziggie
9b7832e446 kvdb: test migration bulk operations
(cherry picked from commit d12a8c3af5)
2026-07-17 01:40:07 +00:00
ziggie
e7f9a9220d kvdb/sqlbase: add postgres migration bulk support
This commit implements MigrationBulkKVStore for Postgres/pgx. The
Postgres wrapper is available through an explicit constructor, so
regular Postgres and shared SQLite backends do not expose the migration
capability accidentally.

The bulk load transaction pins a dedicated *sql.Conn. InsertLeaves streams
rows through pgx COPY inside that transaction. The copied row count is
checked against the input to catch partial loads. Bucket rows are inserted
individually with RETURNING id so nested buckets can reference their parent.

Verification uses a read-only repeatable-read transaction. It fetches
children of a parent-id batch with a native pgx bigint-array and a single
ANY($1) query.

Migration transactions honor the WithTxLevelLock used by regular
transactions. Loads take the write lock and verification takes the
read lock. Commit and Rollback release both the lock and the dedicated
connection. Rollback is idempotent and tolerates an already-closed
transaction.

(cherry picked from commit 9f97c49adf)
2026-07-17 01:40:06 +00:00
ziggie
9c2b01a010 kvdb/sqlbase: define migration bulk KV interfaces
This commit introduces a migration-only interface set that lets the
KV-to-SQL migration load and verify the raw SQL KV schema directly,
bypassing the walletdb/kvdb bucket abstraction. Normal application code
continues to use the bucket APIs; these helpers exist solely to make the
one-time bulk migration fast and verifiable.

MigrationBulkKVStore is the entry point. It exposes CheckEmpty to guard
against migrating into a populated table, TruncateTargetTable to recover
from an interrupted fresh-only attempt, and two transaction openers:
BeginBulk for loading and BeginBulkVerify for batched verification.

The write path inserts buckets one at a time to obtain generated ids.
It inserts leaves in batches, leaving the concrete bulk strategy to the
backend. The read path walks the tree level with FetchTopLevel and
FetchChildren.

MigrationBulkChild uses an explicit IsBucket flag rather than inspecting
the value column. This prevents an empty leaf value from being confused
with the SQL NULL marker used for buckets.

The interfaces use the same build constraints as the SQL kvdb backends.
Backends expose the migration capability explicitly; the first concrete
implementation is Postgres-only.

(cherry picked from commit 5246c15f18)
2026-07-17 01:40:06 +00:00