Rename the ForwardingInfo.OutgoingCTLV field to OutgoingCLTV and update all
call sites. This keeps the exported field spelling consistent with the CLTV
terminology used elsewhere.
Also fix the remaining CTLV typos in nearby comments.
Migrate all btcd dependencies to the new per-package v2 modules (wire/v2,
txscript/v2, chaincfg/v2, chainhash/v2, btcutil/v2, psbt/v2, btcec/v2)
introduced by btcd v0.26.0, and pin the tagged ecosystem versions:
btcwallet v0.17.0, neutrino v0.18.0 and lightning-onion v1.4.0.
The bulk of the import rewrite was produced by the scripted diff from
https://github.com/btcsuite/btcd/pull/2547 (followed by 'make rpc'). The
address symbols that moved out of btcutil into the new address package
are imported as btcaddr where a local "address" variable would otherwise
shadow them. The go.mod/go.sum updates and the remaining manual
compilation fixes are folded into this single commit so it builds on its
own (the migration was previously split into a reproducible scripted-diff
plus follow-ups, intended to be squashed on merge).
Tor stopped serving v2 onion services in October 2021; lnd should not
produce v2 addresses anymore, but it must still verify signatures on
and re-broadcast peer NodeAnnouncement messages that carry v2 entries.
Stop accepting v2 as configuration input (lncfg), strip the legacy
`--tor.v2` flag from the sample config, and remove the
`tor.OnionHostToFakeIP` helper. Operator entry points (`--externalip`,
`--listen`, `lncli connect`, `lncli wtclient towers add`) fail fast on
a v2 `.onion` string, so upgrading nodes must remove any v2 entry from
`lnd.conf` before lnd will start.
Filter persisted v2 state before use without rewriting on-disk records:
the self-announcement builder strips any v2 entry inherited from the
stored self-node; the watchtower client drops v2 entries from each
persisted tower's address list (skipping the tower entirely if no
non-v2 address remains); the autopilot connector, graph bootstrapper,
and static-channel backup restore paths skip v2 entries before
attempting outbound dials. Restrict the Tor controller's ADD_ONION
path to v3 keys, including the encrypted on-disk legacy-key fallback.
For inbound announcements, keep the wire codec wire-faithful:
`lnwire.WriteOnionAddr`, `graph/db.encodeOnionAddr`, and the matching
decoders round-trip v2 bytes so `DataToSign` reproduces the bytes the
remote peer signed, signature validation succeeds, and the announcement
is persisted to the graph DB and re-broadcast across restarts byte-for-
byte. RPC surfaces continue to expose the full address set so external
tools can independently reproduce and verify the signed bytes.
Add a netann regression test that signs a [v3, v2, ipv4] announcement,
round-trips it through Encode/Decode, verifies the signature, and
confirms the resulting models.Node preserves the v2 entry. Add a
graph bootstrapper test asserting v2 entries are skipped while v3 and
plain TCP entries on the same node still surface as bootstrap
candidates.
Fix FundingPKScript() to check for the taproot staging feature bit on
v1 channel edges. When present, reconstruct a taproot funding script
via GenTaprootFundingScript instead of the legacy P2WSH multisig.
This is a pre-existing bug: private taproot channels have always been
stored as v1 gossip objects with the taproot feature bit, but
FundingPKScript() never checked for it. The discovery/gossiper layer
(makeFundingScript) already handled this correctly on the insertion
path, but any read path that called FundingPKScript() -- notably
ChannelView() used for chain filter reconstruction on restart --
would produce the wrong script.
Update the tests from the previous commit to assert the correct
taproot funding script instead of the legacy P2WSH script.
Private taproot channels are currently represented in the gossip/graph
layer as v1 gossip objects with the SimpleTaprootChannelsRequiredStaging
feature bit set on the v1 ChannelAnnouncement1.
However, FundingPKScript() on a v1 ChannelEdgeInfo unconditionally
reconstructs a legacy 2-of-2 P2WSH multisig script, ignoring the
taproot feature bit entirely. This means that code paths such as
ChannelView() (used to rebuild the chain watch filter on restart)
produce the wrong funding script for these channels.
This bug has always been present since private taproot channels were
first introduced. The discovery/gossiper path (makeFundingScript)
correctly honors the taproot bit when validating announcements on
insertion, but the graph DB read paths never did.
Add failing tests at both the model level (FundingPKScript) and the
graph level (ChannelView round-trip) to document this mismatch. The
next commit fixes the behavior so these tests pass.
Add ChanEdgePolicyFromWire to construct ChannelEdgePolicy from channel
update messages, centralizing v1/v2 field mapping.
Update call sites to use the helper:
- discovery/gossiper: handleChanUpdate
- graph/builder: ApplyChannelUpdate
- routing/router_test: ApplyChannelUpdate test helper
This consolidates update-to-policy conversion logic across versions.
Replace MessageFlags and ChannelFlags bitfields in CachedEdgePolicy
with explicit boolean fields to improve clarity and support both v1
and v2 channel updates:
- Replace MessageFlags with HasMaxHTLC boolean.
- Replace ChannelFlags with IsNode1 and IsDisabled booleans.
- Update NewCachedPolicy to extract these fields version-appropriately:
- For v1: derive from MessageFlags and ChannelFlags bits.
- For v2: derive from policy.SecondPeer and policy.DisableFlags.
Update all call sites that used method calls IsNode1() and IsDisabled()
to instead access the fields directly. This includes:
- graph_cache.go: policy direction and disable checks
- unified_edges.go: HasMaxHTLC and IsDisabled checks
- Tests: policy construction and assertions
This refactoring improves readability by making the cached policy's
state explicit rather than encoded in bitfields, and enables seamless
support for both gossip protocol versions.
Extend ChannelEdgePolicy to support v2 channel updates by adding:
- Version field to track gossip protocol version (v1 or v2).
- LastBlockHeight for v2's block-height-based timestamps.
- SecondPeer flag to indicate which peer announced the policy in v2.
- DisableFlags for v2-specific channel disable signaling.
- ExtraSignedFields map for v2 extra signed TLV data.
Add version-aware methods:
- IsNode1() determines if the policy was announced by node_1, handling
both v1 (via ChannelFlags direction bit) and v2 (via SecondPeer).
- IsDisabled() checks disable status using ChannelFlags for v1 and
DisableFlags for v2.
- String() provides version-appropriate string representations.
The new fields use zero values for v1 compatibility (Version defaults
to GossipVersion1, LastBlockHeight to 0, SecondPeer to false). This
lays the groundwork for v2 policy support; a subsequent commit will
handle reading and writing these fields from/to the database.
This commit improves handling of missing channel signatures in the
database:
- Return nil from auth proof accessors instead of empty slices so that
missing signatures are stored as NULL in SQL.
- Update public channel checks to require signature length > 0, which
properly handles existing empty bytea values in the database.
- Add regression test covering empty v1 and v2 channel signatures to
prevent future issues.
Extends the SQL store to support v2 (taproot) channel announcements:
- Add version validation in AddChannelEdge
- Store v2-specific fields: FundingPkScript, MerkleRootHash, Signature
- Update buildEdgeInfoWithBatchData to reconstruct v2 channels from DB
with optional bitcoin keys and funding script handling
- Add WithMerkleRootHash edge modifier for ChannelV2Fields
This commit extends ChannelEdgeInfo to support v2 (taproot) channel
announcements by adding:
- MerkleRootHash for the optional Merkle tree commitment
- ExtraSignedFields for additional signed TLV fields
- ChannelV2Fields struct to encapsulate v2-specific optional fields
- NewV2Channel constructor for creating v2 channel edges
- FundingPKScript handling for MuSig2 key aggregation with optional
taproot tweaks
Add FundingPKScript() method that returns the funding output's
pkScript for the channel. The implementation is version-aware:
- V1: generates a 2-of-2 multisig P2WSH script from the two bitcoin
keys
- V2: will use taproot script (to be implemented)
This encapsulates the script generation logic and makes it clear
which bitcoin keys are being used. Replaces direct calls to
genMultiSigP2WSH with the cleaner method call.
Since not all will be required for V2 channels.
Wrap BitcoinKey1Bytes and BitcoinKey2Bytes in fn.Option since these
fields are only required for v1 channel announcements. V2 channels may
or may not have bitcoin keys present in their announcement.
NewV1Channel constructor wraps the bitcoin keys with fn.Some().
All access sites updated to unwrap the options, using UnwrapOr for
non-critical paths and UnwrapOrErr where the keys must be present
(e.g., KV serialization, ToChannelAnnouncement).
So that we have one place that converts from our `models` struct to the
`lnwire.ChannelAnnouncement` struct.
The commit also refactors netann.CreateChanAnnouncement to only take a
ChannelEdgeInfo and get the proof from there instead of needing the
proof to be passed in separately.
Add ToChannelAnnouncement() method to ChannelEdgeInfo that converts
the model struct to a lnwire.ChannelAnnouncement1 message. This:
- Centralizes the conversion logic in one place instead of scattered
across multiple call sites
- Validates that AuthProof is present (can't create announcement
without proof)
- Currently only supports v1 channels, returning error for v2
Refactor netann.CreateChanAnnouncement to use this helper and remove
the separate chanProof parameter since proof is now accessed from
within ChannelEdgeInfo. This improves encapsulation and reduces
parameter count.
This makes it clear what fields must/can be set for a V1 channel.
Introduce NewV1Channel constructor to create v1 channel edges with
proper initialization and validation. The constructor:
- Takes required fields (chanID, chainHash, node keys) as parameters
- Takes v1-specific fields (bitcoin keys, extra opaque data) via
ChannelV1Fields struct
- Accepts optional fields (capacity, channel point, features, proof)
via functional options (WithCapacity, WithChannelPoint, etc.)
- Validates that if an AuthProof is provided, its version matches the
channel version
This makes it clear which fields are required vs optional for v1
channels and prevents incorrectly initialized channel edges. All
tests and production code updated to use the constructor.
Also update it to more closely match the persisted version which has the
v1 and v2 only fields as optional.
Refactor ChannelAuthProof to support both v1 and v2 channel
announcements:
- Add Version field to distinguish v1 from v2 proofs
- Wrap v1-specific fields (NodeSig1/2, BitcoinSig1/2) in fn.Option
since v2 doesn't use them
- Add optional Signature field for v2's single schnorr signature
- Add constructor functions NewV1ChannelAuthProof and
NewV2ChannelAuthProof to enforce correct initialization
- Add getter methods (NodeSig1(), BitcoinSig1(), etc.) that safely
unwrap options, returning empty slices when not present
The IsEmpty() check is updated to handle both versions correctly.
Both stores validate v1-only for now.
And set it to V1 version everywhere.
Add a Version field to ChannelEdgeInfo to distinguish between v1 and
v2 channel announcements. Set it to GossipVersion1 for all existing
channels.
Both KV and SQL stores now validate that only v1 channels are
currently supported, returning an error for v2 channels. The KV store
automatically sets version to v1 when deserializing (since all
persisted channels in KV format are v1).
This versioning is essential for handling the different field
requirements and validation logic between v1 and v2 channels.
Replace [33]byte with route.Vertex for NodeKey1Bytes, NodeKey2Bytes,
BitcoinKey1Bytes, and BitcoinKey2Bytes in ChannelEdgeInfo. Since
route.Vertex is defined as [33]byte, this change is functionally
equivalent but provides better type safety and consistency with the
rest of the routing subsystem.
OtherNodeKeyBytes is also updated to return route.Vertex.
Remove the cached parsed signature field and its lazy getter method
from ChannelEdgePolicy. This field was unused throughout the codebase
and the signature is already stored as raw bytes in SigBytes.
The SetSigBytes method is updated to remove the cache invalidation
logic.
We add a new NewV2Node constructor which takes a new NodeV2Fields as a
parameter. This NodeV2Fields struct defines the fields that can be set
in a models.Node if the version is V2.
Both NodeKey1 and NodeKey2 methods had the same race condition as the
Node.PubKey method, where concurrent calls could race to write to the
cached fields.
Remove the caching for the same reasons: parsing overhead is minimal
and doesn't justify the complexity and race risk.
The PubKey method had a race condition where concurrent calls could
all pass the nil check and race to write to the cached pubKey field.
This is a classic check-then-act race.
Remove the caching entirely to fix the race. The overhead of parsing
a public key is minimal and doesn't justify the added complexity and
race risk of caching.
In preparation for adding a NodeAnnouncement2 struct along with a
NodeAnnouncement interface, this commit renames the existing
NodeAnnouncment struct to NodeAnnouncement1.
In this commit, we move the serialisation details of a channel's
features to the DB layer and change the `models` field to instead use a
more useful `*lnwire.FeatureVector` type.
This makes the features easier to work with and moves the serialisation
to where it is actually used.
Define a new CachedEdgeInfo type and let the graph cache's AddChannel
use this. This will let us later on (for the SQL impl of the graph db)
only load from the DB what we actually need for the graph cache.
Update the GraphCache.UpdatePolicy method to take a
`models.CachedEdgePolicy` instead of a `models.ChannelEdgePolicy`.
Doing this will allow us later on to only fetch the necessary info for
populating the CachedEdgePolicy when we are populating the cache via
UpdatePolicy.
Here we add an explicit InboundFee field to the ChannelEdgePolicy
struct. Then, in the graph KVStore, at deserialisation time, we extract
the InboundFee from the ExtraOpaqueData. Currently we do this at higher
levels but we are going to move it to the DB layer so that when we add
the SQL implementation of the graph store, we can have explicit columns
for inbound fees. We need to account for the fact that we may have
invalid TLV already persisted though and we dont want to fail if we
deserialise those necessarily. So we return ErrParsingExtraTLVBytes now
if we fail to parse the extra bytes as TLV and then we let the callers
handle it similarly to how ErrParsingExtraTLVBytes is handled in that we
dont necessarily fail if we receive one of these errors.
As of this commit, we can now expect the InboundFee field of a
ChannelEdgePolicy to be set (if inbound fees are set on the policy) for
any update that we read from disk.
In preparation for an upcoming commit which will move all channel
funding tx validation to the gossiper, we first move the helper method
which helps build the expected funding transaction script based on the
fields in the channel announcement. We will still want this script later
on in the builder for updating the ChainView though, and so we pass this
field along with the ChannelEdgeInfo. With this change, we can remove
the TapscriptRoot field from the ChannelEdgeInfo since the only reason
it was there was so that the builder could reconstruct the full funding
script.
All the structs defined in the `channeldb/models` package are graph
related. So once we move all the graph CRUD code to the graph package,
it makes sense to have the schema structs there too. So this just moves
the `models` package over to `graph/db/models`.