11package graph
22
33import (
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.
10951021type 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