diff --git a/discovery/gossiper.go b/discovery/gossiper.go index d9288f766..2e4f5ca42 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -23,6 +23,7 @@ import ( "github.com/lightningnetwork/lnd/graph" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnutils" @@ -2619,6 +2620,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg, // If there were any optional message fields provided, we'll include // them in its serialized disk representation now. + var tapscriptRoot fn.Option[chainhash.Hash] if nMsg.optionalMsgFields != nil { if nMsg.optionalMsgFields.capacity != nil { edge.Capacity = *nMsg.optionalMsgFields.capacity @@ -2629,7 +2631,24 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg, } // Optional tapscript root for custom channels. - edge.TapscriptRoot = nMsg.optionalMsgFields.tapscriptRoot + tapscriptRoot = nMsg.optionalMsgFields.tapscriptRoot + } + + // We only make use of the funding script later on during funding + // transaction validation if AssumeChannelValid is not true. + if !(d.cfg.AssumeChannelValid || d.cfg.IsAlias(scid)) { + fundingPkScript, err := makeFundingScript( + ann.BitcoinKey1[:], ann.BitcoinKey2[:], ann.Features, + tapscriptRoot, + ) + if err != nil { + log.Errorf("Unable to make funding script %v", err) + nMsg.err <- err + + return nil, false + } + + edge.FundingScript = fn.Some(fundingPkScript) } log.Debugf("Adding edge for short_chan_id: %v", scid.ToUint64()) @@ -3598,3 +3617,57 @@ func (d *AuthenticatedGossiper) ShouldDisconnect(pubkey *btcec.PublicKey) ( return false, nil } + +// makeFundingScript is used to make the funding script for both segwit v0 and +// segwit v1 (taproot) channels. +func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte, + features *lnwire.RawFeatureVector, + tapscriptRoot fn.Option[chainhash.Hash]) ([]byte, error) { + + legacyFundingScript := func() ([]byte, error) { + witnessScript, err := input.GenMultiSigScript( + bitcoinKey1, bitcoinKey2, + ) + if err != nil { + return nil, err + } + pkScript, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return nil, err + } + + return pkScript, nil + } + + if features.IsEmpty() { + return legacyFundingScript() + } + + chanFeatureBits := lnwire.NewFeatureVector(features, lnwire.Features) + if chanFeatureBits.HasFeature( + lnwire.SimpleTaprootChannelsOptionalStaging, + ) { + + pubKey1, err := btcec.ParsePubKey(bitcoinKey1) + if err != nil { + return nil, err + } + pubKey2, err := btcec.ParsePubKey(bitcoinKey2) + if err != nil { + return nil, err + } + + fundingScript, _, err := input.GenTaprootFundingScript( + pubKey1, pubKey2, 0, tapscriptRoot, + ) + if err != nil { + return nil, err + } + + // TODO(roasbeef): add tapscript root to gossip v1.5 + + return fundingScript, nil + } + + return legacyFundingScript() +} diff --git a/graph/builder.go b/graph/builder.go index 946e9a904..94eccf6d0 100644 --- a/graph/builder.go +++ b/graph/builder.go @@ -1,7 +1,6 @@ package graph import ( - "bytes" "fmt" "strings" "sync" @@ -10,15 +9,12 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/fn/v2" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" @@ -1024,72 +1020,6 @@ func (b *Builder) MarkZombieEdge(chanID uint64) error { return nil } -// makeFundingScript is used to make the funding script for both segwit v0 and -// segwit v1 (taproot) channels. -// -// TODO(roasbeef: export and use elsewhere? -func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte, chanFeatures []byte, - tapscriptRoot fn.Option[chainhash.Hash]) ([]byte, error) { - - legacyFundingScript := func() ([]byte, error) { - witnessScript, err := input.GenMultiSigScript( - bitcoinKey1, bitcoinKey2, - ) - if err != nil { - return nil, err - } - pkScript, err := input.WitnessScriptHash(witnessScript) - if err != nil { - return nil, err - } - - return pkScript, nil - } - - if len(chanFeatures) == 0 { - return legacyFundingScript() - } - - // In order to make the correct funding script, we'll need to parse the - // chanFeatures bytes into a feature vector we can interact with. - rawFeatures := lnwire.NewRawFeatureVector() - err := rawFeatures.Decode(bytes.NewReader(chanFeatures)) - if err != nil { - return nil, fmt.Errorf("unable to parse chan feature "+ - "bits: %w", err) - } - - chanFeatureBits := lnwire.NewFeatureVector( - rawFeatures, lnwire.Features, - ) - if chanFeatureBits.HasFeature( - lnwire.SimpleTaprootChannelsOptionalStaging, - ) { - - pubKey1, err := btcec.ParsePubKey(bitcoinKey1) - if err != nil { - return nil, err - } - pubKey2, err := btcec.ParsePubKey(bitcoinKey2) - if err != nil { - return nil, err - } - - fundingScript, _, err := input.GenTaprootFundingScript( - pubKey1, pubKey2, 0, tapscriptRoot, - ) - if err != nil { - return nil, err - } - - // TODO(roasbeef): add tapscript root to gossip v1.5 - - return fundingScript, nil - } - - return legacyFundingScript() -} - // routingMsg couples a routing related routing topology update to the // error channel. type routingMsg struct { @@ -1278,6 +1208,16 @@ func (b *Builder) addEdge(edge *models.ChannelEdgeInfo, return nil } + // If AssumeChannelValid is false, then we expect the funding script to + // be present on the edge since it would have been fetched when the + // gossiper validated the announcement. + fundingPkScript, err := edge.FundingScript.UnwrapOrErr(fmt.Errorf( + "expected the funding transaction script to be set", + )) + if err != nil { + return err + } + // Before we can add the channel to the channel graph, we need to obtain // the full funding outpoint that's encoded within the channel ID. channelID := lnwire.NewShortChanIDFromInt(edge.ChannelID) @@ -1317,16 +1257,6 @@ func (b *Builder) addEdge(edge *models.ChannelEdgeInfo, return fmt.Errorf("%w: %w", ErrNoFundingTransaction, err) } - // Recreate witness output to be sure that declared in channel edge - // bitcoin keys and channel value corresponds to the reality. - fundingPkScript, err := makeFundingScript( - edge.BitcoinKey1Bytes[:], edge.BitcoinKey2Bytes[:], - edge.Features, edge.TapscriptRoot, - ) - if err != nil { - return err - } - // Next we'll validate that this channel is actually well formed. If // this check fails, then this channel either doesn't exist, or isn't // the one that was meant to be created according to the passed channel diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go index 6aa67acc6..39e3b196f 100644 --- a/graph/db/models/channel_edge_info.go +++ b/graph/db/models/channel_edge_info.go @@ -63,10 +63,11 @@ type ChannelEdgeInfo struct { // the value output in the outpoint that created this channel. Capacity btcutil.Amount - // TapscriptRoot is the optional Merkle root of the tapscript tree if - // this channel is a taproot channel that also commits to a tapscript - // tree (custom channel). - TapscriptRoot fn.Option[chainhash.Hash] + // FundingScript holds the script of the channel's funding transaction. + // + // NOTE: this is not currently persisted and so will not be present if + // the edge object is loaded from the database. + FundingScript fn.Option[[]byte] // ExtraOpaqueData is the set of data that was appended to this // message, some of which we may not actually know how to iterate or