mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-29 03:01:52 +01:00
Merge pull request #9478 from ellemouton/graph3
discovery+graph: move funding tx validation to the gossiper
This commit is contained in:
commit
9c2c95d46f
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -23,10 +24,13 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/graph"
|
"github.com/lightningnetwork/lnd/graph"
|
||||||
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
||||||
"github.com/lightningnetwork/lnd/graph/db/models"
|
"github.com/lightningnetwork/lnd/graph/db/models"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
"github.com/lightningnetwork/lnd/lnutils"
|
"github.com/lightningnetwork/lnd/lnutils"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/multimutex"
|
"github.com/lightningnetwork/lnd/multimutex"
|
||||||
"github.com/lightningnetwork/lnd/netann"
|
"github.com/lightningnetwork/lnd/netann"
|
||||||
@ -79,6 +83,23 @@ var (
|
|||||||
// the remote peer.
|
// the remote peer.
|
||||||
ErrGossipSyncerNotFound = errors.New("gossip syncer not found")
|
ErrGossipSyncerNotFound = errors.New("gossip syncer not found")
|
||||||
|
|
||||||
|
// ErrNoFundingTransaction is returned when we are unable to find the
|
||||||
|
// funding transaction described by the short channel ID on chain.
|
||||||
|
ErrNoFundingTransaction = errors.New(
|
||||||
|
"unable to find the funding transaction",
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidFundingOutput is returned if the channel funding output
|
||||||
|
// fails validation.
|
||||||
|
ErrInvalidFundingOutput = errors.New(
|
||||||
|
"channel funding output validation failed",
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrChannelSpent is returned when we go to validate a channel, but
|
||||||
|
// the purported funding output has actually already been spent on
|
||||||
|
// chain.
|
||||||
|
ErrChannelSpent = errors.New("channel output has been spent")
|
||||||
|
|
||||||
// emptyPubkey is used to compare compressed pubkeys against an empty
|
// emptyPubkey is used to compare compressed pubkeys against an empty
|
||||||
// byte array.
|
// byte array.
|
||||||
emptyPubkey [33]byte
|
emptyPubkey [33]byte
|
||||||
@ -364,6 +385,11 @@ type Config struct {
|
|||||||
// updates for a channel and returns true if the channel should be
|
// updates for a channel and returns true if the channel should be
|
||||||
// considered a zombie based on these timestamps.
|
// considered a zombie based on these timestamps.
|
||||||
IsStillZombieChannel func(time.Time, time.Time) bool
|
IsStillZombieChannel func(time.Time, time.Time) bool
|
||||||
|
|
||||||
|
// AssumeChannelValid toggles whether the gossiper will check for
|
||||||
|
// spent-ness of channel outpoints. For neutrino, this saves long
|
||||||
|
// rescans from blocking initial usage of the daemon.
|
||||||
|
AssumeChannelValid bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// processedNetworkMsg is a wrapper around networkMsg and a boolean. It is
|
// processedNetworkMsg is a wrapper around networkMsg and a boolean. It is
|
||||||
@ -2072,7 +2098,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
|
|||||||
// the existence of a channel and not yet the routing policies in
|
// the existence of a channel and not yet the routing policies in
|
||||||
// either direction of the channel.
|
// either direction of the channel.
|
||||||
case *lnwire.ChannelAnnouncement1:
|
case *lnwire.ChannelAnnouncement1:
|
||||||
return d.handleChanAnnouncement(nMsg, msg, schedulerOp)
|
return d.handleChanAnnouncement(nMsg, msg, schedulerOp...)
|
||||||
|
|
||||||
// A new authenticated channel edge update has arrived. This indicates
|
// A new authenticated channel edge update has arrived. This indicates
|
||||||
// that the directional information for an already known channel has
|
// that the directional information for an already known channel has
|
||||||
@ -2453,7 +2479,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg,
|
|||||||
// handleChanAnnouncement processes a new channel announcement.
|
// handleChanAnnouncement processes a new channel announcement.
|
||||||
func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||||
ann *lnwire.ChannelAnnouncement1,
|
ann *lnwire.ChannelAnnouncement1,
|
||||||
ops []batch.SchedulerOption) ([]networkMsg, bool) {
|
ops ...batch.SchedulerOption) ([]networkMsg, bool) {
|
||||||
|
|
||||||
scid := ann.ShortChannelID
|
scid := ann.ShortChannelID
|
||||||
|
|
||||||
@ -2614,6 +2640,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
|||||||
|
|
||||||
// If there were any optional message fields provided, we'll include
|
// If there were any optional message fields provided, we'll include
|
||||||
// them in its serialized disk representation now.
|
// them in its serialized disk representation now.
|
||||||
|
var tapscriptRoot fn.Option[chainhash.Hash]
|
||||||
if nMsg.optionalMsgFields != nil {
|
if nMsg.optionalMsgFields != nil {
|
||||||
if nMsg.optionalMsgFields.capacity != nil {
|
if nMsg.optionalMsgFields.capacity != nil {
|
||||||
edge.Capacity = *nMsg.optionalMsgFields.capacity
|
edge.Capacity = *nMsg.optionalMsgFields.capacity
|
||||||
@ -2624,7 +2651,127 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Optional tapscript root for custom channels.
|
// Optional tapscript root for custom channels.
|
||||||
edge.TapscriptRoot = nMsg.optionalMsgFields.tapscriptRoot
|
tapscriptRoot = nMsg.optionalMsgFields.tapscriptRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before we start validation or add the edge to the database, we obtain
|
||||||
|
// the mutex for this channel ID. We do this to ensure no other
|
||||||
|
// goroutine has read the database and is now making decisions based on
|
||||||
|
// this DB state, before it writes to the DB. It also ensures that we
|
||||||
|
// don't perform the expensive validation check on the same channel
|
||||||
|
// announcement at the same time.
|
||||||
|
d.channelMtx.Lock(scid.ToUint64())
|
||||||
|
|
||||||
|
// If AssumeChannelValid is present, then we are unable to perform any
|
||||||
|
// of the expensive checks below, so we'll short-circuit our path
|
||||||
|
// straight to adding the edge to our graph. If the passed
|
||||||
|
// ShortChannelID is an alias, then we'll skip validation as it will
|
||||||
|
// not map to a legitimate tx. This is not a DoS vector as only we can
|
||||||
|
// add an alias ChannelAnnouncement from the gossiper.
|
||||||
|
if !(d.cfg.AssumeChannelValid || d.cfg.IsAlias(scid)) { //nolint:nestif
|
||||||
|
op, capacity, script, err := d.validateFundingTransaction(
|
||||||
|
ann, tapscriptRoot,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
defer d.channelMtx.Unlock(scid.ToUint64())
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, ErrNoFundingTransaction),
|
||||||
|
errors.Is(err, ErrInvalidFundingOutput):
|
||||||
|
|
||||||
|
key := newRejectCacheKey(
|
||||||
|
scid.ToUint64(),
|
||||||
|
sourceToPub(nMsg.source),
|
||||||
|
)
|
||||||
|
_, _ = d.recentRejects.Put(
|
||||||
|
key, &cachedReject{},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Increment the peer's ban score. We check
|
||||||
|
// isRemote so we don't actually ban the peer in
|
||||||
|
// case of a local bug.
|
||||||
|
if nMsg.isRemote {
|
||||||
|
d.banman.incrementBanScore(
|
||||||
|
nMsg.peer.PubKey(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case errors.Is(err, ErrChannelSpent):
|
||||||
|
key := newRejectCacheKey(
|
||||||
|
scid.ToUint64(),
|
||||||
|
sourceToPub(nMsg.source),
|
||||||
|
)
|
||||||
|
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
||||||
|
|
||||||
|
// Since this channel has already been closed,
|
||||||
|
// we'll add it to the graph's closed channel
|
||||||
|
// index such that we won't attempt to do
|
||||||
|
// expensive validation checks on it again.
|
||||||
|
// TODO: Populate the ScidCloser by using closed
|
||||||
|
// channel notifications.
|
||||||
|
dbErr := d.cfg.ScidCloser.PutClosedScid(scid)
|
||||||
|
if dbErr != nil {
|
||||||
|
log.Errorf("failed to mark scid(%v) "+
|
||||||
|
"as closed: %v", scid, dbErr)
|
||||||
|
|
||||||
|
nMsg.err <- dbErr
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the peer's ban score. We check
|
||||||
|
// isRemote so we don't accidentally ban
|
||||||
|
// ourselves in case of a bug.
|
||||||
|
if nMsg.isRemote {
|
||||||
|
d.banman.incrementBanScore(
|
||||||
|
nMsg.peer.PubKey(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Otherwise, this is just a regular rejected
|
||||||
|
// edge.
|
||||||
|
key := newRejectCacheKey(
|
||||||
|
scid.ToUint64(),
|
||||||
|
sourceToPub(nMsg.source),
|
||||||
|
)
|
||||||
|
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !nMsg.isRemote {
|
||||||
|
log.Errorf("failed to add edge for local "+
|
||||||
|
"channel: %v", err)
|
||||||
|
nMsg.err <- err
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldDc, dcErr := d.ShouldDisconnect(
|
||||||
|
nMsg.peer.IdentityKey(),
|
||||||
|
)
|
||||||
|
if dcErr != nil {
|
||||||
|
log.Errorf("failed to check if we should "+
|
||||||
|
"disconnect peer: %v", dcErr)
|
||||||
|
nMsg.err <- dcErr
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldDc {
|
||||||
|
nMsg.peer.Disconnect(ErrPeerBanned)
|
||||||
|
}
|
||||||
|
|
||||||
|
nMsg.err <- err
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
edge.FundingScript = fn.Some(script)
|
||||||
|
|
||||||
|
// TODO(roasbeef): this is a hack, needs to be removed after
|
||||||
|
// commitment fees are dynamic.
|
||||||
|
edge.Capacity = capacity
|
||||||
|
edge.ChannelPoint = op
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Adding edge for short_chan_id: %v", scid.ToUint64())
|
log.Debugf("Adding edge for short_chan_id: %v", scid.ToUint64())
|
||||||
@ -2632,12 +2779,6 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
|||||||
// We will add the edge to the channel router. If the nodes present in
|
// We will add the edge to the channel router. If the nodes present in
|
||||||
// this channel are not present in the database, a partial node will be
|
// this channel are not present in the database, a partial node will be
|
||||||
// added to represent each node while we wait for a node announcement.
|
// added to represent each node while we wait for a node announcement.
|
||||||
//
|
|
||||||
// Before we add the edge to the database, we obtain the mutex for this
|
|
||||||
// channel ID. We do this to ensure no other goroutine has read the
|
|
||||||
// database and is now making decisions based on this DB state, before
|
|
||||||
// it writes to the DB.
|
|
||||||
d.channelMtx.Lock(scid.ToUint64())
|
|
||||||
err = d.cfg.Graph.AddEdge(edge, ops...)
|
err = d.cfg.Graph.AddEdge(edge, ops...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Graph rejected edge for short_chan_id(%v): %v",
|
log.Debugf("Graph rejected edge for short_chan_id(%v): %v",
|
||||||
@ -2648,8 +2789,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
|||||||
// If the edge was rejected due to already being known, then it
|
// If the edge was rejected due to already being known, then it
|
||||||
// may be the case that this new message has a fresh channel
|
// may be the case that this new message has a fresh channel
|
||||||
// proof, so we'll check.
|
// proof, so we'll check.
|
||||||
switch {
|
if graph.IsError(err, graph.ErrIgnored) {
|
||||||
case graph.IsError(err, graph.ErrIgnored):
|
|
||||||
// Attempt to process the rejected message to see if we
|
// Attempt to process the rejected message to see if we
|
||||||
// get any new announcements.
|
// get any new announcements.
|
||||||
anns, rErr := d.processRejectedEdge(ann, proof)
|
anns, rErr := d.processRejectedEdge(ann, proof)
|
||||||
@ -2662,6 +2802,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
|||||||
_, _ = d.recentRejects.Put(key, cr)
|
_, _ = d.recentRejects.Put(key, cr)
|
||||||
|
|
||||||
nMsg.err <- rErr
|
nMsg.err <- rErr
|
||||||
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2677,62 +2818,15 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
|||||||
nMsg.err <- nil
|
nMsg.err <- nil
|
||||||
|
|
||||||
return anns, true
|
return anns, true
|
||||||
|
|
||||||
case errors.Is(err, graph.ErrNoFundingTransaction),
|
|
||||||
errors.Is(err, graph.ErrInvalidFundingOutput):
|
|
||||||
|
|
||||||
key := newRejectCacheKey(
|
|
||||||
scid.ToUint64(),
|
|
||||||
sourceToPub(nMsg.source),
|
|
||||||
)
|
|
||||||
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
|
||||||
|
|
||||||
// Increment the peer's ban score. We check isRemote
|
|
||||||
// so we don't actually ban the peer in case of a local
|
|
||||||
// bug.
|
|
||||||
if nMsg.isRemote {
|
|
||||||
d.banman.incrementBanScore(nMsg.peer.PubKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
case errors.Is(err, graph.ErrChannelSpent):
|
|
||||||
key := newRejectCacheKey(
|
|
||||||
scid.ToUint64(),
|
|
||||||
sourceToPub(nMsg.source),
|
|
||||||
)
|
|
||||||
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
|
||||||
|
|
||||||
// Since this channel has already been closed, we'll
|
|
||||||
// add it to the graph's closed channel index such that
|
|
||||||
// we won't attempt to do expensive validation checks
|
|
||||||
// on it again.
|
|
||||||
// TODO: Populate the ScidCloser by using closed
|
|
||||||
// channel notifications.
|
|
||||||
dbErr := d.cfg.ScidCloser.PutClosedScid(scid)
|
|
||||||
if dbErr != nil {
|
|
||||||
log.Errorf("failed to mark scid(%v) as "+
|
|
||||||
"closed: %v", scid, dbErr)
|
|
||||||
|
|
||||||
nMsg.err <- dbErr
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the peer's ban score. We check isRemote
|
|
||||||
// so we don't accidentally ban ourselves in case of a
|
|
||||||
// bug.
|
|
||||||
if nMsg.isRemote {
|
|
||||||
d.banman.incrementBanScore(nMsg.peer.PubKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Otherwise, this is just a regular rejected edge.
|
|
||||||
key := newRejectCacheKey(
|
|
||||||
scid.ToUint64(),
|
|
||||||
sourceToPub(nMsg.source),
|
|
||||||
)
|
|
||||||
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, this is just a regular rejected edge.
|
||||||
|
key := newRejectCacheKey(
|
||||||
|
scid.ToUint64(),
|
||||||
|
sourceToPub(nMsg.source),
|
||||||
|
)
|
||||||
|
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
||||||
|
|
||||||
if !nMsg.isRemote {
|
if !nMsg.isRemote {
|
||||||
log.Errorf("failed to add edge for local channel: %v",
|
log.Errorf("failed to add edge for local channel: %v",
|
||||||
err)
|
err)
|
||||||
@ -3593,3 +3687,165 @@ func (d *AuthenticatedGossiper) ShouldDisconnect(pubkey *btcec.PublicKey) (
|
|||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateFundingTransaction fetches the channel announcements claimed funding
|
||||||
|
// transaction from chain to ensure that it exists, is not spent and matches
|
||||||
|
// the channel announcement proof. The transaction's outpoint and value are
|
||||||
|
// returned if we can glean them from the work done in this method.
|
||||||
|
func (d *AuthenticatedGossiper) validateFundingTransaction(
|
||||||
|
ann *lnwire.ChannelAnnouncement1,
|
||||||
|
tapscriptRoot fn.Option[chainhash.Hash]) (wire.OutPoint, btcutil.Amount,
|
||||||
|
[]byte, error) {
|
||||||
|
|
||||||
|
scid := ann.ShortChannelID
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
fundingTx, err := lnwallet.FetchFundingTxWrapper(
|
||||||
|
d.cfg.ChainIO, &scid, d.quit,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
//nolint:ll
|
||||||
|
//
|
||||||
|
// In order to ensure we don't erroneously mark a channel as a
|
||||||
|
// zombie due to an RPC failure, we'll attempt to string match
|
||||||
|
// for the relevant errors.
|
||||||
|
//
|
||||||
|
// * btcd:
|
||||||
|
// * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1316
|
||||||
|
// * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1086
|
||||||
|
// * bitcoind:
|
||||||
|
// * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L770
|
||||||
|
// * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L954
|
||||||
|
switch {
|
||||||
|
case strings.Contains(err.Error(), "not found"):
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case strings.Contains(err.Error(), "out of range"):
|
||||||
|
// If the funding transaction isn't found at all, then
|
||||||
|
// we'll mark the edge itself as a zombie so we don't
|
||||||
|
// continue to request it. We use the "zero key" for
|
||||||
|
// both node pubkeys so this edge can't be resurrected.
|
||||||
|
zErr := d.cfg.Graph.MarkZombieEdge(scid.ToUint64())
|
||||||
|
if zErr != nil {
|
||||||
|
return wire.OutPoint{}, 0, nil, zErr
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
return wire.OutPoint{}, 0, nil, 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(
|
||||||
|
ann.BitcoinKey1[:], ann.BitcoinKey2[:], ann.Features,
|
||||||
|
tapscriptRoot,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return wire.OutPoint{}, 0, nil, 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
|
||||||
|
// proofs.
|
||||||
|
fundingPoint, err := chanvalidate.Validate(
|
||||||
|
&chanvalidate.Context{
|
||||||
|
Locator: &chanvalidate.ShortChanIDChanLocator{
|
||||||
|
ID: scid,
|
||||||
|
},
|
||||||
|
MultiSigPkScript: fundingPkScript,
|
||||||
|
FundingTx: fundingTx,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
// Mark the edge as a zombie so we won't try to re-validate it
|
||||||
|
// on start up.
|
||||||
|
zErr := d.cfg.Graph.MarkZombieEdge(scid.ToUint64())
|
||||||
|
if zErr != nil {
|
||||||
|
return wire.OutPoint{}, 0, nil, zErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return wire.OutPoint{}, 0, nil, fmt.Errorf("%w: %w",
|
||||||
|
ErrInvalidFundingOutput, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have the funding outpoint of the channel, ensure
|
||||||
|
// that it hasn't yet been spent. If so, then this channel has
|
||||||
|
// been closed so we'll ignore it.
|
||||||
|
chanUtxo, err := d.cfg.ChainIO.GetUtxo(
|
||||||
|
fundingPoint, fundingPkScript, scid.BlockHeight, d.quit,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, btcwallet.ErrOutputSpent) {
|
||||||
|
zErr := d.cfg.Graph.MarkZombieEdge(scid.ToUint64())
|
||||||
|
if zErr != nil {
|
||||||
|
return wire.OutPoint{}, 0, nil, zErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wire.OutPoint{}, 0, nil, fmt.Errorf("%w: unable to "+
|
||||||
|
"fetch utxo for chan_id=%v, chan_point=%v: %w",
|
||||||
|
ErrChannelSpent, scid.ToUint64(), fundingPoint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return *fundingPoint, btcutil.Amount(chanUtxo.Value), fundingPkScript,
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
@ -27,15 +27,18 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/graph"
|
"github.com/lightningnetwork/lnd/graph"
|
||||||
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
||||||
"github.com/lightningnetwork/lnd/graph/db/models"
|
"github.com/lightningnetwork/lnd/graph/db/models"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnmock"
|
"github.com/lightningnetwork/lnd/lnmock"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
"github.com/lightningnetwork/lnd/lntest/mock"
|
"github.com/lightningnetwork/lnd/lntest/mock"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/netann"
|
"github.com/lightningnetwork/lnd/netann"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
"github.com/lightningnetwork/lnd/ticker"
|
"github.com/lightningnetwork/lnd/ticker"
|
||||||
|
tmock "github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -82,7 +85,6 @@ type mockGraphSource struct {
|
|||||||
edges map[uint64][]models.ChannelEdgePolicy
|
edges map[uint64][]models.ChannelEdgePolicy
|
||||||
zombies map[uint64][][33]byte
|
zombies map[uint64][][33]byte
|
||||||
chansToReject map[uint64]struct{}
|
chansToReject map[uint64]struct{}
|
||||||
addEdgeErr error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockRouter(height uint32) *mockGraphSource {
|
func newMockRouter(height uint32) *mockGraphSource {
|
||||||
@ -107,16 +109,29 @@ func (r *mockGraphSource) AddNode(node *models.LightningNode,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mockGraphSource) MarkZombieEdge(scid uint64) error {
|
||||||
|
return r.MarkEdgeZombie(
|
||||||
|
lnwire.NewShortChanIDFromInt(scid), [33]byte{}, [33]byte{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mockGraphSource) IsZombieEdge(chanID lnwire.ShortChannelID) (bool,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
_, ok := r.zombies[chanID.ToUint64()]
|
||||||
|
|
||||||
|
return ok, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mockGraphSource) AddEdge(info *models.ChannelEdgeInfo,
|
func (r *mockGraphSource) AddEdge(info *models.ChannelEdgeInfo,
|
||||||
_ ...batch.SchedulerOption) error {
|
_ ...batch.SchedulerOption) error {
|
||||||
|
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
if r.addEdgeErr != nil {
|
|
||||||
return r.addEdgeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := r.infos[info.ChannelID]; ok {
|
if _, ok := r.infos[info.ChannelID]; ok {
|
||||||
return errors.New("info already exist")
|
return errors.New("info already exist")
|
||||||
}
|
}
|
||||||
@ -129,14 +144,6 @@ func (r *mockGraphSource) AddEdge(info *models.ChannelEdgeInfo,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mockGraphSource) resetAddEdgeErr() {
|
|
||||||
r.addEdgeErr = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *mockGraphSource) setAddEdgeErr(err error) {
|
|
||||||
r.addEdgeErr = err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *mockGraphSource) queueValidationFail(chanID uint64) {
|
func (r *mockGraphSource) queueValidationFail(chanID uint64) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
@ -583,7 +590,7 @@ func createUpdateAnnouncement(blockHeight uint32,
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
htlcMinMsat := lnwire.MilliSatoshi(prand.Int63())
|
htlcMinMsat := lnwire.MilliSatoshi(100)
|
||||||
a := &lnwire.ChannelUpdate1{
|
a := &lnwire.ChannelUpdate1{
|
||||||
ShortChannelID: lnwire.ShortChannelID{
|
ShortChannelID: lnwire.ShortChannelID{
|
||||||
BlockHeight: blockHeight,
|
BlockHeight: blockHeight,
|
||||||
@ -627,8 +634,37 @@ func signUpdate(nodeKey *btcec.PrivateKey, a *lnwire.ChannelUpdate1) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fundingTxPrepType determines how we will prep the mock Chain for calls during
|
||||||
|
// a test run.
|
||||||
|
type fundingTxPrepType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// fundingTxPrepTypeGood is the default type and will result in a valid
|
||||||
|
// block and funding transaction being returned by the mock Chain.
|
||||||
|
fundingTxPrepTypeGood fundingTxPrepType = iota
|
||||||
|
|
||||||
|
// fundingTxPrepTypeNone will result in the mock Chain not being prepped
|
||||||
|
// for any calls.
|
||||||
|
fundingTxPrepTypeNone
|
||||||
|
|
||||||
|
// fundingTxPrepTypeInvalidOutput will result in the mock Chain
|
||||||
|
// behaving such that the funding transaction it returns in a block is
|
||||||
|
// invalid.
|
||||||
|
fundingTxPrepTypeInvalidOutput
|
||||||
|
|
||||||
|
// fundingTxPrepTypeNoTx will result in the mock Chain behaving such
|
||||||
|
// the desired block cannot be found.
|
||||||
|
fundingTxPrepTypeNoTx
|
||||||
|
|
||||||
|
// fundingTxPrepTypeSpent will result in the mock Chain behaving such
|
||||||
|
// that the block is valid but the GetUtxo call will return a
|
||||||
|
// btcwallet.ErrOutputSpent error.
|
||||||
|
fundingTxPrepTypeSpent
|
||||||
|
)
|
||||||
|
|
||||||
type fundingTxOpts struct {
|
type fundingTxOpts struct {
|
||||||
extraBytes []byte
|
extraBytes []byte
|
||||||
|
fundingTxPrep fundingTxPrepType
|
||||||
}
|
}
|
||||||
|
|
||||||
type fundingTxOption func(*fundingTxOpts)
|
type fundingTxOption func(*fundingTxOpts)
|
||||||
@ -639,6 +675,12 @@ func withExtraBytes(extraBytes []byte) fundingTxOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withFundingTxPrep(prep fundingTxPrepType) fundingTxOption {
|
||||||
|
return func(opts *fundingTxOpts) {
|
||||||
|
opts.fundingTxPrep = prep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ctx *testCtx) createAnnouncementWithoutProof(blockHeight uint32,
|
func (ctx *testCtx) createAnnouncementWithoutProof(blockHeight uint32,
|
||||||
key1, key2 *btcec.PublicKey,
|
key1, key2 *btcec.PublicKey,
|
||||||
options ...fundingTxOption) *lnwire.ChannelAnnouncement1 {
|
options ...fundingTxOption) *lnwire.ChannelAnnouncement1 {
|
||||||
@ -648,6 +690,59 @@ func (ctx *testCtx) createAnnouncementWithoutProof(blockHeight uint32,
|
|||||||
opt(&opts)
|
opt(&opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch opts.fundingTxPrep {
|
||||||
|
case fundingTxPrepTypeGood:
|
||||||
|
info := makeFundingTxInBlock(ctx.t)
|
||||||
|
|
||||||
|
ctx.chain.On("GetBlockHash", int64(blockHeight)).
|
||||||
|
Return(&chainhash.Hash{}, nil).Once()
|
||||||
|
|
||||||
|
ctx.chain.On("GetBlock", tmock.Anything).
|
||||||
|
Return(info.fundingBlock, nil).Once()
|
||||||
|
|
||||||
|
ctx.chain.On(
|
||||||
|
"GetUtxo", tmock.Anything, tmock.Anything,
|
||||||
|
tmock.Anything, tmock.Anything,
|
||||||
|
).Return(info.fundingTx, nil).Once()
|
||||||
|
|
||||||
|
case fundingTxPrepTypeInvalidOutput:
|
||||||
|
ctx.chain.On(
|
||||||
|
"GetBlockHash", int64(blockHeight),
|
||||||
|
).Return(&chainhash.Hash{}, nil).Once()
|
||||||
|
|
||||||
|
ctx.chain.On(
|
||||||
|
"GetBlock", tmock.Anything,
|
||||||
|
).Return(
|
||||||
|
&wire.MsgBlock{Transactions: []*wire.MsgTx{{}}}, nil,
|
||||||
|
).Once()
|
||||||
|
|
||||||
|
case fundingTxPrepTypeSpent:
|
||||||
|
info := makeFundingTxInBlock(ctx.t)
|
||||||
|
|
||||||
|
ctx.chain.On(
|
||||||
|
"GetBlockHash", int64(blockHeight),
|
||||||
|
).Return(&chainhash.Hash{}, nil).Once()
|
||||||
|
|
||||||
|
ctx.chain.On(
|
||||||
|
"GetBlock", tmock.Anything,
|
||||||
|
).Return(info.fundingBlock, nil).Once()
|
||||||
|
|
||||||
|
ctx.chain.On(
|
||||||
|
"GetUtxo", tmock.Anything, tmock.Anything,
|
||||||
|
tmock.Anything, tmock.Anything,
|
||||||
|
).Return(nil, btcwallet.ErrOutputSpent).Once()
|
||||||
|
|
||||||
|
case fundingTxPrepTypeNoTx:
|
||||||
|
ctx.chain.On("GetBlockHash", int64(blockHeight)).Return(
|
||||||
|
&chainhash.Hash{}, nil,
|
||||||
|
).Once()
|
||||||
|
ctx.chain.On("GetBlock", tmock.Anything).Return(
|
||||||
|
nil, fmt.Errorf("block not found"),
|
||||||
|
).Once()
|
||||||
|
|
||||||
|
case fundingTxPrepTypeNone:
|
||||||
|
}
|
||||||
|
|
||||||
a := &lnwire.ChannelAnnouncement1{
|
a := &lnwire.ChannelAnnouncement1{
|
||||||
ShortChannelID: lnwire.ShortChannelID{
|
ShortChannelID: lnwire.ShortChannelID{
|
||||||
BlockHeight: blockHeight,
|
BlockHeight: blockHeight,
|
||||||
@ -665,6 +760,38 @@ func (ctx *testCtx) createAnnouncementWithoutProof(blockHeight uint32,
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fundingTxInfo struct {
|
||||||
|
chanUtxo *wire.OutPoint
|
||||||
|
fundingBlock *wire.MsgBlock
|
||||||
|
fundingTx *wire.TxOut
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFundingTxInBlock(t *testing.T) *fundingTxInfo {
|
||||||
|
fundingTx := wire.NewMsgTx(2)
|
||||||
|
_, tx, err := input.GenFundingPkScript(
|
||||||
|
bitcoinKeyPub1.SerializeCompressed(),
|
||||||
|
bitcoinKeyPub2.SerializeCompressed(),
|
||||||
|
int64(1000),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fundingTx.TxOut = append(fundingTx.TxOut, tx)
|
||||||
|
chanUtxo := &wire.OutPoint{
|
||||||
|
Hash: fundingTx.TxHash(),
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
block := &wire.MsgBlock{
|
||||||
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &fundingTxInfo{
|
||||||
|
chanUtxo: chanUtxo,
|
||||||
|
fundingBlock: block,
|
||||||
|
fundingTx: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ctx *testCtx) createRemoteChannelAnnouncement(blockHeight uint32,
|
func (ctx *testCtx) createRemoteChannelAnnouncement(blockHeight uint32,
|
||||||
opts ...fundingTxOption) (*lnwire.ChannelAnnouncement1, error) {
|
opts ...fundingTxOption) (*lnwire.ChannelAnnouncement1, error) {
|
||||||
|
|
||||||
@ -998,7 +1125,9 @@ func TestPrematureAnnouncement(t *testing.T) {
|
|||||||
// remote side, but block height of this announcement is greater than
|
// remote side, but block height of this announcement is greater than
|
||||||
// highest know to us, for that reason it should be ignored and not
|
// highest know to us, for that reason it should be ignored and not
|
||||||
// added to the router.
|
// added to the router.
|
||||||
ca, err := ctx.createRemoteChannelAnnouncement(1)
|
ca, err := ctx.createRemoteChannelAnnouncement(
|
||||||
|
1, withFundingTxPrep(fundingTxPrepTypeNone),
|
||||||
|
)
|
||||||
require.NoError(t, err, "can't create channel announcement")
|
require.NoError(t, err, "can't create channel announcement")
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@ -1823,7 +1952,9 @@ func TestDeDuplicatedAnnouncements(t *testing.T) {
|
|||||||
|
|
||||||
// Ensure that remote channel announcements are properly stored
|
// Ensure that remote channel announcements are properly stored
|
||||||
// and de-duplicated.
|
// and de-duplicated.
|
||||||
ca, err := ctx.createRemoteChannelAnnouncement(0)
|
ca, err := ctx.createRemoteChannelAnnouncement(
|
||||||
|
0, withFundingTxPrep(fundingTxPrepTypeNone),
|
||||||
|
)
|
||||||
require.NoError(t, err, "can't create remote channel announcement")
|
require.NoError(t, err, "can't create remote channel announcement")
|
||||||
|
|
||||||
nodePeer := &mockPeer{bitcoinKeyPub2, nil, nil, atomic.Bool{}}
|
nodePeer := &mockPeer{bitcoinKeyPub2, nil, nil, atomic.Bool{}}
|
||||||
@ -1839,7 +1970,9 @@ func TestDeDuplicatedAnnouncements(t *testing.T) {
|
|||||||
// We'll create a second instance of the same announcement with the
|
// We'll create a second instance of the same announcement with the
|
||||||
// same channel ID. Adding this shouldn't cause an increase in the
|
// same channel ID. Adding this shouldn't cause an increase in the
|
||||||
// number of items as they should be de-duplicated.
|
// number of items as they should be de-duplicated.
|
||||||
ca2, err := ctx.createRemoteChannelAnnouncement(0)
|
ca2, err := ctx.createRemoteChannelAnnouncement(
|
||||||
|
0, withFundingTxPrep(fundingTxPrepTypeNone),
|
||||||
|
)
|
||||||
require.NoError(t, err, "can't create remote channel announcement")
|
require.NoError(t, err, "can't create remote channel announcement")
|
||||||
announcements.AddMsgs(networkMsg{
|
announcements.AddMsgs(networkMsg{
|
||||||
msg: ca2,
|
msg: ca2,
|
||||||
@ -3599,11 +3732,18 @@ func TestProcessChannelAnnouncementOptionalMsgFields(t *testing.T) {
|
|||||||
ctx, err := createTestCtx(t, 0, false)
|
ctx, err := createTestCtx(t, 0, false)
|
||||||
require.NoError(t, err, "unable to create test context")
|
require.NoError(t, err, "unable to create test context")
|
||||||
|
|
||||||
|
// We set AssumeValid to true for this test so that the full validation
|
||||||
|
// of a funding transaction is not done and ie, we don't fetch the
|
||||||
|
// channel capacity from the on-chain transaction.
|
||||||
|
ctx.gossiper.cfg.AssumeChannelValid = true
|
||||||
|
|
||||||
chanAnn1 := ctx.createAnnouncementWithoutProof(
|
chanAnn1 := ctx.createAnnouncementWithoutProof(
|
||||||
100, selfKeyDesc.PubKey, remoteKeyPub1,
|
100, selfKeyDesc.PubKey, remoteKeyPub1,
|
||||||
|
withFundingTxPrep(fundingTxPrepTypeNone),
|
||||||
)
|
)
|
||||||
chanAnn2 := ctx.createAnnouncementWithoutProof(
|
chanAnn2 := ctx.createAnnouncementWithoutProof(
|
||||||
101, selfKeyDesc.PubKey, remoteKeyPub1,
|
101, selfKeyDesc.PubKey, remoteKeyPub1,
|
||||||
|
withFundingTxPrep(fundingTxPrepTypeNone),
|
||||||
)
|
)
|
||||||
|
|
||||||
// assertOptionalMsgFields is a helper closure that ensures the optional
|
// assertOptionalMsgFields is a helper closure that ensures the optional
|
||||||
@ -4228,21 +4368,22 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) {
|
|||||||
remoteKeyPriv2.PubKey(), nil, nil, atomic.Bool{},
|
remoteKeyPriv2.PubKey(), nil, nil, atomic.Bool{},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.router.setAddEdgeErr(graph.ErrInvalidFundingOutput)
|
|
||||||
|
|
||||||
// Loop 100 times to get nodePeer banned.
|
// Loop 100 times to get nodePeer banned.
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
// Craft a valid channel announcement for a channel we don't
|
// Craft a valid channel announcement for a channel we don't
|
||||||
// have. We will ensure that it fails validation by modifying
|
// have. We will ensure that it fails validation by modifying
|
||||||
// the router.
|
// the tx script.
|
||||||
ca, err := ctx.createRemoteChannelAnnouncement(uint32(i))
|
ca, err := ctx.createRemoteChannelAnnouncement(
|
||||||
|
uint32(i),
|
||||||
|
withFundingTxPrep(fundingTxPrepTypeInvalidOutput),
|
||||||
|
)
|
||||||
require.NoError(t, err, "can't create channel announcement")
|
require.NoError(t, err, "can't create channel announcement")
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err = <-ctx.gossiper.ProcessRemoteAnnouncement(
|
case err = <-ctx.gossiper.ProcessRemoteAnnouncement(
|
||||||
ca, nodePeer1,
|
ca, nodePeer1,
|
||||||
):
|
):
|
||||||
require.ErrorIs(t, err, graph.ErrInvalidFundingOutput)
|
require.ErrorIs(t, err, ErrInvalidFundingOutput)
|
||||||
|
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
t.Fatalf("remote announcement not processed")
|
t.Fatalf("remote announcement not processed")
|
||||||
@ -4255,16 +4396,16 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) {
|
|||||||
// Assert that nodePeer has been disconnected.
|
// Assert that nodePeer has been disconnected.
|
||||||
require.True(t, nodePeer1.disconnected.Load())
|
require.True(t, nodePeer1.disconnected.Load())
|
||||||
|
|
||||||
ca, err := ctx.createRemoteChannelAnnouncement(101)
|
// Mark the UTXO as spent so that we get the ErrChannelSpent error and
|
||||||
|
// can thus tests that the gossiper ignores closed channels.
|
||||||
|
ca, err := ctx.createRemoteChannelAnnouncement(
|
||||||
|
101, withFundingTxPrep(fundingTxPrepTypeSpent),
|
||||||
|
)
|
||||||
require.NoError(t, err, "can't create channel announcement")
|
require.NoError(t, err, "can't create channel announcement")
|
||||||
|
|
||||||
// Set the error to ErrChannelSpent so that we can test that the
|
|
||||||
// gossiper ignores closed channels.
|
|
||||||
ctx.router.setAddEdgeErr(graph.ErrChannelSpent)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ca, nodePeer2):
|
case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ca, nodePeer2):
|
||||||
require.ErrorIs(t, err, graph.ErrChannelSpent)
|
require.ErrorIs(t, err, ErrChannelSpent)
|
||||||
|
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
t.Fatalf("remote announcement not processed")
|
t.Fatalf("remote announcement not processed")
|
||||||
@ -4285,13 +4426,15 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) {
|
|||||||
|
|
||||||
ctx.gossiper.recentRejects.Delete(key)
|
ctx.gossiper.recentRejects.Delete(key)
|
||||||
|
|
||||||
// Reset the AddEdge error and pass the same announcement again. An
|
// The validateFundingTransaction method will mark this channel
|
||||||
// error should be returned even though AddEdge won't fail.
|
// as a zombie if any error occurs in the chanvalidate.Validate call.
|
||||||
ctx.router.resetAddEdgeErr()
|
// For the sake of the rest of the test, however, we mark it as live
|
||||||
|
// here.
|
||||||
|
_ = ctx.router.MarkEdgeLive(ca.ShortChannelID)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ca, nodePeer2):
|
case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ca, nodePeer2):
|
||||||
require.NotNil(t, err)
|
require.ErrorContains(t, err, "ignoring closed channel")
|
||||||
|
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
t.Fatalf("remote announcement not processed")
|
t.Fatalf("remote announcement not processed")
|
||||||
@ -4308,21 +4451,22 @@ func TestChanAnnBanningChanPeer(t *testing.T) {
|
|||||||
|
|
||||||
nodePeer := &mockPeer{remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}}
|
nodePeer := &mockPeer{remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}}
|
||||||
|
|
||||||
ctx.router.setAddEdgeErr(graph.ErrInvalidFundingOutput)
|
|
||||||
|
|
||||||
// Loop 100 times to get nodePeer banned.
|
// Loop 100 times to get nodePeer banned.
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
// Craft a valid channel announcement for a channel we don't
|
// Craft a valid channel announcement for a channel we don't
|
||||||
// have. We will ensure that it fails validation by modifying
|
// have. We will ensure that it fails validation by modifying
|
||||||
// the router.
|
// the router.
|
||||||
ca, err := ctx.createRemoteChannelAnnouncement(uint32(i))
|
ca, err := ctx.createRemoteChannelAnnouncement(
|
||||||
|
uint32(i),
|
||||||
|
withFundingTxPrep(fundingTxPrepTypeInvalidOutput),
|
||||||
|
)
|
||||||
require.NoError(t, err, "can't create channel announcement")
|
require.NoError(t, err, "can't create channel announcement")
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err = <-ctx.gossiper.ProcessRemoteAnnouncement(
|
case err = <-ctx.gossiper.ProcessRemoteAnnouncement(
|
||||||
ca, nodePeer,
|
ca, nodePeer,
|
||||||
):
|
):
|
||||||
require.ErrorIs(t, err, graph.ErrInvalidFundingOutput)
|
require.ErrorIs(t, err, ErrInvalidFundingOutput)
|
||||||
|
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
t.Fatalf("remote announcement not processed")
|
t.Fatalf("remote announcement not processed")
|
||||||
@ -4335,3 +4479,75 @@ func TestChanAnnBanningChanPeer(t *testing.T) {
|
|||||||
// Assert that the peer wasn't disconnected.
|
// Assert that the peer wasn't disconnected.
|
||||||
require.False(t, nodePeer.disconnected.Load())
|
require.False(t, nodePeer.disconnected.Load())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestChannelOnChainRejectionZombie tests that if we fail validating a channel
|
||||||
|
// due to some sort of on-chain rejection (no funding transaction, or invalid
|
||||||
|
// UTXO), then we'll mark the channel as a zombie.
|
||||||
|
func TestChannelOnChainRejectionZombie(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx, err := createTestCtx(t, 1000, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// To start, we'll make an edge for the channel, but we won't add the
|
||||||
|
// funding transaction to the mock blockchain, which should cause the
|
||||||
|
// validation to fail below.
|
||||||
|
chanAnn, err := ctx.createRemoteChannelAnnouncement(
|
||||||
|
1, withFundingTxPrep(fundingTxPrepTypeNoTx),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// We expect this to fail as the transaction isn't present in the
|
||||||
|
// chain (nor the block).
|
||||||
|
assertChanChainRejection(t, ctx, chanAnn, ErrNoFundingTransaction)
|
||||||
|
|
||||||
|
// Next, we'll make another channel edge, but actually add it to the
|
||||||
|
// graph this time.
|
||||||
|
chanAnn, err = ctx.createRemoteChannelAnnouncement(
|
||||||
|
2, withFundingTxPrep(fundingTxPrepTypeSpent),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Instead now, we'll remove it from the set of UTXOs which should
|
||||||
|
// cause the spentness validation to fail.
|
||||||
|
assertChanChainRejection(t, ctx, chanAnn, ErrChannelSpent)
|
||||||
|
|
||||||
|
// If we cause the funding transaction the chain to fail validation, we
|
||||||
|
// should see similar behavior.
|
||||||
|
chanAnn, err = ctx.createRemoteChannelAnnouncement(
|
||||||
|
3, withFundingTxPrep(fundingTxPrepTypeInvalidOutput),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertChanChainRejection(t, ctx, chanAnn, ErrInvalidFundingOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertChanChainRejection(t *testing.T, ctx *testCtx,
|
||||||
|
edge *lnwire.ChannelAnnouncement1, expectedErr error) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
nodePeer := &mockPeer{bitcoinKeyPub2, nil, nil, atomic.Bool{}}
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
nMsg := &networkMsg{
|
||||||
|
msg: edge,
|
||||||
|
isRemote: true,
|
||||||
|
peer: nodePeer,
|
||||||
|
source: nodePeer.IdentityKey(),
|
||||||
|
err: errChan,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, added := ctx.gossiper.handleChanAnnouncement(nMsg, edge)
|
||||||
|
require.False(t, added)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
require.ErrorIs(t, err, expectedErr)
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("channel announcement not processed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This channel should now be present in the zombie channel index.
|
||||||
|
isZombie, err := ctx.router.IsZombieEdge(edge.ShortChannelID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, isZombie, "edge should be marked as zombie")
|
||||||
|
}
|
||||||
|
@ -251,10 +251,10 @@ The underlying functionality between those two options remain the same.
|
|||||||
* [Golang was updated to
|
* [Golang was updated to
|
||||||
`v1.22.11`](https://github.com/lightningnetwork/lnd/pull/9462).
|
`v1.22.11`](https://github.com/lightningnetwork/lnd/pull/9462).
|
||||||
|
|
||||||
* Various refactors and preparations to simplify the
|
* Move funding transaction validation to the gossiper
|
||||||
`graph.Builder` and to move the funding tx validation to the gossiper.
|
|
||||||
[1](https://github.com/lightningnetwork/lnd/pull/9476)
|
[1](https://github.com/lightningnetwork/lnd/pull/9476)
|
||||||
[2](https://github.com/lightningnetwork/lnd/pull/9477)
|
[2](https://github.com/lightningnetwork/lnd/pull/9477)
|
||||||
|
[3](https://github.com/lightningnetwork/lnd/pull/9478).
|
||||||
|
|
||||||
|
|
||||||
## Breaking Changes
|
## Breaking Changes
|
||||||
|
226
graph/builder.go
226
graph/builder.go
@ -1,29 +1,21 @@
|
|||||||
package graph
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/batch"
|
"github.com/lightningnetwork/lnd/batch"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/fn/v2"
|
|
||||||
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
||||||
"github.com/lightningnetwork/lnd/graph/db/models"
|
"github.com/lightningnetwork/lnd/graph/db/models"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lnutils"
|
"github.com/lightningnetwork/lnd/lnutils"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
|
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/multimutex"
|
"github.com/lightningnetwork/lnd/multimutex"
|
||||||
"github.com/lightningnetwork/lnd/netann"
|
"github.com/lightningnetwork/lnd/netann"
|
||||||
@ -95,9 +87,9 @@ type Config struct {
|
|||||||
// blocked by this job.
|
// blocked by this job.
|
||||||
FirstTimePruneDelay time.Duration
|
FirstTimePruneDelay time.Duration
|
||||||
|
|
||||||
// AssumeChannelValid toggles whether the router will check for
|
// AssumeChannelValid toggles whether the builder will prune channels
|
||||||
// spentness of channel outpoints. For neutrino, this saves long rescans
|
// based on their spentness vs using the fact that they are considered
|
||||||
// from blocking initial usage of the daemon.
|
// zombies.
|
||||||
AssumeChannelValid bool
|
AssumeChannelValid bool
|
||||||
|
|
||||||
// StrictZombiePruning determines if we attempt to prune zombie
|
// StrictZombiePruning determines if we attempt to prune zombie
|
||||||
@ -1008,9 +1000,9 @@ func (b *Builder) assertNodeAnnFreshness(node route.Vertex,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addZombieEdge adds a channel that failed complete validation into the zombie
|
// MarkZombieEdge adds a channel that failed complete validation into the zombie
|
||||||
// index so we can avoid having to re-validate it in the future.
|
// index so we can avoid having to re-validate it in the future.
|
||||||
func (b *Builder) addZombieEdge(chanID uint64) error {
|
func (b *Builder) MarkZombieEdge(chanID uint64) error {
|
||||||
// If the edge fails validation we'll mark the edge itself as a zombie
|
// If the edge fails validation we'll mark the edge itself as a zombie
|
||||||
// so we don't continue to request it. We use the "zero key" for both
|
// so we don't continue to request it. We use the "zero key" for both
|
||||||
// node pubkeys so this edge can't be resurrected.
|
// node pubkeys so this edge can't be resurrected.
|
||||||
@ -1024,72 +1016,6 @@ func (b *Builder) addZombieEdge(chanID uint64) error {
|
|||||||
return nil
|
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
|
// routingMsg couples a routing related routing topology update to the
|
||||||
// error channel.
|
// error channel.
|
||||||
type routingMsg struct {
|
type routingMsg struct {
|
||||||
@ -1258,137 +1184,47 @@ func (b *Builder) addEdge(edge *models.ChannelEdgeInfo,
|
|||||||
edge.ChannelID)
|
edge.ChannelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If AssumeChannelValid is present, then we are unable to perform any
|
if err := b.cfg.Graph.AddChannelEdge(edge, op...); err != nil {
|
||||||
// of the expensive checks below, so we'll short-circuit our path
|
return fmt.Errorf("unable to add edge: %w", err)
|
||||||
// straight to adding the edge to our graph. If the passed
|
}
|
||||||
// ShortChannelID is an alias, then we'll skip validation as it will
|
|
||||||
// not map to a legitimate tx. This is not a DoS vector as only we can
|
b.stats.incNumEdgesDiscovered()
|
||||||
// add an alias ChannelAnnouncement from the gossiper.
|
|
||||||
|
// If AssumeChannelValid is present, of if the SCID is an alias, then
|
||||||
|
// the gossiper would not have done the expensive work of fetching
|
||||||
|
// a funding transaction and validating it. So we won't have the channel
|
||||||
|
// capacity nor the funding script. So we just log and return here.
|
||||||
scid := lnwire.NewShortChanIDFromInt(edge.ChannelID)
|
scid := lnwire.NewShortChanIDFromInt(edge.ChannelID)
|
||||||
if b.cfg.AssumeChannelValid || b.cfg.IsAlias(scid) {
|
if b.cfg.AssumeChannelValid || b.cfg.IsAlias(scid) {
|
||||||
err := b.cfg.Graph.AddChannelEdge(edge, op...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to add edge: %w", err)
|
|
||||||
}
|
|
||||||
log.Tracef("New channel discovered! Link connects %x and %x "+
|
log.Tracef("New channel discovered! Link connects %x and %x "+
|
||||||
"with ChannelID(%v)", edge.NodeKey1Bytes,
|
"with ChannelID(%v)", edge.NodeKey1Bytes,
|
||||||
edge.NodeKey2Bytes, edge.ChannelID)
|
edge.NodeKey2Bytes, edge.ChannelID)
|
||||||
b.stats.incNumEdgesDiscovered()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before we can add the channel to the channel graph, we need to obtain
|
log.Debugf("New channel discovered! Link connects %x and %x with "+
|
||||||
// the full funding outpoint that's encoded within the channel ID.
|
"ChannelPoint(%v): chan_id=%v, capacity=%v", edge.NodeKey1Bytes,
|
||||||
channelID := lnwire.NewShortChanIDFromInt(edge.ChannelID)
|
edge.NodeKey2Bytes, edge.ChannelPoint, edge.ChannelID,
|
||||||
fundingTx, err := lnwallet.FetchFundingTxWrapper(
|
edge.Capacity)
|
||||||
b.cfg.Chain, &channelID, b.quit,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
//nolint:ll
|
|
||||||
//
|
|
||||||
// In order to ensure we don't erroneously mark a channel as a
|
|
||||||
// zombie due to an RPC failure, we'll attempt to string match
|
|
||||||
// for the relevant errors.
|
|
||||||
//
|
|
||||||
// * btcd:
|
|
||||||
// * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1316
|
|
||||||
// * https://github.com/btcsuite/btcd/blob/master/rpcserver.go#L1086
|
|
||||||
// * bitcoind:
|
|
||||||
// * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L770
|
|
||||||
// * https://github.com/bitcoin/bitcoin/blob/7fcf53f7b4524572d1d0c9a5fdc388e87eb02416/src/rpc/blockchain.cpp#L954
|
|
||||||
switch {
|
|
||||||
case strings.Contains(err.Error(), "not found"):
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case strings.Contains(err.Error(), "out of range"):
|
// Otherwise, then we expect the funding script to be present on the
|
||||||
// If the funding transaction isn't found at all, then
|
// edge since it would have been fetched when the gossiper validated the
|
||||||
// we'll mark the edge itself as a zombie so we don't
|
// announcement.
|
||||||
// continue to request it. We use the "zero key" for
|
fundingPkScript, err := edge.FundingScript.UnwrapOrErr(fmt.Errorf(
|
||||||
// both node pubkeys so this edge can't be resurrected.
|
"expected the funding transaction script to be set",
|
||||||
zErr := b.addZombieEdge(edge.ChannelID)
|
))
|
||||||
if zErr != nil {
|
|
||||||
return zErr
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
return err
|
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
|
|
||||||
// proofs.
|
|
||||||
fundingPoint, err := chanvalidate.Validate(
|
|
||||||
&chanvalidate.Context{
|
|
||||||
Locator: &chanvalidate.ShortChanIDChanLocator{
|
|
||||||
ID: channelID,
|
|
||||||
},
|
|
||||||
MultiSigPkScript: fundingPkScript,
|
|
||||||
FundingTx: fundingTx,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
// Mark the edge as a zombie so we won't try to re-validate it
|
|
||||||
// on start up.
|
|
||||||
if err := b.addZombieEdge(edge.ChannelID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%w: %w", ErrInvalidFundingOutput, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we have the funding outpoint of the channel, ensure
|
|
||||||
// that it hasn't yet been spent. If so, then this channel has
|
|
||||||
// been closed so we'll ignore it.
|
|
||||||
chanUtxo, err := b.cfg.Chain.GetUtxo(
|
|
||||||
fundingPoint, fundingPkScript, channelID.BlockHeight, b.quit,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, btcwallet.ErrOutputSpent) {
|
|
||||||
zErr := b.addZombieEdge(edge.ChannelID)
|
|
||||||
if zErr != nil {
|
|
||||||
return zErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%w: unable to fetch utxo for chan_id=%v, "+
|
|
||||||
"chan_point=%v: %w", ErrChannelSpent, scid.ToUint64(),
|
|
||||||
fundingPoint, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(roasbeef): this is a hack, needs to be removed after commitment
|
|
||||||
// fees are dynamic.
|
|
||||||
edge.Capacity = btcutil.Amount(chanUtxo.Value)
|
|
||||||
edge.ChannelPoint = *fundingPoint
|
|
||||||
if err := b.cfg.Graph.AddChannelEdge(edge, op...); err != nil {
|
|
||||||
return errors.Errorf("unable to add edge: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("New channel discovered! Link connects %x and %x with "+
|
|
||||||
"ChannelPoint(%v): chan_id=%v, capacity=%v", edge.NodeKey1Bytes,
|
|
||||||
edge.NodeKey2Bytes, fundingPoint, edge.ChannelID, edge.Capacity)
|
|
||||||
b.stats.incNumEdgesDiscovered()
|
|
||||||
|
|
||||||
// As a new edge has been added to the channel graph, we'll update the
|
// As a new edge has been added to the channel graph, we'll update the
|
||||||
// current UTXO filter within our active FilteredChainView so we are
|
// current UTXO filter within our active FilteredChainView so we are
|
||||||
// notified if/when this channel is closed.
|
// notified if/when this channel is closed.
|
||||||
filterUpdate := []graphdb.EdgePoint{
|
filterUpdate := []graphdb.EdgePoint{
|
||||||
{
|
{
|
||||||
FundingPkScript: fundingPkScript,
|
FundingPkScript: fundingPkScript,
|
||||||
OutPoint: *fundingPoint,
|
OutPoint: edge.ChannelPoint,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1630,6 +1466,16 @@ func (b *Builder) IsKnownEdge(chanID lnwire.ShortChannelID) bool {
|
|||||||
return exists || isZombie
|
return exists || isZombie
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsZombieEdge returns true if the graph source has marked the given channel ID
|
||||||
|
// as a zombie edge.
|
||||||
|
//
|
||||||
|
// NOTE: This method is part of the ChannelGraphSource interface.
|
||||||
|
func (b *Builder) IsZombieEdge(chanID lnwire.ShortChannelID) (bool, error) {
|
||||||
|
_, _, _, isZombie, err := b.cfg.Graph.HasChannelEdge(chanID.ToUint64())
|
||||||
|
|
||||||
|
return isZombie, err
|
||||||
|
}
|
||||||
|
|
||||||
// IsStaleEdgePolicy returns true if the graph source has a channel edge for
|
// IsStaleEdgePolicy returns true if the graph source has a channel edge for
|
||||||
// the passed channel ID (and flags) that have a more recent timestamp.
|
// the passed channel ID (and flags) that have a more recent timestamp.
|
||||||
//
|
//
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
|
"github.com/lightningnetwork/lnd/fn/v2"
|
||||||
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
||||||
"github.com/lightningnetwork/lnd/graph/db/models"
|
"github.com/lightningnetwork/lnd/graph/db/models"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
@ -52,8 +53,8 @@ func TestAddProof(t *testing.T) {
|
|||||||
|
|
||||||
// In order to be able to add the edge we should have a valid funding
|
// In order to be able to add the edge we should have a valid funding
|
||||||
// UTXO within the blockchain.
|
// UTXO within the blockchain.
|
||||||
fundingTx, _, chanID, err := createChannelEdge(
|
script, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
ctx, bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(), 100, 0,
|
bitcoinKey2.SerializeCompressed(), 100, 0,
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable create channel edge")
|
require.NoError(t, err, "unable create channel edge")
|
||||||
@ -68,6 +69,7 @@ func TestAddProof(t *testing.T) {
|
|||||||
NodeKey1Bytes: node1.PubKeyBytes,
|
NodeKey1Bytes: node1.PubKeyBytes,
|
||||||
NodeKey2Bytes: node2.PubKeyBytes,
|
NodeKey2Bytes: node2.PubKeyBytes,
|
||||||
AuthProof: nil,
|
AuthProof: nil,
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
@ -136,8 +138,8 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) {
|
|||||||
|
|
||||||
// Add the edge between the two unknown nodes to the graph, and check
|
// Add the edge between the two unknown nodes to the graph, and check
|
||||||
// that the nodes are found after the fact.
|
// that the nodes are found after the fact.
|
||||||
fundingTx, _, chanID, err := createChannelEdge(
|
script, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
ctx, bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(), 10000, 500,
|
bitcoinKey2.SerializeCompressed(), 10000, 500,
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create channel edge")
|
require.NoError(t, err, "unable to create channel edge")
|
||||||
@ -153,6 +155,7 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) {
|
|||||||
BitcoinKey1Bytes: pub1,
|
BitcoinKey1Bytes: pub1,
|
||||||
BitcoinKey2Bytes: pub2,
|
BitcoinKey2Bytes: pub2,
|
||||||
AuthProof: nil,
|
AuthProof: nil,
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
edgePolicy := &models.ChannelEdgePolicy{
|
edgePolicy := &models.ChannelEdgePolicy{
|
||||||
SigBytes: testSig.Serialize(),
|
SigBytes: testSig.Serialize(),
|
||||||
@ -199,22 +202,25 @@ func TestWakeUpOnStaleBranch(t *testing.T) {
|
|||||||
var chanID2 uint64
|
var chanID2 uint64
|
||||||
|
|
||||||
// Create 10 common blocks, confirming chanID1.
|
// Create 10 common blocks, confirming chanID1.
|
||||||
|
var fundingScript1 []byte
|
||||||
for i := uint32(1); i <= 10; i++ {
|
for i := uint32(1); i <= 10; i++ {
|
||||||
block := &wire.MsgBlock{
|
block := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{},
|
Transactions: []*wire.MsgTx{},
|
||||||
}
|
}
|
||||||
height := startingBlockHeight + i
|
height := startingBlockHeight + i
|
||||||
if i == 5 {
|
if i == 5 {
|
||||||
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
script, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
chanValue, height)
|
chanValue, height,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable create channel edge: %v", err)
|
t.Fatalf("unable create channel edge: %v", err)
|
||||||
}
|
}
|
||||||
block.Transactions = append(block.Transactions,
|
block.Transactions = append(block.Transactions,
|
||||||
fundingTx)
|
fundingTx)
|
||||||
chanID1 = chanID.ToUint64()
|
chanID1 = chanID.ToUint64()
|
||||||
|
fundingScript1 = script
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(block, height, rand.Uint32())
|
ctx.chain.addBlock(block, height, rand.Uint32())
|
||||||
ctx.chain.setBestBlock(int32(height))
|
ctx.chain.setBestBlock(int32(height))
|
||||||
@ -229,13 +235,14 @@ func TestWakeUpOnStaleBranch(t *testing.T) {
|
|||||||
require.NoError(t, err, "unable to ge best block")
|
require.NoError(t, err, "unable to ge best block")
|
||||||
|
|
||||||
// Create 10 blocks on the minority chain, confirming chanID2.
|
// Create 10 blocks on the minority chain, confirming chanID2.
|
||||||
|
var fundingScript2 []byte
|
||||||
for i := uint32(1); i <= 10; i++ {
|
for i := uint32(1); i <= 10; i++ {
|
||||||
block := &wire.MsgBlock{
|
block := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{},
|
Transactions: []*wire.MsgTx{},
|
||||||
}
|
}
|
||||||
height := uint32(forkHeight) + i
|
height := uint32(forkHeight) + i
|
||||||
if i == 5 {
|
if i == 5 {
|
||||||
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
script, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
chanValue, height)
|
chanValue, height)
|
||||||
@ -245,6 +252,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) {
|
|||||||
block.Transactions = append(block.Transactions,
|
block.Transactions = append(block.Transactions,
|
||||||
fundingTx)
|
fundingTx)
|
||||||
chanID2 = chanID.ToUint64()
|
chanID2 = chanID.ToUint64()
|
||||||
|
fundingScript2 = script
|
||||||
}
|
}
|
||||||
ctx.chain.addBlock(block, height, rand.Uint32())
|
ctx.chain.addBlock(block, height, rand.Uint32())
|
||||||
ctx.chain.setBestBlock(int32(height))
|
ctx.chain.setBestBlock(int32(height))
|
||||||
@ -269,6 +277,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) {
|
|||||||
BitcoinSig1Bytes: testSig.Serialize(),
|
BitcoinSig1Bytes: testSig.Serialize(),
|
||||||
BitcoinSig2Bytes: testSig.Serialize(),
|
BitcoinSig2Bytes: testSig.Serialize(),
|
||||||
},
|
},
|
||||||
|
FundingScript: fn.Some(fundingScript1),
|
||||||
}
|
}
|
||||||
copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
@ -287,6 +296,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) {
|
|||||||
BitcoinSig1Bytes: testSig.Serialize(),
|
BitcoinSig1Bytes: testSig.Serialize(),
|
||||||
BitcoinSig2Bytes: testSig.Serialize(),
|
BitcoinSig2Bytes: testSig.Serialize(),
|
||||||
},
|
},
|
||||||
|
FundingScript: fn.Some(fundingScript2),
|
||||||
}
|
}
|
||||||
copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
@ -406,10 +416,11 @@ func TestDisconnectedBlocks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
height := startingBlockHeight + i
|
height := startingBlockHeight + i
|
||||||
if i == 5 {
|
if i == 5 {
|
||||||
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
_, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
chanValue, height)
|
chanValue, height,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable create channel edge: %v", err)
|
t.Fatalf("unable create channel edge: %v", err)
|
||||||
}
|
}
|
||||||
@ -437,10 +448,11 @@ func TestDisconnectedBlocks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
height := uint32(forkHeight) + i
|
height := uint32(forkHeight) + i
|
||||||
if i == 5 {
|
if i == 5 {
|
||||||
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
_, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
chanValue, height)
|
chanValue, height,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable create channel edge: %v", err)
|
t.Fatalf("unable create channel edge: %v", err)
|
||||||
}
|
}
|
||||||
@ -474,6 +486,7 @@ func TestDisconnectedBlocks(t *testing.T) {
|
|||||||
BitcoinSig1Bytes: testSig.Serialize(),
|
BitcoinSig1Bytes: testSig.Serialize(),
|
||||||
BitcoinSig2Bytes: testSig.Serialize(),
|
BitcoinSig2Bytes: testSig.Serialize(),
|
||||||
},
|
},
|
||||||
|
FundingScript: fn.Some([]byte{}),
|
||||||
}
|
}
|
||||||
copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
@ -494,6 +507,7 @@ func TestDisconnectedBlocks(t *testing.T) {
|
|||||||
BitcoinSig1Bytes: testSig.Serialize(),
|
BitcoinSig1Bytes: testSig.Serialize(),
|
||||||
BitcoinSig2Bytes: testSig.Serialize(),
|
BitcoinSig2Bytes: testSig.Serialize(),
|
||||||
},
|
},
|
||||||
|
FundingScript: fn.Some([]byte{}),
|
||||||
}
|
}
|
||||||
copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
@ -595,10 +609,11 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) {
|
|||||||
Transactions: []*wire.MsgTx{},
|
Transactions: []*wire.MsgTx{},
|
||||||
}
|
}
|
||||||
nextHeight := startingBlockHeight + 1
|
nextHeight := startingBlockHeight + 1
|
||||||
fundingTx1, chanUTXO, chanID1, err := createChannelEdge(ctx,
|
script, fundingTx1, chanUTXO, chanID1, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
chanValue, uint32(nextHeight))
|
chanValue, uint32(nextHeight),
|
||||||
|
)
|
||||||
require.NoError(t, err, "unable create channel edge")
|
require.NoError(t, err, "unable create channel edge")
|
||||||
block102.Transactions = append(block102.Transactions, fundingTx1)
|
block102.Transactions = append(block102.Transactions, fundingTx1)
|
||||||
ctx.chain.addBlock(block102, uint32(nextHeight), rand.Uint32())
|
ctx.chain.addBlock(block102, uint32(nextHeight), rand.Uint32())
|
||||||
@ -622,6 +637,9 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) {
|
|||||||
BitcoinSig1Bytes: testSig.Serialize(),
|
BitcoinSig1Bytes: testSig.Serialize(),
|
||||||
BitcoinSig2Bytes: testSig.Serialize(),
|
BitcoinSig2Bytes: testSig.Serialize(),
|
||||||
},
|
},
|
||||||
|
ChannelPoint: *chanUTXO,
|
||||||
|
Capacity: chanValue,
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
@ -1007,10 +1025,11 @@ func TestIsStaleNode(t *testing.T) {
|
|||||||
copy(pub1[:], priv1.PubKey().SerializeCompressed())
|
copy(pub1[:], priv1.PubKey().SerializeCompressed())
|
||||||
copy(pub2[:], priv2.PubKey().SerializeCompressed())
|
copy(pub2[:], priv2.PubKey().SerializeCompressed())
|
||||||
|
|
||||||
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
script, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
10000, 500)
|
10000, 500,
|
||||||
|
)
|
||||||
require.NoError(t, err, "unable to create channel edge")
|
require.NoError(t, err, "unable to create channel edge")
|
||||||
fundingBlock := &wire.MsgBlock{
|
fundingBlock := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
@ -1024,6 +1043,7 @@ func TestIsStaleNode(t *testing.T) {
|
|||||||
BitcoinKey1Bytes: pub1,
|
BitcoinKey1Bytes: pub1,
|
||||||
BitcoinKey2Bytes: pub2,
|
BitcoinKey2Bytes: pub2,
|
||||||
AuthProof: nil,
|
AuthProof: nil,
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
if err := ctx.builder.AddEdge(edge); err != nil {
|
if err := ctx.builder.AddEdge(edge); err != nil {
|
||||||
t.Fatalf("unable to add edge: %v", err)
|
t.Fatalf("unable to add edge: %v", err)
|
||||||
@ -1083,10 +1103,11 @@ func TestIsKnownEdge(t *testing.T) {
|
|||||||
copy(pub1[:], priv1.PubKey().SerializeCompressed())
|
copy(pub1[:], priv1.PubKey().SerializeCompressed())
|
||||||
copy(pub2[:], priv2.PubKey().SerializeCompressed())
|
copy(pub2[:], priv2.PubKey().SerializeCompressed())
|
||||||
|
|
||||||
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
script, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
10000, 500)
|
10000, 500,
|
||||||
|
)
|
||||||
require.NoError(t, err, "unable to create channel edge")
|
require.NoError(t, err, "unable to create channel edge")
|
||||||
fundingBlock := &wire.MsgBlock{
|
fundingBlock := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
@ -1100,6 +1121,7 @@ func TestIsKnownEdge(t *testing.T) {
|
|||||||
BitcoinKey1Bytes: pub1,
|
BitcoinKey1Bytes: pub1,
|
||||||
BitcoinKey2Bytes: pub2,
|
BitcoinKey2Bytes: pub2,
|
||||||
AuthProof: nil,
|
AuthProof: nil,
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
if err := ctx.builder.AddEdge(edge); err != nil {
|
if err := ctx.builder.AddEdge(edge); err != nil {
|
||||||
t.Fatalf("unable to add edge: %v", err)
|
t.Fatalf("unable to add edge: %v", err)
|
||||||
@ -1129,10 +1151,11 @@ func TestIsStaleEdgePolicy(t *testing.T) {
|
|||||||
copy(pub1[:], priv1.PubKey().SerializeCompressed())
|
copy(pub1[:], priv1.PubKey().SerializeCompressed())
|
||||||
copy(pub2[:], priv2.PubKey().SerializeCompressed())
|
copy(pub2[:], priv2.PubKey().SerializeCompressed())
|
||||||
|
|
||||||
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
script, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
10000, 500)
|
10000, 500,
|
||||||
|
)
|
||||||
require.NoError(t, err, "unable to create channel edge")
|
require.NoError(t, err, "unable to create channel edge")
|
||||||
fundingBlock := &wire.MsgBlock{
|
fundingBlock := &wire.MsgBlock{
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
Transactions: []*wire.MsgTx{fundingTx},
|
||||||
@ -1156,6 +1179,7 @@ func TestIsStaleEdgePolicy(t *testing.T) {
|
|||||||
BitcoinKey1Bytes: pub1,
|
BitcoinKey1Bytes: pub1,
|
||||||
BitcoinKey2Bytes: pub2,
|
BitcoinKey2Bytes: pub2,
|
||||||
AuthProof: nil,
|
AuthProof: nil,
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
if err := ctx.builder.AddEdge(edge); err != nil {
|
if err := ctx.builder.AddEdge(edge); err != nil {
|
||||||
t.Fatalf("unable to add edge: %v", err)
|
t.Fatalf("unable to add edge: %v", err)
|
||||||
@ -1210,120 +1234,6 @@ func TestIsStaleEdgePolicy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// edgeCreationModifier is an enum-like type used to modify steps that are
|
|
||||||
// skipped when creating a channel in the test context.
|
|
||||||
type edgeCreationModifier uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// edgeCreationNoFundingTx is used to skip adding the funding
|
|
||||||
// transaction of an edge to the chain.
|
|
||||||
edgeCreationNoFundingTx edgeCreationModifier = iota
|
|
||||||
|
|
||||||
// edgeCreationNoUTXO is used to skip adding the UTXO of a channel to
|
|
||||||
// the UTXO set.
|
|
||||||
edgeCreationNoUTXO
|
|
||||||
|
|
||||||
// edgeCreationBadScript is used to create the edge, but use the wrong
|
|
||||||
// scrip which should cause it to fail output validation.
|
|
||||||
edgeCreationBadScript
|
|
||||||
)
|
|
||||||
|
|
||||||
// newChannelEdgeInfo is a helper function used to create a new channel edge,
|
|
||||||
// possibly skipping adding it to parts of the chain/state as well.
|
|
||||||
func newChannelEdgeInfo(t *testing.T, ctx *testCtx, fundingHeight uint32,
|
|
||||||
ecm edgeCreationModifier) (*models.ChannelEdgeInfo, error) {
|
|
||||||
|
|
||||||
node1 := createTestNode(t)
|
|
||||||
node2 := createTestNode(t)
|
|
||||||
|
|
||||||
fundingTx, _, chanID, err := createChannelEdge(
|
|
||||||
ctx, bitcoinKey1.SerializeCompressed(),
|
|
||||||
bitcoinKey2.SerializeCompressed(), 100, fundingHeight,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create edge: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
edge := &models.ChannelEdgeInfo{
|
|
||||||
ChannelID: chanID.ToUint64(),
|
|
||||||
NodeKey1Bytes: node1.PubKeyBytes,
|
|
||||||
NodeKey2Bytes: node2.PubKeyBytes,
|
|
||||||
}
|
|
||||||
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
|
||||||
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
|
||||||
|
|
||||||
if ecm == edgeCreationNoFundingTx {
|
|
||||||
return edge, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fundingBlock := &wire.MsgBlock{
|
|
||||||
Transactions: []*wire.MsgTx{fundingTx},
|
|
||||||
}
|
|
||||||
ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight)
|
|
||||||
|
|
||||||
if ecm == edgeCreationNoUTXO {
|
|
||||||
ctx.chain.delUtxo(wire.OutPoint{
|
|
||||||
Hash: fundingTx.TxHash(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if ecm == edgeCreationBadScript {
|
|
||||||
fundingTx.TxOut[0].PkScript[0] ^= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return edge, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertChanChainRejection(t *testing.T, ctx *testCtx,
|
|
||||||
edge *models.ChannelEdgeInfo, expectedErr error) {
|
|
||||||
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
err := ctx.builder.AddEdge(edge)
|
|
||||||
require.ErrorIs(t, err, expectedErr)
|
|
||||||
|
|
||||||
// This channel should now be present in the zombie channel index.
|
|
||||||
_, _, _, isZombie, err := ctx.graph.HasChannelEdge(
|
|
||||||
edge.ChannelID,
|
|
||||||
)
|
|
||||||
require.Nil(t, err)
|
|
||||||
require.True(t, isZombie, "edge should be marked as zombie")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestChannelOnChainRejectionZombie tests that if we fail validating a channel
|
|
||||||
// due to some sort of on-chain rejection (no funding transaction, or invalid
|
|
||||||
// UTXO), then we'll mark the channel as a zombie.
|
|
||||||
func TestChannelOnChainRejectionZombie(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctx := createTestCtxSingleNode(t, 0)
|
|
||||||
|
|
||||||
// To start, we'll make an edge for the channel, but we won't add the
|
|
||||||
// funding transaction to the mock blockchain, which should cause the
|
|
||||||
// validation to fail below.
|
|
||||||
edge, err := newChannelEdgeInfo(t, ctx, 1, edgeCreationNoFundingTx)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// We expect this to fail as the transaction isn't present in the
|
|
||||||
// chain (nor the block).
|
|
||||||
assertChanChainRejection(t, ctx, edge, ErrNoFundingTransaction)
|
|
||||||
|
|
||||||
// Next, we'll make another channel edge, but actually add it to the
|
|
||||||
// graph this time.
|
|
||||||
edge, err = newChannelEdgeInfo(t, ctx, 2, edgeCreationNoUTXO)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
// Instead now, we'll remove it from the set of UTXOs which should
|
|
||||||
// cause the spentness validation to fail.
|
|
||||||
assertChanChainRejection(t, ctx, edge, ErrChannelSpent)
|
|
||||||
|
|
||||||
// If we cause the funding transaction the chain to fail validation, we
|
|
||||||
// should see similar behavior.
|
|
||||||
edge, err = newChannelEdgeInfo(t, ctx, 3, edgeCreationBadScript)
|
|
||||||
require.Nil(t, err)
|
|
||||||
assertChanChainRejection(t, ctx, edge, ErrInvalidFundingOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestBlockDifferenceFix tests if when the router is behind on blocks, the
|
// TestBlockDifferenceFix tests if when the router is behind on blocks, the
|
||||||
// router catches up to the best block head.
|
// router catches up to the best block head.
|
||||||
func TestBlockDifferenceFix(t *testing.T) {
|
func TestBlockDifferenceFix(t *testing.T) {
|
||||||
|
@ -63,10 +63,11 @@ type ChannelEdgeInfo struct {
|
|||||||
// the value output in the outpoint that created this channel.
|
// the value output in the outpoint that created this channel.
|
||||||
Capacity btcutil.Amount
|
Capacity btcutil.Amount
|
||||||
|
|
||||||
// TapscriptRoot is the optional Merkle root of the tapscript tree if
|
// FundingScript holds the script of the channel's funding transaction.
|
||||||
// this channel is a taproot channel that also commits to a tapscript
|
//
|
||||||
// tree (custom channel).
|
// NOTE: this is not currently persisted and so will not be present if
|
||||||
TapscriptRoot fn.Option[chainhash.Hash]
|
// the edge object is loaded from the database.
|
||||||
|
FundingScript fn.Option[[]byte]
|
||||||
|
|
||||||
// ExtraOpaqueData is the set of data that was appended to this
|
// ExtraOpaqueData is the set of data that was appended to this
|
||||||
// message, some of which we may not actually know how to iterate or
|
// message, some of which we may not actually know how to iterate or
|
||||||
|
@ -2,25 +2,6 @@ package graph
|
|||||||
|
|
||||||
import "github.com/go-errors/errors"
|
import "github.com/go-errors/errors"
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNoFundingTransaction is returned when we are unable to find the
|
|
||||||
// funding transaction described by the short channel ID on chain.
|
|
||||||
ErrNoFundingTransaction = errors.New(
|
|
||||||
"unable to find the funding transaction",
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrInvalidFundingOutput is returned if the channel funding output
|
|
||||||
// fails validation.
|
|
||||||
ErrInvalidFundingOutput = errors.New(
|
|
||||||
"channel funding output validation failed",
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrChannelSpent is returned when we go to validate a channel, but
|
|
||||||
// the purported funding output has actually already been spent on
|
|
||||||
// chain.
|
|
||||||
ErrChannelSpent = errors.New("channel output has been spent")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrorCode is used to represent the various errors that can occur within this
|
// ErrorCode is used to represent the various errors that can occur within this
|
||||||
// package.
|
// package.
|
||||||
type ErrorCode uint8
|
type ErrorCode uint8
|
||||||
|
@ -85,6 +85,13 @@ type ChannelGraphSource interface {
|
|||||||
// public key. channeldb.ErrGraphNodeNotFound is returned if the node
|
// public key. channeldb.ErrGraphNodeNotFound is returned if the node
|
||||||
// doesn't exist within the graph.
|
// doesn't exist within the graph.
|
||||||
FetchLightningNode(route.Vertex) (*models.LightningNode, error)
|
FetchLightningNode(route.Vertex) (*models.LightningNode, error)
|
||||||
|
|
||||||
|
// MarkZombieEdge marks the channel with the given ID as a zombie edge.
|
||||||
|
MarkZombieEdge(chanID uint64) error
|
||||||
|
|
||||||
|
// IsZombieEdge returns true if the edge with the given channel ID is
|
||||||
|
// currently marked as a zombie edge.
|
||||||
|
IsZombieEdge(chanID lnwire.ShortChannelID) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DB is an interface describing a persisted Lightning Network graph.
|
// DB is an interface describing a persisted Lightning Network graph.
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
|
"github.com/lightningnetwork/lnd/fn/v2"
|
||||||
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
||||||
"github.com/lightningnetwork/lnd/graph/db/models"
|
"github.com/lightningnetwork/lnd/graph/db/models"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
@ -24,7 +25,6 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
lnmock "github.com/lightningnetwork/lnd/lntest/mock"
|
lnmock "github.com/lightningnetwork/lnd/lntest/mock"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/routing/chainview"
|
"github.com/lightningnetwork/lnd/routing/chainview"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
@ -126,18 +126,18 @@ func randEdgePolicy(chanID *lnwire.ShortChannelID,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createChannelEdge(ctx *testCtx, bitcoinKey1, bitcoinKey2 []byte,
|
func createChannelEdge(bitcoinKey1, bitcoinKey2 []byte,
|
||||||
chanValue btcutil.Amount, fundingHeight uint32) (*wire.MsgTx, *wire.OutPoint,
|
chanValue btcutil.Amount, fundingHeight uint32) ([]byte, *wire.MsgTx,
|
||||||
*lnwire.ShortChannelID, error) {
|
*wire.OutPoint, *lnwire.ShortChannelID, error) {
|
||||||
|
|
||||||
fundingTx := wire.NewMsgTx(2)
|
fundingTx := wire.NewMsgTx(2)
|
||||||
_, tx, err := input.GenFundingPkScript(
|
script, tx, err := input.GenFundingPkScript(
|
||||||
bitcoinKey1,
|
bitcoinKey1,
|
||||||
bitcoinKey2,
|
bitcoinKey2,
|
||||||
int64(chanValue),
|
int64(chanValue),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fundingTx.TxOut = append(fundingTx.TxOut, tx)
|
fundingTx.TxOut = append(fundingTx.TxOut, tx)
|
||||||
@ -146,9 +146,6 @@ func createChannelEdge(ctx *testCtx, bitcoinKey1, bitcoinKey2 []byte,
|
|||||||
Index: 0,
|
Index: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the utxo constructed, we'll mark it as closed.
|
|
||||||
ctx.chain.addUtxo(chanUtxo, tx)
|
|
||||||
|
|
||||||
// Our fake channel will be "confirmed" at height 101.
|
// Our fake channel will be "confirmed" at height 101.
|
||||||
chanID := &lnwire.ShortChannelID{
|
chanID := &lnwire.ShortChannelID{
|
||||||
BlockHeight: fundingHeight,
|
BlockHeight: fundingHeight,
|
||||||
@ -156,16 +153,16 @@ func createChannelEdge(ctx *testCtx, bitcoinKey1, bitcoinKey2 []byte,
|
|||||||
TxPosition: 0,
|
TxPosition: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
return fundingTx, &chanUtxo, chanID, nil
|
return script, fundingTx, &chanUtxo, chanID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockChain struct {
|
type mockChain struct {
|
||||||
|
lnwallet.BlockChainIO
|
||||||
|
|
||||||
blocks map[chainhash.Hash]*wire.MsgBlock
|
blocks map[chainhash.Hash]*wire.MsgBlock
|
||||||
blockIndex map[uint32]chainhash.Hash
|
blockIndex map[uint32]chainhash.Hash
|
||||||
blockHeightIndex map[chainhash.Hash]uint32
|
blockHeightIndex map[chainhash.Hash]uint32
|
||||||
|
|
||||||
utxos map[wire.OutPoint]wire.TxOut
|
|
||||||
|
|
||||||
bestHeight int32
|
bestHeight int32
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
@ -179,7 +176,6 @@ func newMockChain(currentHeight uint32) *mockChain {
|
|||||||
chain := &mockChain{
|
chain := &mockChain{
|
||||||
bestHeight: int32(currentHeight),
|
bestHeight: int32(currentHeight),
|
||||||
blocks: make(map[chainhash.Hash]*wire.MsgBlock),
|
blocks: make(map[chainhash.Hash]*wire.MsgBlock),
|
||||||
utxos: make(map[wire.OutPoint]wire.TxOut),
|
|
||||||
blockIndex: make(map[uint32]chainhash.Hash),
|
blockIndex: make(map[uint32]chainhash.Hash),
|
||||||
blockHeightIndex: make(map[chainhash.Hash]uint32),
|
blockHeightIndex: make(map[chainhash.Hash]uint32),
|
||||||
}
|
}
|
||||||
@ -213,10 +209,6 @@ func (m *mockChain) GetBestBlock() (*chainhash.Hash, int32, error) {
|
|||||||
return &blockHash, m.bestHeight, nil
|
return &blockHash, m.bestHeight, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockChain) GetTransaction(txid *chainhash.Hash) (*wire.MsgTx, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockChain) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
|
func (m *mockChain) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
|
||||||
m.RLock()
|
m.RLock()
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
@ -230,31 +222,6 @@ func (m *mockChain) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
|
|||||||
return &hash, nil
|
return &hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockChain) addUtxo(op wire.OutPoint, out *wire.TxOut) {
|
|
||||||
m.Lock()
|
|
||||||
m.utxos[op] = *out
|
|
||||||
m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockChain) delUtxo(op wire.OutPoint) {
|
|
||||||
m.Lock()
|
|
||||||
delete(m.utxos, op)
|
|
||||||
m.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockChain) GetUtxo(op *wire.OutPoint, _ []byte, _ uint32,
|
|
||||||
_ <-chan struct{}) (*wire.TxOut, error) {
|
|
||||||
m.RLock()
|
|
||||||
defer m.RUnlock()
|
|
||||||
|
|
||||||
utxo, ok := m.utxos[*op]
|
|
||||||
if !ok {
|
|
||||||
return nil, btcwallet.ErrOutputSpent
|
|
||||||
}
|
|
||||||
|
|
||||||
return &utxo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockChain) addBlock(block *wire.MsgBlock, height uint32, nonce uint32) {
|
func (m *mockChain) addBlock(block *wire.MsgBlock, height uint32, nonce uint32) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
block.Header.Nonce = nonce
|
block.Header.Nonce = nonce
|
||||||
@ -459,9 +426,10 @@ func TestEdgeUpdateNotification(t *testing.T) {
|
|||||||
|
|
||||||
// First we'll create the utxo for the channel to be "closed"
|
// First we'll create the utxo for the channel to be "closed"
|
||||||
const chanValue = 10000
|
const chanValue = 10000
|
||||||
fundingTx, chanPoint, chanID, err := createChannelEdge(ctx,
|
script, fundingTx, chanPoint, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
chanValue, 0)
|
bitcoinKey2.SerializeCompressed(), chanValue, 0,
|
||||||
|
)
|
||||||
require.NoError(t, err, "unable create channel edge")
|
require.NoError(t, err, "unable create channel edge")
|
||||||
|
|
||||||
// We'll also add a record for the block that included our funding
|
// We'll also add a record for the block that included our funding
|
||||||
@ -488,6 +456,9 @@ func TestEdgeUpdateNotification(t *testing.T) {
|
|||||||
BitcoinSig1Bytes: testSig.Serialize(),
|
BitcoinSig1Bytes: testSig.Serialize(),
|
||||||
BitcoinSig2Bytes: testSig.Serialize(),
|
BitcoinSig2Bytes: testSig.Serialize(),
|
||||||
},
|
},
|
||||||
|
ChannelPoint: *chanPoint,
|
||||||
|
Capacity: chanValue,
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
@ -643,10 +614,11 @@ func TestNodeUpdateNotification(t *testing.T) {
|
|||||||
// We only accept node announcements from nodes having a known channel,
|
// We only accept node announcements from nodes having a known channel,
|
||||||
// so create one now.
|
// so create one now.
|
||||||
const chanValue = 10000
|
const chanValue = 10000
|
||||||
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
script, fundingTx, _, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
chanValue, startingBlockHeight)
|
chanValue, startingBlockHeight,
|
||||||
|
)
|
||||||
require.NoError(t, err, "unable create channel edge")
|
require.NoError(t, err, "unable create channel edge")
|
||||||
|
|
||||||
// We'll also add a record for the block that included our funding
|
// We'll also add a record for the block that included our funding
|
||||||
@ -675,6 +647,7 @@ func TestNodeUpdateNotification(t *testing.T) {
|
|||||||
BitcoinSig1Bytes: testSig.Serialize(),
|
BitcoinSig1Bytes: testSig.Serialize(),
|
||||||
BitcoinSig2Bytes: testSig.Serialize(),
|
BitcoinSig2Bytes: testSig.Serialize(),
|
||||||
},
|
},
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
@ -825,10 +798,11 @@ func TestNotificationCancellation(t *testing.T) {
|
|||||||
|
|
||||||
// We'll create the utxo for a new channel.
|
// We'll create the utxo for a new channel.
|
||||||
const chanValue = 10000
|
const chanValue = 10000
|
||||||
fundingTx, _, chanID, err := createChannelEdge(ctx,
|
script, fundingTx, chanPoint, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
bitcoinKey2.SerializeCompressed(),
|
bitcoinKey2.SerializeCompressed(),
|
||||||
chanValue, startingBlockHeight)
|
chanValue, startingBlockHeight,
|
||||||
|
)
|
||||||
require.NoError(t, err, "unable create channel edge")
|
require.NoError(t, err, "unable create channel edge")
|
||||||
|
|
||||||
// We'll also add a record for the block that included our funding
|
// We'll also add a record for the block that included our funding
|
||||||
@ -859,6 +833,9 @@ func TestNotificationCancellation(t *testing.T) {
|
|||||||
BitcoinSig1Bytes: testSig.Serialize(),
|
BitcoinSig1Bytes: testSig.Serialize(),
|
||||||
BitcoinSig2Bytes: testSig.Serialize(),
|
BitcoinSig2Bytes: testSig.Serialize(),
|
||||||
},
|
},
|
||||||
|
ChannelPoint: *chanPoint,
|
||||||
|
Capacity: chanValue,
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
@ -899,9 +876,11 @@ func TestChannelCloseNotification(t *testing.T) {
|
|||||||
|
|
||||||
// First we'll create the utxo for the channel to be "closed"
|
// First we'll create the utxo for the channel to be "closed"
|
||||||
const chanValue = 10000
|
const chanValue = 10000
|
||||||
fundingTx, chanUtxo, chanID, err := createChannelEdge(ctx,
|
script, fundingTx, chanUtxo, chanID, err := createChannelEdge(
|
||||||
bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(),
|
bitcoinKey1.SerializeCompressed(),
|
||||||
chanValue, startingBlockHeight)
|
bitcoinKey2.SerializeCompressed(), chanValue,
|
||||||
|
startingBlockHeight,
|
||||||
|
)
|
||||||
require.NoError(t, err, "unable create channel edge")
|
require.NoError(t, err, "unable create channel edge")
|
||||||
|
|
||||||
// We'll also add a record for the block that included our funding
|
// We'll also add a record for the block that included our funding
|
||||||
@ -928,6 +907,9 @@ func TestChannelCloseNotification(t *testing.T) {
|
|||||||
BitcoinSig1Bytes: testSig.Serialize(),
|
BitcoinSig1Bytes: testSig.Serialize(),
|
||||||
BitcoinSig2Bytes: testSig.Serialize(),
|
BitcoinSig2Bytes: testSig.Serialize(),
|
||||||
},
|
},
|
||||||
|
ChannelPoint: *chanUtxo,
|
||||||
|
Capacity: chanValue,
|
||||||
|
FundingScript: fn.Some(script),
|
||||||
}
|
}
|
||||||
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
|
||||||
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
|
||||||
|
@ -1142,6 +1142,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
FindChannel: s.findChannel,
|
FindChannel: s.findChannel,
|
||||||
IsStillZombieChannel: s.graphBuilder.IsZombieChannel,
|
IsStillZombieChannel: s.graphBuilder.IsZombieChannel,
|
||||||
ScidCloser: scidCloserMan,
|
ScidCloser: scidCloserMan,
|
||||||
|
AssumeChannelValid: cfg.Routing.AssumeChannelValid,
|
||||||
}, nodeKeyDesc)
|
}, nodeKeyDesc)
|
||||||
|
|
||||||
selfVertex := route.Vertex(nodeKeyDesc.PubKey.SerializeCompressed())
|
selfVertex := route.Vertex(nodeKeyDesc.PubKey.SerializeCompressed())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user