4
4
"bytes"
5
5
"errors"
6
6
"fmt"
7
+ "strings"
7
8
"sync"
8
9
"sync/atomic"
9
10
"time"
@@ -28,6 +29,8 @@ import (
28
29
"github.com/lightningnetwork/lnd/lnpeer"
29
30
"github.com/lightningnetwork/lnd/lnutils"
30
31
"github.com/lightningnetwork/lnd/lnwallet"
32
+ "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
33
+ "github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
31
34
"github.com/lightningnetwork/lnd/lnwire"
32
35
"github.com/lightningnetwork/lnd/multimutex"
33
36
"github.com/lightningnetwork/lnd/netann"
80
83
// the remote peer.
81
84
ErrGossipSyncerNotFound = errors .New ("gossip syncer not found" )
82
85
86
+ // ErrNoFundingTransaction is returned when we are unable to find the
87
+ // funding transaction described by the short channel ID on chain.
88
+ ErrNoFundingTransaction = errors .New (
89
+ "unable to find the funding transaction" ,
90
+ )
91
+
92
+ // ErrInvalidFundingOutput is returned if the channel funding output
93
+ // fails validation.
94
+ ErrInvalidFundingOutput = errors .New (
95
+ "channel funding output validation failed" ,
96
+ )
97
+
98
+ // ErrChannelSpent is returned when we go to validate a channel, but
99
+ // the purported funding output has actually already been spent on
100
+ // chain.
101
+ ErrChannelSpent = errors .New ("channel output has been spent" )
102
+
83
103
// emptyPubkey is used to compare compressed pubkeys against an empty
84
104
// byte array.
85
105
emptyPubkey [33 ]byte
@@ -2078,7 +2098,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
2078
2098
// the existence of a channel and not yet the routing policies in
2079
2099
// either direction of the channel.
2080
2100
case * lnwire.ChannelAnnouncement1 :
2081
- return d .handleChanAnnouncement (nMsg , msg , schedulerOp )
2101
+ return d .handleChanAnnouncement (nMsg , msg , schedulerOp ... )
2082
2102
2083
2103
// A new authenticated channel edge update has arrived. This indicates
2084
2104
// that the directional information for an already known channel has
@@ -2459,7 +2479,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg,
2459
2479
// handleChanAnnouncement processes a new channel announcement.
2460
2480
func (d * AuthenticatedGossiper ) handleChanAnnouncement (nMsg * networkMsg ,
2461
2481
ann * lnwire.ChannelAnnouncement1 ,
2462
- ops [] batch.SchedulerOption ) ([]networkMsg , bool ) {
2482
+ ops ... batch.SchedulerOption ) ([]networkMsg , bool ) {
2463
2483
2464
2484
scid := ann .ShortChannelID
2465
2485
@@ -2642,23 +2662,116 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
2642
2662
// announcement at the same time.
2643
2663
d .channelMtx .Lock (scid .ToUint64 ())
2644
2664
2645
- // We only make use of the funding script later on during funding
2646
- // transaction validation if AssumeChannelValid is not true.
2647
- if ! (d .cfg .AssumeChannelValid || d .cfg .IsAlias (scid )) {
2648
- fundingPkScript , err := makeFundingScript (
2649
- ann .BitcoinKey1 [:], ann .BitcoinKey2 [:], ann .Features ,
2650
- tapscriptRoot ,
2665
+ // If AssumeChannelValid is present, then we are unable to perform any
2666
+ // of the expensive checks below, so we'll short-circuit our path
2667
+ // straight to adding the edge to our graph. If the passed
2668
+ // ShortChannelID is an alias, then we'll skip validation as it will
2669
+ // not map to a legitimate tx. This is not a DoS vector as only we can
2670
+ // add an alias ChannelAnnouncement from the gossiper.
2671
+ if ! (d .cfg .AssumeChannelValid || d .cfg .IsAlias (scid )) { //nolint:nestif
2672
+ op , capacity , script , err := d .validateFundingTransaction (
2673
+ ann , tapscriptRoot ,
2651
2674
)
2652
2675
if err != nil {
2653
2676
defer d .channelMtx .Unlock (scid .ToUint64 ())
2654
2677
2655
- log .Errorf ("Unable to make funding script %v" , err )
2678
+ switch {
2679
+ case errors .Is (err , ErrNoFundingTransaction ),
2680
+ errors .Is (err , ErrInvalidFundingOutput ):
2681
+
2682
+ key := newRejectCacheKey (
2683
+ scid .ToUint64 (),
2684
+ sourceToPub (nMsg .source ),
2685
+ )
2686
+ _ , _ = d .recentRejects .Put (
2687
+ key , & cachedReject {},
2688
+ )
2689
+
2690
+ // Increment the peer's ban score. We check
2691
+ // isRemote so we don't actually ban the peer in
2692
+ // case of a local bug.
2693
+ if nMsg .isRemote {
2694
+ d .banman .incrementBanScore (
2695
+ nMsg .peer .PubKey (),
2696
+ )
2697
+ }
2698
+
2699
+ case errors .Is (err , ErrChannelSpent ):
2700
+ key := newRejectCacheKey (
2701
+ scid .ToUint64 (),
2702
+ sourceToPub (nMsg .source ),
2703
+ )
2704
+ _ , _ = d .recentRejects .Put (key , & cachedReject {})
2705
+
2706
+ // Since this channel has already been closed,
2707
+ // we'll add it to the graph's closed channel
2708
+ // index such that we won't attempt to do
2709
+ // expensive validation checks on it again.
2710
+ // TODO: Populate the ScidCloser by using closed
2711
+ // channel notifications.
2712
+ dbErr := d .cfg .ScidCloser .PutClosedScid (scid )
2713
+ if dbErr != nil {
2714
+ log .Errorf ("failed to mark scid(%v) " +
2715
+ "as closed: %v" , scid , dbErr )
2716
+
2717
+ nMsg .err <- dbErr
2718
+
2719
+ return nil , false
2720
+ }
2721
+
2722
+ // Increment the peer's ban score. We check
2723
+ // isRemote so we don't accidentally ban
2724
+ // ourselves in case of a bug.
2725
+ if nMsg .isRemote {
2726
+ d .banman .incrementBanScore (
2727
+ nMsg .peer .PubKey (),
2728
+ )
2729
+ }
2730
+
2731
+ default :
2732
+ // Otherwise, this is just a regular rejected
2733
+ // edge.
2734
+ key := newRejectCacheKey (
2735
+ scid .ToUint64 (),
2736
+ sourceToPub (nMsg .source ),
2737
+ )
2738
+ _ , _ = d .recentRejects .Put (key , & cachedReject {})
2739
+ }
2740
+
2741
+ if ! nMsg .isRemote {
2742
+ log .Errorf ("failed to add edge for local " +
2743
+ "channel: %v" , err )
2744
+ nMsg .err <- err
2745
+
2746
+ return nil , false
2747
+ }
2748
+
2749
+ shouldDc , dcErr := d .ShouldDisconnect (
2750
+ nMsg .peer .IdentityKey (),
2751
+ )
2752
+ if dcErr != nil {
2753
+ log .Errorf ("failed to check if we should " +
2754
+ "disconnect peer: %v" , dcErr )
2755
+ nMsg .err <- dcErr
2756
+
2757
+ return nil , false
2758
+ }
2759
+
2760
+ if shouldDc {
2761
+ nMsg .peer .Disconnect (ErrPeerBanned )
2762
+ }
2763
+
2656
2764
nMsg .err <- err
2657
2765
2658
2766
return nil , false
2659
2767
}
2660
2768
2661
- edge .FundingScript = fn .Some (fundingPkScript )
2769
+ edge .FundingScript = fn .Some (script )
2770
+
2771
+ // TODO(roasbeef): this is a hack, needs to be removed after
2772
+ // commitment fees are dynamic.
2773
+ edge .Capacity = capacity
2774
+ edge .ChannelPoint = op
2662
2775
}
2663
2776
2664
2777
log .Debugf ("Adding edge for short_chan_id: %v" , scid .ToUint64 ())
@@ -2676,8 +2789,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
2676
2789
// If the edge was rejected due to already being known, then it
2677
2790
// may be the case that this new message has a fresh channel
2678
2791
// proof, so we'll check.
2679
- switch {
2680
- case graph .IsError (err , graph .ErrIgnored ):
2792
+ if graph .IsError (err , graph .ErrIgnored ) {
2681
2793
// Attempt to process the rejected message to see if we
2682
2794
// get any new announcements.
2683
2795
anns , rErr := d .processRejectedEdge (ann , proof )
@@ -2690,6 +2802,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
2690
2802
_ , _ = d .recentRejects .Put (key , cr )
2691
2803
2692
2804
nMsg .err <- rErr
2805
+
2693
2806
return nil , false
2694
2807
}
2695
2808
@@ -2705,62 +2818,15 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
2705
2818
nMsg .err <- nil
2706
2819
2707
2820
return anns , true
2708
-
2709
- case errors .Is (err , graph .ErrNoFundingTransaction ),
2710
- errors .Is (err , graph .ErrInvalidFundingOutput ):
2711
-
2712
- key := newRejectCacheKey (
2713
- scid .ToUint64 (),
2714
- sourceToPub (nMsg .source ),
2715
- )
2716
- _ , _ = d .recentRejects .Put (key , & cachedReject {})
2717
-
2718
- // Increment the peer's ban score. We check isRemote
2719
- // so we don't actually ban the peer in case of a local
2720
- // bug.
2721
- if nMsg .isRemote {
2722
- d .banman .incrementBanScore (nMsg .peer .PubKey ())
2723
- }
2724
-
2725
- case errors .Is (err , graph .ErrChannelSpent ):
2726
- key := newRejectCacheKey (
2727
- scid .ToUint64 (),
2728
- sourceToPub (nMsg .source ),
2729
- )
2730
- _ , _ = d .recentRejects .Put (key , & cachedReject {})
2731
-
2732
- // Since this channel has already been closed, we'll
2733
- // add it to the graph's closed channel index such that
2734
- // we won't attempt to do expensive validation checks
2735
- // on it again.
2736
- // TODO: Populate the ScidCloser by using closed
2737
- // channel notifications.
2738
- dbErr := d .cfg .ScidCloser .PutClosedScid (scid )
2739
- if dbErr != nil {
2740
- log .Errorf ("failed to mark scid(%v) as " +
2741
- "closed: %v" , scid , dbErr )
2742
-
2743
- nMsg .err <- dbErr
2744
-
2745
- return nil , false
2746
- }
2747
-
2748
- // Increment the peer's ban score. We check isRemote
2749
- // so we don't accidentally ban ourselves in case of a
2750
- // bug.
2751
- if nMsg .isRemote {
2752
- d .banman .incrementBanScore (nMsg .peer .PubKey ())
2753
- }
2754
-
2755
- default :
2756
- // Otherwise, this is just a regular rejected edge.
2757
- key := newRejectCacheKey (
2758
- scid .ToUint64 (),
2759
- sourceToPub (nMsg .source ),
2760
- )
2761
- _ , _ = d .recentRejects .Put (key , & cachedReject {})
2762
2821
}
2763
2822
2823
+ // Otherwise, this is just a regular rejected edge.
2824
+ key := newRejectCacheKey (
2825
+ scid .ToUint64 (),
2826
+ sourceToPub (nMsg .source ),
2827
+ )
2828
+ _ , _ = d .recentRejects .Put (key , & cachedReject {})
2829
+
2764
2830
if ! nMsg .isRemote {
2765
2831
log .Errorf ("failed to add edge for local channel: %v" ,
2766
2832
err )
@@ -3622,6 +3688,114 @@ func (d *AuthenticatedGossiper) ShouldDisconnect(pubkey *btcec.PublicKey) (
3622
3688
return false , nil
3623
3689
}
3624
3690
3691
+ // validateFundingTransaction fetches the channel announcements claimed funding
3692
+ // transaction from chain to ensure that it exists, is not spent and matches
3693
+ // the channel announcement proof. The transaction's outpoint and value are
3694
+ // returned if we can glean them from the work done in this method.
3695
+ func (d * AuthenticatedGossiper ) validateFundingTransaction (
3696
+ ann * lnwire.ChannelAnnouncement1 ,
3697
+ tapscriptRoot fn.Option [chainhash.Hash ]) (wire.OutPoint , btcutil.Amount ,
3698
+ []byte , error ) {
3699
+
3700
+ scid := ann .ShortChannelID
3701
+
3702
+ // Before we can add the channel to the channel graph, we need to obtain
3703
+ // the full funding outpoint that's encoded within the channel ID.
3704
+ fundingTx , err := lnwallet .FetchFundingTxWrapper (
3705
+ d .cfg .ChainIO , & scid , d .quit ,
3706
+ )
3707
+ if err != nil {
3708
+ //nolint:ll
3709
+ //
3710
+ // In order to ensure we don't erroneously mark a channel as a
3711
+ // zombie due to an RPC failure, we'll attempt to string match
3712
+ // for the relevant errors.
3713
+ //
3714
+ // * btcd:
3715
+ // * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1316
3716
+ // * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1086
3717
+ // * bitcoind:
3718
+ // * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L770
3719
+ // * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L954
3720
+ switch {
3721
+ case strings .Contains (err .Error (), "not found" ):
3722
+ fallthrough
3723
+
3724
+ case strings .Contains (err .Error (), "out of range" ):
3725
+ // If the funding transaction isn't found at all, then
3726
+ // we'll mark the edge itself as a zombie so we don't
3727
+ // continue to request it. We use the "zero key" for
3728
+ // both node pubkeys so this edge can't be resurrected.
3729
+ zErr := d .cfg .Graph .MarkZombieEdge (scid .ToUint64 ())
3730
+ if zErr != nil {
3731
+ return wire.OutPoint {}, 0 , nil , zErr
3732
+ }
3733
+
3734
+ default :
3735
+ }
3736
+
3737
+ return wire.OutPoint {}, 0 , nil , fmt .Errorf ("%w: %w" ,
3738
+ ErrNoFundingTransaction , err )
3739
+ }
3740
+
3741
+ // Recreate witness output to be sure that declared in channel edge
3742
+ // bitcoin keys and channel value corresponds to the reality.
3743
+ fundingPkScript , err := makeFundingScript (
3744
+ ann .BitcoinKey1 [:], ann .BitcoinKey2 [:], ann .Features ,
3745
+ tapscriptRoot ,
3746
+ )
3747
+ if err != nil {
3748
+ return wire.OutPoint {}, 0 , nil , err
3749
+ }
3750
+
3751
+ // Next we'll validate that this channel is actually well formed. If
3752
+ // this check fails, then this channel either doesn't exist, or isn't
3753
+ // the one that was meant to be created according to the passed channel
3754
+ // proofs.
3755
+ fundingPoint , err := chanvalidate .Validate (
3756
+ & chanvalidate.Context {
3757
+ Locator : & chanvalidate.ShortChanIDChanLocator {
3758
+ ID : scid ,
3759
+ },
3760
+ MultiSigPkScript : fundingPkScript ,
3761
+ FundingTx : fundingTx ,
3762
+ },
3763
+ )
3764
+ if err != nil {
3765
+ // Mark the edge as a zombie so we won't try to re-validate it
3766
+ // on start up.
3767
+ zErr := d .cfg .Graph .MarkZombieEdge (scid .ToUint64 ())
3768
+ if zErr != nil {
3769
+ return wire.OutPoint {}, 0 , nil , zErr
3770
+ }
3771
+
3772
+ return wire.OutPoint {}, 0 , nil , fmt .Errorf ("%w: %w" ,
3773
+ ErrInvalidFundingOutput , err )
3774
+ }
3775
+
3776
+ // Now that we have the funding outpoint of the channel, ensure
3777
+ // that it hasn't yet been spent. If so, then this channel has
3778
+ // been closed so we'll ignore it.
3779
+ chanUtxo , err := d .cfg .ChainIO .GetUtxo (
3780
+ fundingPoint , fundingPkScript , scid .BlockHeight , d .quit ,
3781
+ )
3782
+ if err != nil {
3783
+ if errors .Is (err , btcwallet .ErrOutputSpent ) {
3784
+ zErr := d .cfg .Graph .MarkZombieEdge (scid .ToUint64 ())
3785
+ if zErr != nil {
3786
+ return wire.OutPoint {}, 0 , nil , zErr
3787
+ }
3788
+ }
3789
+
3790
+ return wire.OutPoint {}, 0 , nil , fmt .Errorf ("%w: unable to " +
3791
+ "fetch utxo for chan_id=%v, chan_point=%v: %w" ,
3792
+ ErrChannelSpent , scid .ToUint64 (), fundingPoint , err )
3793
+ }
3794
+
3795
+ return * fundingPoint , btcutil .Amount (chanUtxo .Value ), fundingPkScript ,
3796
+ nil
3797
+ }
3798
+
3625
3799
// makeFundingScript is used to make the funding script for both segwit v0 and
3626
3800
// segwit v1 (taproot) channels.
3627
3801
func makeFundingScript (bitcoinKey1 , bitcoinKey2 []byte ,
0 commit comments