Skip to content

Commit 9c2c95d

Browse files
authored
Merge pull request #9478 from ellemouton/graph3
discovery+graph: move funding tx validation to the gossiper
2 parents 693b399 + e5db0d6 commit 9c2c95d

File tree

10 files changed

+703
-503
lines changed

10 files changed

+703
-503
lines changed

discovery/gossiper.go

Lines changed: 321 additions & 65 deletions
Large diffs are not rendered by default.

discovery/gossiper_test.go

Lines changed: 253 additions & 37 deletions
Large diffs are not rendered by default.

docs/release-notes/release-notes-0.19.0.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,10 @@ The underlying functionality between those two options remain the same.
251251
* [Golang was updated to
252252
`v1.22.11`](https://github.com/lightningnetwork/lnd/pull/9462).
253253

254-
* Various refactors and preparations to simplify the
255-
`graph.Builder` and to move the funding tx validation to the gossiper.
254+
* Move funding transaction validation to the gossiper
256255
[1](https://github.com/lightningnetwork/lnd/pull/9476)
257256
[2](https://github.com/lightningnetwork/lnd/pull/9477)
257+
[3](https://github.com/lightningnetwork/lnd/pull/9478).
258258

259259

260260
## Breaking Changes

graph/builder.go

Lines changed: 37 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,21 @@
11
package graph
22

33
import (
4-
"bytes"
54
"fmt"
6-
"strings"
75
"sync"
86
"sync/atomic"
97
"time"
108

119
"github.com/btcsuite/btcd/btcec/v2"
12-
"github.com/btcsuite/btcd/btcutil"
13-
"github.com/btcsuite/btcd/chaincfg/chainhash"
1410
"github.com/btcsuite/btcd/wire"
1511
"github.com/go-errors/errors"
1612
"github.com/lightningnetwork/lnd/batch"
1713
"github.com/lightningnetwork/lnd/chainntnfs"
18-
"github.com/lightningnetwork/lnd/fn/v2"
1914
graphdb "github.com/lightningnetwork/lnd/graph/db"
2015
"github.com/lightningnetwork/lnd/graph/db/models"
21-
"github.com/lightningnetwork/lnd/input"
2216
"github.com/lightningnetwork/lnd/kvdb"
2317
"github.com/lightningnetwork/lnd/lnutils"
2418
"github.com/lightningnetwork/lnd/lnwallet"
25-
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
26-
"github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
2719
"github.com/lightningnetwork/lnd/lnwire"
2820
"github.com/lightningnetwork/lnd/multimutex"
2921
"github.com/lightningnetwork/lnd/netann"
@@ -95,9 +87,9 @@ type Config struct {
9587
// blocked by this job.
9688
FirstTimePruneDelay time.Duration
9789

98-
// AssumeChannelValid toggles whether the router will check for
99-
// spentness of channel outpoints. For neutrino, this saves long rescans
100-
// from blocking initial usage of the daemon.
90+
// AssumeChannelValid toggles whether the builder will prune channels
91+
// based on their spentness vs using the fact that they are considered
92+
// zombies.
10193
AssumeChannelValid bool
10294

10395
// StrictZombiePruning determines if we attempt to prune zombie
@@ -1008,9 +1000,9 @@ func (b *Builder) assertNodeAnnFreshness(node route.Vertex,
10081000
return nil
10091001
}
10101002

1011-
// addZombieEdge adds a channel that failed complete validation into the zombie
1003+
// MarkZombieEdge adds a channel that failed complete validation into the zombie
10121004
// index so we can avoid having to re-validate it in the future.
1013-
func (b *Builder) addZombieEdge(chanID uint64) error {
1005+
func (b *Builder) MarkZombieEdge(chanID uint64) error {
10141006
// If the edge fails validation we'll mark the edge itself as a zombie
10151007
// so we don't continue to request it. We use the "zero key" for both
10161008
// node pubkeys so this edge can't be resurrected.
@@ -1024,72 +1016,6 @@ func (b *Builder) addZombieEdge(chanID uint64) error {
10241016
return nil
10251017
}
10261018

1027-
// makeFundingScript is used to make the funding script for both segwit v0 and
1028-
// segwit v1 (taproot) channels.
1029-
//
1030-
// TODO(roasbeef: export and use elsewhere?
1031-
func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte, chanFeatures []byte,
1032-
tapscriptRoot fn.Option[chainhash.Hash]) ([]byte, error) {
1033-
1034-
legacyFundingScript := func() ([]byte, error) {
1035-
witnessScript, err := input.GenMultiSigScript(
1036-
bitcoinKey1, bitcoinKey2,
1037-
)
1038-
if err != nil {
1039-
return nil, err
1040-
}
1041-
pkScript, err := input.WitnessScriptHash(witnessScript)
1042-
if err != nil {
1043-
return nil, err
1044-
}
1045-
1046-
return pkScript, nil
1047-
}
1048-
1049-
if len(chanFeatures) == 0 {
1050-
return legacyFundingScript()
1051-
}
1052-
1053-
// In order to make the correct funding script, we'll need to parse the
1054-
// chanFeatures bytes into a feature vector we can interact with.
1055-
rawFeatures := lnwire.NewRawFeatureVector()
1056-
err := rawFeatures.Decode(bytes.NewReader(chanFeatures))
1057-
if err != nil {
1058-
return nil, fmt.Errorf("unable to parse chan feature "+
1059-
"bits: %w", err)
1060-
}
1061-
1062-
chanFeatureBits := lnwire.NewFeatureVector(
1063-
rawFeatures, lnwire.Features,
1064-
)
1065-
if chanFeatureBits.HasFeature(
1066-
lnwire.SimpleTaprootChannelsOptionalStaging,
1067-
) {
1068-
1069-
pubKey1, err := btcec.ParsePubKey(bitcoinKey1)
1070-
if err != nil {
1071-
return nil, err
1072-
}
1073-
pubKey2, err := btcec.ParsePubKey(bitcoinKey2)
1074-
if err != nil {
1075-
return nil, err
1076-
}
1077-
1078-
fundingScript, _, err := input.GenTaprootFundingScript(
1079-
pubKey1, pubKey2, 0, tapscriptRoot,
1080-
)
1081-
if err != nil {
1082-
return nil, err
1083-
}
1084-
1085-
// TODO(roasbeef): add tapscript root to gossip v1.5
1086-
1087-
return fundingScript, nil
1088-
}
1089-
1090-
return legacyFundingScript()
1091-
}
1092-
10931019
// routingMsg couples a routing related routing topology update to the
10941020
// error channel.
10951021
type routingMsg struct {
@@ -1258,137 +1184,47 @@ func (b *Builder) addEdge(edge *models.ChannelEdgeInfo,
12581184
edge.ChannelID)
12591185
}
12601186

1261-
// If AssumeChannelValid is present, then we are unable to perform any
1262-
// of the expensive checks below, so we'll short-circuit our path
1263-
// straight to adding the edge to our graph. If the passed
1264-
// ShortChannelID is an alias, then we'll skip validation as it will
1265-
// not map to a legitimate tx. This is not a DoS vector as only we can
1266-
// add an alias ChannelAnnouncement from the gossiper.
1187+
if err := b.cfg.Graph.AddChannelEdge(edge, op...); err != nil {
1188+
return fmt.Errorf("unable to add edge: %w", err)
1189+
}
1190+
1191+
b.stats.incNumEdgesDiscovered()
1192+
1193+
// If AssumeChannelValid is present, of if the SCID is an alias, then
1194+
// the gossiper would not have done the expensive work of fetching
1195+
// a funding transaction and validating it. So we won't have the channel
1196+
// capacity nor the funding script. So we just log and return here.
12671197
scid := lnwire.NewShortChanIDFromInt(edge.ChannelID)
12681198
if b.cfg.AssumeChannelValid || b.cfg.IsAlias(scid) {
1269-
err := b.cfg.Graph.AddChannelEdge(edge, op...)
1270-
if err != nil {
1271-
return fmt.Errorf("unable to add edge: %w", err)
1272-
}
12731199
log.Tracef("New channel discovered! Link connects %x and %x "+
12741200
"with ChannelID(%v)", edge.NodeKey1Bytes,
12751201
edge.NodeKey2Bytes, edge.ChannelID)
1276-
b.stats.incNumEdgesDiscovered()
12771202

12781203
return nil
12791204
}
12801205

1281-
// Before we can add the channel to the channel graph, we need to obtain
1282-
// the full funding outpoint that's encoded within the channel ID.
1283-
channelID := lnwire.NewShortChanIDFromInt(edge.ChannelID)
1284-
fundingTx, err := lnwallet.FetchFundingTxWrapper(
1285-
b.cfg.Chain, &channelID, b.quit,
1286-
)
1287-
if err != nil {
1288-
//nolint:ll
1289-
//
1290-
// In order to ensure we don't erroneously mark a channel as a
1291-
// zombie due to an RPC failure, we'll attempt to string match
1292-
// for the relevant errors.
1293-
//
1294-
// * btcd:
1295-
// * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1316
1296-
// * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1086
1297-
// * bitcoind:
1298-
// * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L770
1299-
// * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L954
1300-
switch {
1301-
case strings.Contains(err.Error(), "not found"):
1302-
fallthrough
1303-
1304-
case strings.Contains(err.Error(), "out of range"):
1305-
// If the funding transaction isn't found at all, then
1306-
// we'll mark the edge itself as a zombie so we don't
1307-
// continue to request it. We use the "zero key" for
1308-
// both node pubkeys so this edge can't be resurrected.
1309-
zErr := b.addZombieEdge(edge.ChannelID)
1310-
if zErr != nil {
1311-
return zErr
1312-
}
1313-
1314-
default:
1315-
}
1316-
1317-
return fmt.Errorf("%w: %w", ErrNoFundingTransaction, err)
1318-
}
1319-
1320-
// Recreate witness output to be sure that declared in channel edge
1321-
// bitcoin keys and channel value corresponds to the reality.
1322-
fundingPkScript, err := makeFundingScript(
1323-
edge.BitcoinKey1Bytes[:], edge.BitcoinKey2Bytes[:],
1324-
edge.Features, edge.TapscriptRoot,
1325-
)
1206+
log.Debugf("New channel discovered! Link connects %x and %x with "+
1207+
"ChannelPoint(%v): chan_id=%v, capacity=%v", edge.NodeKey1Bytes,
1208+
edge.NodeKey2Bytes, edge.ChannelPoint, edge.ChannelID,
1209+
edge.Capacity)
1210+
1211+
// Otherwise, then we expect the funding script to be present on the
1212+
// edge since it would have been fetched when the gossiper validated the
1213+
// announcement.
1214+
fundingPkScript, err := edge.FundingScript.UnwrapOrErr(fmt.Errorf(
1215+
"expected the funding transaction script to be set",
1216+
))
13261217
if err != nil {
13271218
return err
13281219
}
13291220

1330-
// Next we'll validate that this channel is actually well formed. If
1331-
// this check fails, then this channel either doesn't exist, or isn't
1332-
// the one that was meant to be created according to the passed channel
1333-
// proofs.
1334-
fundingPoint, err := chanvalidate.Validate(
1335-
&chanvalidate.Context{
1336-
Locator: &chanvalidate.ShortChanIDChanLocator{
1337-
ID: channelID,
1338-
},
1339-
MultiSigPkScript: fundingPkScript,
1340-
FundingTx: fundingTx,
1341-
},
1342-
)
1343-
if err != nil {
1344-
// Mark the edge as a zombie so we won't try to re-validate it
1345-
// on start up.
1346-
if err := b.addZombieEdge(edge.ChannelID); err != nil {
1347-
return err
1348-
}
1349-
1350-
return fmt.Errorf("%w: %w", ErrInvalidFundingOutput, err)
1351-
}
1352-
1353-
// Now that we have the funding outpoint of the channel, ensure
1354-
// that it hasn't yet been spent. If so, then this channel has
1355-
// been closed so we'll ignore it.
1356-
chanUtxo, err := b.cfg.Chain.GetUtxo(
1357-
fundingPoint, fundingPkScript, channelID.BlockHeight, b.quit,
1358-
)
1359-
if err != nil {
1360-
if errors.Is(err, btcwallet.ErrOutputSpent) {
1361-
zErr := b.addZombieEdge(edge.ChannelID)
1362-
if zErr != nil {
1363-
return zErr
1364-
}
1365-
}
1366-
1367-
return fmt.Errorf("%w: unable to fetch utxo for chan_id=%v, "+
1368-
"chan_point=%v: %w", ErrChannelSpent, scid.ToUint64(),
1369-
fundingPoint, err)
1370-
}
1371-
1372-
// TODO(roasbeef): this is a hack, needs to be removed after commitment
1373-
// fees are dynamic.
1374-
edge.Capacity = btcutil.Amount(chanUtxo.Value)
1375-
edge.ChannelPoint = *fundingPoint
1376-
if err := b.cfg.Graph.AddChannelEdge(edge, op...); err != nil {
1377-
return errors.Errorf("unable to add edge: %v", err)
1378-
}
1379-
1380-
log.Debugf("New channel discovered! Link connects %x and %x with "+
1381-
"ChannelPoint(%v): chan_id=%v, capacity=%v", edge.NodeKey1Bytes,
1382-
edge.NodeKey2Bytes, fundingPoint, edge.ChannelID, edge.Capacity)
1383-
b.stats.incNumEdgesDiscovered()
1384-
13851221
// As a new edge has been added to the channel graph, we'll update the
13861222
// current UTXO filter within our active FilteredChainView so we are
13871223
// notified if/when this channel is closed.
13881224
filterUpdate := []graphdb.EdgePoint{
13891225
{
13901226
FundingPkScript: fundingPkScript,
1391-
OutPoint: *fundingPoint,
1227+
OutPoint: edge.ChannelPoint,
13921228
},
13931229
}
13941230

@@ -1630,6 +1466,16 @@ func (b *Builder) IsKnownEdge(chanID lnwire.ShortChannelID) bool {
16301466
return exists || isZombie
16311467
}
16321468

1469+
// IsZombieEdge returns true if the graph source has marked the given channel ID
1470+
// as a zombie edge.
1471+
//
1472+
// NOTE: This method is part of the ChannelGraphSource interface.
1473+
func (b *Builder) IsZombieEdge(chanID lnwire.ShortChannelID) (bool, error) {
1474+
_, _, _, isZombie, err := b.cfg.Graph.HasChannelEdge(chanID.ToUint64())
1475+
1476+
return isZombie, err
1477+
}
1478+
16331479
// IsStaleEdgePolicy returns true if the graph source has a channel edge for
16341480
// the passed channel ID (and flags) that have a more recent timestamp.
16351481
//

0 commit comments

Comments
 (0)