refactor: move various duties from ChannelRouter to graph.Builder
This commit is a large refactor that moves over various responsibilities from the ChannelRouter to the graph.Builder. These include all graph related tasks such as: - graph pruning - validation of new network updates & persisting new updates - notifying topology update clients of any changes. This is a large commit but: - many of the files are purely moved from `routing` to `graph` - the business logic put in the graph Builder is copied exactly as is from the ChannelRouter with one exception: - The ChannelRouter just needs to be able to call the Builder's `ApplyChannelUpdate` method. So this is now exported and provided to the ChannelRouter as a config option. - The trickiest part was just moving over the test code since quite a bit had to be duplicated.
This commit is contained in:
26 files changed
+5121
-3862
No files matched your search
@@ -1,190 +0,0 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// ValidateChannelAnn validates the channel announcement message and checks
|
||||
// that node signatures covers the announcement message, and that the bitcoin
|
||||
// signatures covers the node keys.
|
||||
func ValidateChannelAnn(a *lnwire.ChannelAnnouncement) error {
|
||||
// First, we'll compute the digest (h) which is to be signed by each of
|
||||
// the keys included within the node announcement message. This hash
|
||||
// digest includes all the keys, so the (up to 4 signatures) will
|
||||
// attest to the validity of each of the keys.
|
||||
data, err := a.DataToSign()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataHash := chainhash.DoubleHashB(data)
|
||||
|
||||
// First we'll verify that the passed bitcoin key signature is indeed a
|
||||
// signature over the computed hash digest.
|
||||
bitcoinSig1, err := a.BitcoinSig1.ToSignature()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bitcoinKey1, err := btcec.ParsePubKey(a.BitcoinKey1[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bitcoinSig1.Verify(dataHash, bitcoinKey1) {
|
||||
return errors.New("can't verify first bitcoin signature")
|
||||
}
|
||||
|
||||
// If that checks out, then we'll verify that the second bitcoin
|
||||
// signature is a valid signature of the bitcoin public key over hash
|
||||
// digest as well.
|
||||
bitcoinSig2, err := a.BitcoinSig2.ToSignature()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bitcoinKey2, err := btcec.ParsePubKey(a.BitcoinKey2[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bitcoinSig2.Verify(dataHash, bitcoinKey2) {
|
||||
return errors.New("can't verify second bitcoin signature")
|
||||
}
|
||||
|
||||
// Both node signatures attached should indeed be a valid signature
|
||||
// over the selected digest of the channel announcement signature.
|
||||
nodeSig1, err := a.NodeSig1.ToSignature()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeKey1, err := btcec.ParsePubKey(a.NodeID1[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !nodeSig1.Verify(dataHash, nodeKey1) {
|
||||
return errors.New("can't verify data in first node signature")
|
||||
}
|
||||
|
||||
nodeSig2, err := a.NodeSig2.ToSignature()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeKey2, err := btcec.ParsePubKey(a.NodeID2[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !nodeSig2.Verify(dataHash, nodeKey2) {
|
||||
return errors.New("can't verify data in second node signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// ValidateNodeAnn validates the node announcement by ensuring that the
|
||||
// attached signature is needed a signature of the node announcement under the
|
||||
// specified node public key.
|
||||
func ValidateNodeAnn(a *lnwire.NodeAnnouncement) error {
|
||||
// Reconstruct the data of announcement which should be covered by the
|
||||
// signature so we can verify the signature shortly below
|
||||
data, err := a.DataToSign()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodeSig, err := a.Signature.ToSignature()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeKey, err := btcec.ParsePubKey(a.NodeID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally ensure that the passed signature is valid, if not we'll
|
||||
// return an error so this node announcement can be rejected.
|
||||
dataHash := chainhash.DoubleHashB(data)
|
||||
if !nodeSig.Verify(dataHash, nodeKey) {
|
||||
var msgBuf bytes.Buffer
|
||||
if _, err := lnwire.WriteMessage(&msgBuf, a, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.Errorf("signature on NodeAnnouncement(%x) is "+
|
||||
"invalid: %x", nodeKey.SerializeCompressed(),
|
||||
msgBuf.Bytes())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateChannelUpdateAnn validates the channel update announcement by
|
||||
// checking (1) that the included signature covers the announcement and has been
|
||||
// signed by the node's private key, and (2) that the announcement's message
|
||||
// flags and optional fields are sane.
|
||||
func ValidateChannelUpdateAnn(pubKey *btcec.PublicKey, capacity btcutil.Amount,
|
||||
a *lnwire.ChannelUpdate) error {
|
||||
|
||||
if err := ValidateChannelUpdateFields(capacity, a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return VerifyChannelUpdateSignature(a, pubKey)
|
||||
}
|
||||
|
||||
// VerifyChannelUpdateSignature verifies that the channel update message was
|
||||
// signed by the party with the given node public key.
|
||||
func VerifyChannelUpdateSignature(msg *lnwire.ChannelUpdate,
|
||||
pubKey *btcec.PublicKey) error {
|
||||
|
||||
data, err := msg.DataToSign()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to reconstruct message data: %w", err)
|
||||
}
|
||||
dataHash := chainhash.DoubleHashB(data)
|
||||
|
||||
nodeSig, err := msg.Signature.ToSignature()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !nodeSig.Verify(dataHash, pubKey) {
|
||||
return fmt.Errorf("invalid signature for channel update %v",
|
||||
spew.Sdump(msg))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateChannelUpdateFields validates a channel update's message flags and
|
||||
// corresponding update fields.
|
||||
func ValidateChannelUpdateFields(capacity btcutil.Amount,
|
||||
msg *lnwire.ChannelUpdate) error {
|
||||
|
||||
// The maxHTLC flag is mandatory.
|
||||
if !msg.MessageFlags.HasMaxHtlc() {
|
||||
return errors.Errorf("max htlc flag not set for channel "+
|
||||
"update %v", spew.Sdump(msg))
|
||||
}
|
||||
|
||||
maxHtlc := msg.HtlcMaximumMsat
|
||||
if maxHtlc == 0 || maxHtlc < msg.HtlcMinimumMsat {
|
||||
return errors.Errorf("invalid max htlc for channel "+
|
||||
"update %v", spew.Sdump(msg))
|
||||
}
|
||||
|
||||
// For light clients, the capacity will not be set so we'll skip
|
||||
// checking whether the MaxHTLC value respects the channel's
|
||||
// capacity.
|
||||
capacityMsat := lnwire.NewMSatFromSatoshis(capacity)
|
||||
if capacityMsat != 0 && maxHtlc > capacityMsat {
|
||||
return errors.Errorf("max_htlc (%v) for channel update "+
|
||||
"greater than capacity (%v)", maxHtlc, capacityMsat)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package routing
|
||||
|
||||
import "github.com/go-errors/errors"
|
||||
|
||||
// errorCode is used to represent the various errors that can occur within this
|
||||
// package.
|
||||
type errorCode uint8
|
||||
|
||||
const (
|
||||
// ErrOutdated is returned when the routing update already have
|
||||
// been applied, or a newer update is already known.
|
||||
ErrOutdated errorCode = iota
|
||||
|
||||
// ErrIgnored is returned when the update have been ignored because
|
||||
// this update can't bring us something new, or because a node
|
||||
// announcement was given for node not found in any channel.
|
||||
ErrIgnored
|
||||
|
||||
// ErrChannelSpent is returned when we go to validate a channel, but
|
||||
// the purported funding output has actually already been spent on
|
||||
// chain.
|
||||
ErrChannelSpent
|
||||
|
||||
// ErrNoFundingTransaction is returned when we are unable to find the
|
||||
// funding transaction described by the short channel ID on chain.
|
||||
ErrNoFundingTransaction
|
||||
|
||||
// ErrInvalidFundingOutput is returned if the channel funding output
|
||||
// fails validation.
|
||||
ErrInvalidFundingOutput
|
||||
|
||||
// ErrVBarrierShuttingDown signals that the barrier has been requested
|
||||
// to shutdown, and that the caller should not treat the wait condition
|
||||
// as fulfilled.
|
||||
ErrVBarrierShuttingDown
|
||||
|
||||
// ErrParentValidationFailed signals that the validation of a
|
||||
// dependent's parent failed, so the dependent must not be processed.
|
||||
ErrParentValidationFailed
|
||||
)
|
||||
|
||||
// routerError is a structure that represent the error inside the routing package,
|
||||
// this structure carries additional information about error code in order to
|
||||
// be able distinguish errors outside of the current package.
|
||||
type routerError struct {
|
||||
err *errors.Error
|
||||
code errorCode
|
||||
}
|
||||
|
||||
// Error represents errors as the string
|
||||
// NOTE: Part of the error interface.
|
||||
func (e *routerError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
// A compile time check to ensure routerError implements the error interface.
|
||||
var _ error = (*routerError)(nil)
|
||||
|
||||
// newErrf creates a routerError by the given error formatted description and
|
||||
// its corresponding error code.
|
||||
func newErrf(code errorCode, format string, a ...interface{}) *routerError {
|
||||
return &routerError{
|
||||
code: code,
|
||||
err: errors.Errorf(format, a...),
|
||||
}
|
||||
}
|
||||
|
||||
// IsError is a helper function which is needed to have ability to check that
|
||||
// returned error has specific error code.
|
||||
func IsError(e interface{}, codes ...errorCode) bool {
|
||||
err, ok := e.(*routerError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, code := range codes {
|
||||
if err.code == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/graph"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// TopologyClient represents an intent to receive notifications from the
|
||||
// channel router regarding changes to the topology of the channel graph. The
|
||||
// TopologyChanges channel will be sent upon with new updates to the channel
|
||||
// graph in real-time as they're encountered.
|
||||
type TopologyClient struct {
|
||||
// TopologyChanges is a receive only channel that new channel graph
|
||||
// updates will be sent over.
|
||||
//
|
||||
// TODO(roasbeef): chan for each update type instead?
|
||||
TopologyChanges <-chan *TopologyChange
|
||||
|
||||
// Cancel is a function closure that should be executed when the client
|
||||
// wishes to cancel their notification intent. Doing so allows the
|
||||
// ChannelRouter to free up resources.
|
||||
Cancel func()
|
||||
}
|
||||
|
||||
// topologyClientUpdate is a message sent to the channel router to either
|
||||
// register a new topology client or re-register an existing client.
|
||||
type topologyClientUpdate struct {
|
||||
// cancel indicates if the update to the client is cancelling an
|
||||
// existing client's notifications. If not then this update will be to
|
||||
// register a new set of notifications.
|
||||
cancel bool
|
||||
|
||||
// clientID is the unique identifier for this client. Any further
|
||||
// updates (deleting or adding) to this notification client will be
|
||||
// dispatched according to the target clientID.
|
||||
clientID uint64
|
||||
|
||||
// ntfnChan is a *send-only* channel in which notifications should be
|
||||
// sent over from router -> client.
|
||||
ntfnChan chan<- *TopologyChange
|
||||
}
|
||||
|
||||
// SubscribeTopology returns a new topology client which can be used by the
|
||||
// caller to receive notifications whenever a change in the channel graph
|
||||
// topology occurs. Changes that will be sent at notifications include: new
|
||||
// nodes appearing, node updating their attributes, new channels, channels
|
||||
// closing, and updates in the routing policies of a channel's directed edges.
|
||||
func (r *ChannelRouter) SubscribeTopology() (*TopologyClient, error) {
|
||||
// If the router is not yet started, return an error to avoid a
|
||||
// deadlock waiting for it to handle the subscription request.
|
||||
if atomic.LoadUint32(&r.started) == 0 {
|
||||
return nil, fmt.Errorf("router not started")
|
||||
}
|
||||
|
||||
// We'll first atomically obtain the next ID for this client from the
|
||||
// incrementing client ID counter.
|
||||
clientID := atomic.AddUint64(&r.ntfnClientCounter, 1)
|
||||
|
||||
log.Debugf("New graph topology client subscription, client %v",
|
||||
clientID)
|
||||
|
||||
ntfnChan := make(chan *TopologyChange, 10)
|
||||
|
||||
select {
|
||||
case r.ntfnClientUpdates <- &topologyClientUpdate{
|
||||
cancel: false,
|
||||
clientID: clientID,
|
||||
ntfnChan: ntfnChan,
|
||||
}:
|
||||
case <-r.quit:
|
||||
return nil, errors.New("ChannelRouter shutting down")
|
||||
}
|
||||
|
||||
return &TopologyClient{
|
||||
TopologyChanges: ntfnChan,
|
||||
Cancel: func() {
|
||||
select {
|
||||
case r.ntfnClientUpdates <- &topologyClientUpdate{
|
||||
cancel: true,
|
||||
clientID: clientID,
|
||||
}:
|
||||
case <-r.quit:
|
||||
return
|
||||
}
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// topologyClient is a data-structure use by the channel router to couple the
|
||||
// client's notification channel along with a special "exit" channel that can
|
||||
// be used to cancel all lingering goroutines blocked on a send to the
|
||||
// notification channel.
|
||||
type topologyClient struct {
|
||||
// ntfnChan is a send-only channel that's used to propagate
|
||||
// notification s from the channel router to an instance of a
|
||||
// topologyClient client.
|
||||
ntfnChan chan<- *TopologyChange
|
||||
|
||||
// exit is a channel that is used internally by the channel router to
|
||||
// cancel any active un-consumed goroutine notifications.
|
||||
exit chan struct{}
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// notifyTopologyChange notifies all registered clients of a new change in
|
||||
// graph topology in a non-blocking.
|
||||
func (r *ChannelRouter) notifyTopologyChange(topologyDiff *TopologyChange) {
|
||||
|
||||
// notifyClient is a helper closure that will send topology updates to
|
||||
// the given client.
|
||||
notifyClient := func(clientID uint64, client *topologyClient) bool {
|
||||
client.wg.Add(1)
|
||||
|
||||
log.Tracef("Sending topology notification to client=%v, "+
|
||||
"NodeUpdates=%v, ChannelEdgeUpdates=%v, "+
|
||||
"ClosedChannels=%v", clientID,
|
||||
len(topologyDiff.NodeUpdates),
|
||||
len(topologyDiff.ChannelEdgeUpdates),
|
||||
len(topologyDiff.ClosedChannels))
|
||||
|
||||
go func(c *topologyClient) {
|
||||
defer c.wg.Done()
|
||||
|
||||
select {
|
||||
|
||||
// In this case we'll try to send the notification
|
||||
// directly to the upstream client consumer.
|
||||
case c.ntfnChan <- topologyDiff:
|
||||
|
||||
// If the client cancels the notifications, then we'll
|
||||
// exit early.
|
||||
case <-c.exit:
|
||||
|
||||
// Similarly, if the ChannelRouter itself exists early,
|
||||
// then we'll also exit ourselves.
|
||||
case <-r.quit:
|
||||
|
||||
}
|
||||
}(client)
|
||||
|
||||
// Always return true here so the following Range will iterate
|
||||
// all clients.
|
||||
return true
|
||||
}
|
||||
|
||||
// Range over the set of active clients, and attempt to send the
|
||||
// topology updates.
|
||||
r.topologyClients.Range(notifyClient)
|
||||
}
|
||||
|
||||
// TopologyChange represents a new set of modifications to the channel graph.
|
||||
// Topology changes will be dispatched in real-time as the ChannelGraph
|
||||
// validates and process modifications to the authenticated channel graph.
|
||||
type TopologyChange struct {
|
||||
// NodeUpdates is a slice of nodes which are either new to the channel
|
||||
// graph, or have had their attributes updated in an authenticated
|
||||
// manner.
|
||||
NodeUpdates []*NetworkNodeUpdate
|
||||
|
||||
// ChanelEdgeUpdates is a slice of channel edges which are either newly
|
||||
// opened and authenticated, or have had their routing policies
|
||||
// updated.
|
||||
ChannelEdgeUpdates []*ChannelEdgeUpdate
|
||||
|
||||
// ClosedChannels contains a slice of close channel summaries which
|
||||
// described which block a channel was closed at, and also carry
|
||||
// supplemental information such as the capacity of the former channel.
|
||||
ClosedChannels []*ClosedChanSummary
|
||||
}
|
||||
|
||||
// isEmpty returns true if the TopologyChange is empty. A TopologyChange is
|
||||
// considered empty, if it contains no *new* updates of any type.
|
||||
func (t *TopologyChange) isEmpty() bool {
|
||||
return len(t.NodeUpdates) == 0 && len(t.ChannelEdgeUpdates) == 0 &&
|
||||
len(t.ClosedChannels) == 0
|
||||
}
|
||||
|
||||
// ClosedChanSummary is a summary of a channel that was detected as being
|
||||
// closed by monitoring the blockchain. Once a channel's funding point has been
|
||||
// spent, the channel will automatically be marked as closed by the
|
||||
// ChainNotifier.
|
||||
//
|
||||
// TODO(roasbeef): add nodes involved?
|
||||
type ClosedChanSummary struct {
|
||||
// ChanID is the short-channel ID which uniquely identifies the
|
||||
// channel.
|
||||
ChanID uint64
|
||||
|
||||
// Capacity was the total capacity of the channel before it was closed.
|
||||
Capacity btcutil.Amount
|
||||
|
||||
// ClosedHeight is the height in the chain that the channel was closed
|
||||
// at.
|
||||
ClosedHeight uint32
|
||||
|
||||
// ChanPoint is the funding point, or the multi-sig utxo which
|
||||
// previously represented the channel.
|
||||
ChanPoint wire.OutPoint
|
||||
}
|
||||
|
||||
// createCloseSummaries takes in a slice of channels closed at the target block
|
||||
// height and creates a slice of summaries which of each channel closure.
|
||||
func createCloseSummaries(blockHeight uint32,
|
||||
closedChans ...*models.ChannelEdgeInfo) []*ClosedChanSummary {
|
||||
|
||||
closeSummaries := make([]*ClosedChanSummary, len(closedChans))
|
||||
for i, closedChan := range closedChans {
|
||||
closeSummaries[i] = &ClosedChanSummary{
|
||||
ChanID: closedChan.ChannelID,
|
||||
Capacity: closedChan.Capacity,
|
||||
ClosedHeight: blockHeight,
|
||||
ChanPoint: closedChan.ChannelPoint,
|
||||
}
|
||||
}
|
||||
|
||||
return closeSummaries
|
||||
}
|
||||
|
||||
// NetworkNodeUpdate is an update for a node within the Lightning Network. A
|
||||
// NetworkNodeUpdate is sent out either when a new node joins the network, or a
|
||||
// node broadcasts a new update with a newer time stamp that supersedes its
|
||||
// old update. All updates are properly authenticated.
|
||||
type NetworkNodeUpdate struct {
|
||||
// Addresses is a slice of all the node's known addresses.
|
||||
Addresses []net.Addr
|
||||
|
||||
// IdentityKey is the identity public key of the target node. This is
|
||||
// used to encrypt onion blobs as well as to authenticate any new
|
||||
// updates.
|
||||
IdentityKey *btcec.PublicKey
|
||||
|
||||
// Alias is the alias or nick name of the node.
|
||||
Alias string
|
||||
|
||||
// Color is the node's color in hex code format.
|
||||
Color string
|
||||
|
||||
// Features holds the set of features the node supports.
|
||||
Features *lnwire.FeatureVector
|
||||
}
|
||||
|
||||
// ChannelEdgeUpdate is an update for a new channel within the ChannelGraph.
|
||||
// This update is sent out once a new authenticated channel edge is discovered
|
||||
// within the network. These updates are directional, so if a channel is fully
|
||||
// public, then there will be two updates sent out: one for each direction
|
||||
// within the channel. Each update will carry that particular routing edge
|
||||
// policy for the channel direction.
|
||||
//
|
||||
// An edge is a channel in the direction of AdvertisingNode -> ConnectingNode.
|
||||
type ChannelEdgeUpdate struct {
|
||||
// ChanID is the unique short channel ID for the channel. This encodes
|
||||
// where in the blockchain the channel's funding transaction was
|
||||
// originally confirmed.
|
||||
ChanID uint64
|
||||
|
||||
// ChanPoint is the outpoint which represents the multi-sig funding
|
||||
// output for the channel.
|
||||
ChanPoint wire.OutPoint
|
||||
|
||||
// Capacity is the capacity of the newly created channel.
|
||||
Capacity btcutil.Amount
|
||||
|
||||
// MinHTLC is the minimum HTLC amount that this channel will forward.
|
||||
MinHTLC lnwire.MilliSatoshi
|
||||
|
||||
// MaxHTLC is the maximum HTLC amount that this channel will forward.
|
||||
MaxHTLC lnwire.MilliSatoshi
|
||||
|
||||
// BaseFee is the base fee that will charged for all HTLC's forwarded
|
||||
// across the this channel direction.
|
||||
BaseFee lnwire.MilliSatoshi
|
||||
|
||||
// FeeRate is the fee rate that will be shared for all HTLC's forwarded
|
||||
// across this channel direction.
|
||||
FeeRate lnwire.MilliSatoshi
|
||||
|
||||
// TimeLockDelta is the time-lock expressed in blocks that will be
|
||||
// added to outgoing HTLC's from incoming HTLC's. This value is the
|
||||
// difference of the incoming and outgoing HTLC's time-locks routed
|
||||
// through this hop.
|
||||
TimeLockDelta uint16
|
||||
|
||||
// AdvertisingNode is the node that's advertising this edge.
|
||||
AdvertisingNode *btcec.PublicKey
|
||||
|
||||
// ConnectingNode is the node that the advertising node connects to.
|
||||
ConnectingNode *btcec.PublicKey
|
||||
|
||||
// Disabled, if true, signals that the channel is unavailable to relay
|
||||
// payments.
|
||||
Disabled bool
|
||||
|
||||
// ExtraOpaqueData is the set of data that was appended to this message
|
||||
// to fill out the full maximum transport message size. These fields can
|
||||
// be used to specify optional data such as custom TLV fields.
|
||||
ExtraOpaqueData lnwire.ExtraOpaqueData
|
||||
}
|
||||
|
||||
// appendTopologyChange appends the passed update message to the passed
|
||||
// TopologyChange, properly identifying which type of update the message
|
||||
// constitutes. This function will also fetch any required auxiliary
|
||||
// information required to create the topology change update from the graph
|
||||
// database.
|
||||
func addToTopologyChange(graph graph.DB, update *TopologyChange,
|
||||
msg interface{}) error {
|
||||
|
||||
switch m := msg.(type) {
|
||||
|
||||
// Any node announcement maps directly to a NetworkNodeUpdate struct.
|
||||
// No further data munging or db queries are required.
|
||||
case *channeldb.LightningNode:
|
||||
pubKey, err := m.PubKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodeUpdate := &NetworkNodeUpdate{
|
||||
Addresses: m.Addresses,
|
||||
IdentityKey: pubKey,
|
||||
Alias: m.Alias,
|
||||
Color: EncodeHexColor(m.Color),
|
||||
Features: m.Features.Clone(),
|
||||
}
|
||||
|
||||
update.NodeUpdates = append(update.NodeUpdates, nodeUpdate)
|
||||
return nil
|
||||
|
||||
// We ignore initial channel announcements as we'll only send out
|
||||
// updates once the individual edges themselves have been updated.
|
||||
case *models.ChannelEdgeInfo:
|
||||
return nil
|
||||
|
||||
// Any new ChannelUpdateAnnouncements will generate a corresponding
|
||||
// ChannelEdgeUpdate notification.
|
||||
case *models.ChannelEdgePolicy:
|
||||
// We'll need to fetch the edge's information from the database
|
||||
// in order to get the information concerning which nodes are
|
||||
// being connected.
|
||||
edgeInfo, _, _, err := graph.FetchChannelEdgesByID(m.ChannelID)
|
||||
if err != nil {
|
||||
return errors.Errorf("unable fetch channel edge: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
// If the flag is one, then the advertising node is actually
|
||||
// the second node.
|
||||
sourceNode := edgeInfo.NodeKey1
|
||||
connectingNode := edgeInfo.NodeKey2
|
||||
if m.ChannelFlags&lnwire.ChanUpdateDirection == 1 {
|
||||
sourceNode = edgeInfo.NodeKey2
|
||||
connectingNode = edgeInfo.NodeKey1
|
||||
}
|
||||
|
||||
aNode, err := sourceNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cNode, err := connectingNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edgeUpdate := &ChannelEdgeUpdate{
|
||||
ChanID: m.ChannelID,
|
||||
ChanPoint: edgeInfo.ChannelPoint,
|
||||
TimeLockDelta: m.TimeLockDelta,
|
||||
Capacity: edgeInfo.Capacity,
|
||||
MinHTLC: m.MinHTLC,
|
||||
MaxHTLC: m.MaxHTLC,
|
||||
BaseFee: m.FeeBaseMSat,
|
||||
FeeRate: m.FeeProportionalMillionths,
|
||||
AdvertisingNode: aNode,
|
||||
ConnectingNode: cNode,
|
||||
Disabled: m.ChannelFlags&lnwire.ChanUpdateDisabled != 0,
|
||||
ExtraOpaqueData: m.ExtraOpaqueData,
|
||||
}
|
||||
|
||||
// TODO(roasbeef): add bit to toggle
|
||||
update.ChannelEdgeUpdates = append(update.ChannelEdgeUpdates,
|
||||
edgeUpdate)
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unable to add to topology change, "+
|
||||
"unknown message type %T", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeHexColor takes a color and returns it in hex code format.
|
||||
func EncodeHexColor(color color.RGBA) string {
|
||||
return fmt.Sprintf("#%02x%02x%02x", color.R, color.G, color.B)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+23
-102
@@ -1392,10 +1392,6 @@ func TestNewRoute(t *testing.T) {
|
||||
// to fail or succeed.
|
||||
expectError bool
|
||||
|
||||
// expectedErrorCode indicates the expected error code when
|
||||
// expectError is true.
|
||||
expectedErrorCode errorCode
|
||||
|
||||
expectedMPP *record.MPP
|
||||
}{
|
||||
{
|
||||
@@ -1606,23 +1602,9 @@ func TestNewRoute(t *testing.T) {
|
||||
metadata: testCase.metadata,
|
||||
}, nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
if testCase.expectError {
|
||||
expectedCode := testCase.expectedErrorCode
|
||||
if err == nil || !IsError(err, expectedCode) {
|
||||
t.Fatalf("expected newRoute to fail "+
|
||||
"with error code %v but got "+
|
||||
"%v instead",
|
||||
expectedCode, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unable to create path: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assertRoute(t, route)
|
||||
}
|
||||
assertRoute(t, route)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2232,8 +2214,8 @@ func TestPathFindSpecExample(t *testing.T) {
|
||||
carol := ctx.aliases["C"]
|
||||
const amt lnwire.MilliSatoshi = 4999999
|
||||
req, err := NewRouteRequest(
|
||||
bob, &carol, amt, 0, noRestrictions, nil, nil, nil,
|
||||
MinCLTVDelta,
|
||||
bob, &carol, amt, 0, noRestrictions, nil, nil,
|
||||
nil, MinCLTVDelta,
|
||||
)
|
||||
require.NoError(t, err, "invalid route request")
|
||||
|
||||
@@ -2244,33 +2226,18 @@ func TestPathFindSpecExample(t *testing.T) {
|
||||
//
|
||||
// It should be sending the exact payment amount as there are no
|
||||
// additional hops.
|
||||
if route.TotalAmount != amt {
|
||||
t.Fatalf("wrong total amount: got %v, expected %v",
|
||||
route.TotalAmount, amt)
|
||||
}
|
||||
if route.Hops[0].AmtToForward != amt {
|
||||
t.Fatalf("wrong forward amount: got %v, expected %v",
|
||||
route.Hops[0].AmtToForward, amt)
|
||||
}
|
||||
|
||||
fee := route.HopFee(0)
|
||||
if fee != 0 {
|
||||
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0)
|
||||
}
|
||||
require.Equal(t, amt, route.TotalAmount)
|
||||
require.Equal(t, amt, route.Hops[0].AmtToForward)
|
||||
require.Zero(t, route.HopFee(0))
|
||||
|
||||
// The CLTV expiry should be the current height plus 18 (the expiry for
|
||||
// the B -> C channel.
|
||||
if route.TotalTimeLock !=
|
||||
startingHeight+MinCLTVDelta {
|
||||
|
||||
t.Fatalf("wrong total time lock: got %v, expecting %v",
|
||||
route.TotalTimeLock,
|
||||
startingHeight+MinCLTVDelta)
|
||||
}
|
||||
require.EqualValues(t, startingHeight+MinCLTVDelta, route.TotalTimeLock)
|
||||
|
||||
// Next, we'll set A as the source node so we can assert that we create
|
||||
// the proper route for any queries starting with Alice.
|
||||
alice := ctx.aliases["A"]
|
||||
ctx.router.cfg.SelfNode = alice
|
||||
|
||||
// We'll now request a route from A -> B -> C.
|
||||
req, err = NewRouteRequest(
|
||||
@@ -2283,32 +2250,21 @@ func TestPathFindSpecExample(t *testing.T) {
|
||||
require.NoError(t, err, "unable to find routes")
|
||||
|
||||
// The route should be two hops.
|
||||
if len(route.Hops) != 2 {
|
||||
t.Fatalf("route should be %v hops, is instead %v", 2,
|
||||
len(route.Hops))
|
||||
}
|
||||
require.Len(t, route.Hops, 2)
|
||||
|
||||
// The total amount should factor in a fee of 10199 and also use a CLTV
|
||||
// delta total of 38 (20 + 18),
|
||||
expectedAmt := lnwire.MilliSatoshi(5010198)
|
||||
if route.TotalAmount != expectedAmt {
|
||||
t.Fatalf("wrong amount: got %v, expected %v",
|
||||
route.TotalAmount, expectedAmt)
|
||||
}
|
||||
require.Equal(t, expectedAmt, route.TotalAmount)
|
||||
|
||||
expectedDelta := uint32(20 + MinCLTVDelta)
|
||||
if route.TotalTimeLock != startingHeight+expectedDelta {
|
||||
t.Fatalf("wrong total time lock: got %v, expecting %v",
|
||||
route.TotalTimeLock, startingHeight+expectedDelta)
|
||||
}
|
||||
require.Equal(t, startingHeight+expectedDelta, route.TotalTimeLock)
|
||||
|
||||
// Ensure that the hops of the route are properly crafted.
|
||||
//
|
||||
// After taking the fee, Bob should be forwarding the remainder which
|
||||
// is the exact payment to Bob.
|
||||
if route.Hops[0].AmtToForward != amt {
|
||||
t.Fatalf("wrong forward amount: got %v, expected %v",
|
||||
route.Hops[0].AmtToForward, amt)
|
||||
}
|
||||
require.Equal(t, amt, route.Hops[0].AmtToForward)
|
||||
|
||||
// We shouldn't pay any fee for the first, hop, but the fee for the
|
||||
// second hop posted fee should be exactly:
|
||||
@@ -2317,59 +2273,31 @@ func TestPathFindSpecExample(t *testing.T) {
|
||||
// hop, so we should get a fee of exactly:
|
||||
//
|
||||
// * 200 + 4999999 * 2000 / 1000000 = 10199
|
||||
|
||||
fee = route.HopFee(0)
|
||||
if fee != 10199 {
|
||||
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 10199)
|
||||
}
|
||||
require.EqualValues(t, 10199, route.HopFee(0))
|
||||
|
||||
// While for the final hop, as there's no additional hop afterwards, we
|
||||
// pay no fee.
|
||||
fee = route.HopFee(1)
|
||||
if fee != 0 {
|
||||
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0)
|
||||
}
|
||||
require.Zero(t, route.HopFee(1))
|
||||
|
||||
// The outgoing CLTV value itself should be the current height plus 30
|
||||
// to meet Carol's requirements.
|
||||
if route.Hops[0].OutgoingTimeLock !=
|
||||
startingHeight+MinCLTVDelta {
|
||||
|
||||
t.Fatalf("wrong total time lock: got %v, expecting %v",
|
||||
route.Hops[0].OutgoingTimeLock,
|
||||
startingHeight+MinCLTVDelta)
|
||||
}
|
||||
require.EqualValues(t, startingHeight+MinCLTVDelta,
|
||||
route.Hops[0].OutgoingTimeLock)
|
||||
|
||||
// For B -> C, we assert that the final hop also has the proper
|
||||
// parameters.
|
||||
lastHop := route.Hops[1]
|
||||
if lastHop.AmtToForward != amt {
|
||||
t.Fatalf("wrong forward amount: got %v, expected %v",
|
||||
lastHop.AmtToForward, amt)
|
||||
}
|
||||
if lastHop.OutgoingTimeLock !=
|
||||
startingHeight+MinCLTVDelta {
|
||||
|
||||
t.Fatalf("wrong total time lock: got %v, expecting %v",
|
||||
lastHop.OutgoingTimeLock,
|
||||
startingHeight+MinCLTVDelta)
|
||||
}
|
||||
require.EqualValues(t, amt, lastHop.AmtToForward)
|
||||
require.EqualValues(t, startingHeight+MinCLTVDelta, lastHop.OutgoingTimeLock)
|
||||
}
|
||||
|
||||
func assertExpectedPath(t *testing.T, aliasMap map[string]route.Vertex,
|
||||
path []*unifiedEdge, nodeAliases ...string) {
|
||||
|
||||
if len(path) != len(nodeAliases) {
|
||||
t.Fatalf("number of hops=(%v) and number of aliases=(%v) do "+
|
||||
"not match", len(path), len(nodeAliases))
|
||||
}
|
||||
require.Len(t, path, len(nodeAliases))
|
||||
|
||||
for i, hop := range path {
|
||||
if hop.policy.ToNodePubKey() != aliasMap[nodeAliases[i]] {
|
||||
t.Fatalf("expected %v to be pos #%v in hop, instead "+
|
||||
"%v was", nodeAliases[i], i,
|
||||
hop.policy.ToNodePubKey())
|
||||
}
|
||||
require.Equal(t, aliasMap[nodeAliases[i]], hop.policy.ToNodePubKey())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2380,9 +2308,7 @@ func TestNewRouteFromEmptyHops(t *testing.T) {
|
||||
|
||||
var source route.Vertex
|
||||
_, err := route.NewRouteFromHops(0, 0, source, []*route.Hop{})
|
||||
if err != route.ErrNoRouteHopsProvided {
|
||||
t.Fatalf("expected empty hops error: instead got: %v", err)
|
||||
}
|
||||
require.ErrorIs(t, err, route.ErrNoRouteHopsProvided)
|
||||
}
|
||||
|
||||
// runRestrictOutgoingChannel asserts that a outgoing channel restriction is
|
||||
@@ -2425,11 +2351,6 @@ func runRestrictOutgoingChannel(t *testing.T, useCache bool) {
|
||||
|
||||
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
|
||||
|
||||
const (
|
||||
startingHeight = 100
|
||||
finalHopCLTV = 1
|
||||
)
|
||||
|
||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||
target := ctx.keyFromAlias("target")
|
||||
outgoingChannelID := uint64(chanSourceB1)
|
||||
|
||||
@@ -912,7 +912,7 @@ func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
|
||||
}
|
||||
|
||||
// Apply channel update to the channel edge policy in our db.
|
||||
if !p.router.applyChannelUpdate(update) {
|
||||
if !p.router.cfg.ApplyChannelUpdate(update) {
|
||||
log.Debugf("Invalid channel update received: node=%v",
|
||||
errVertex)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/graph"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
@@ -412,7 +413,7 @@ func (p *paymentSession) UpdateAdditionalEdge(msg *lnwire.ChannelUpdate,
|
||||
pubKey *btcec.PublicKey, policy *models.CachedEdgePolicy) bool {
|
||||
|
||||
// Validate the message signature.
|
||||
if err := VerifyChannelUpdateSignature(msg, pubKey); err != nil {
|
||||
if err := graph.VerifyChannelUpdateSignature(msg, pubKey); err != nil {
|
||||
log.Errorf(
|
||||
"Unable to validate channel update signature: %v", err,
|
||||
)
|
||||
|
||||
+7
-1743
File diff suppressed because it is too large.
Load diff
+458
-1852
File diff suppressed because it is too large.
Load diff
@@ -1,69 +0,0 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// routerStats is a struct that tracks various updates to the graph and
|
||||
// facilitates aggregate logging of the statistics.
|
||||
type routerStats struct {
|
||||
numChannels uint32
|
||||
numUpdates uint32
|
||||
numNodes uint32
|
||||
lastReset time.Time
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// incNumEdges increments the number of discovered edges.
|
||||
func (g *routerStats) incNumEdgesDiscovered() {
|
||||
g.mu.Lock()
|
||||
g.numChannels++
|
||||
g.mu.Unlock()
|
||||
}
|
||||
|
||||
// incNumUpdates increments the number of channel updates processed.
|
||||
func (g *routerStats) incNumChannelUpdates() {
|
||||
g.mu.Lock()
|
||||
g.numUpdates++
|
||||
g.mu.Unlock()
|
||||
}
|
||||
|
||||
// incNumNodeUpdates increments the number of node updates processed.
|
||||
func (g *routerStats) incNumNodeUpdates() {
|
||||
g.mu.Lock()
|
||||
g.numNodes++
|
||||
g.mu.Unlock()
|
||||
}
|
||||
|
||||
// Empty returns true if all stats are zero.
|
||||
func (g *routerStats) Empty() bool {
|
||||
g.mu.RLock()
|
||||
isEmpty := g.numChannels == 0 &&
|
||||
g.numUpdates == 0 &&
|
||||
g.numNodes == 0
|
||||
g.mu.RUnlock()
|
||||
return isEmpty
|
||||
}
|
||||
|
||||
// Reset clears any router stats and sets the lastReset field to now.
|
||||
func (g *routerStats) Reset() {
|
||||
g.mu.Lock()
|
||||
g.numChannels = 0
|
||||
g.numUpdates = 0
|
||||
g.numNodes = 0
|
||||
g.lastReset = time.Now()
|
||||
g.mu.Unlock()
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the router stats.
|
||||
func (g *routerStats) String() string {
|
||||
g.mu.RLock()
|
||||
str := fmt.Sprintf("Processed channels=%d updates=%d nodes=%d in "+
|
||||
"last %v", g.numChannels, g.numUpdates, g.numNodes,
|
||||
time.Since(g.lastReset))
|
||||
g.mu.RUnlock()
|
||||
return str
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
// validationSignals contains two signals which allows the ValidationBarrier to
|
||||
// communicate back to the caller whether a dependent should be processed or not
|
||||
// based on whether its parent was successfully validated. Only one of these
|
||||
// signals is to be used at a time.
|
||||
type validationSignals struct {
|
||||
// allow is the signal used to allow a dependent to be processed.
|
||||
allow chan struct{}
|
||||
|
||||
// deny is the signal used to prevent a dependent from being processed.
|
||||
deny chan struct{}
|
||||
}
|
||||
|
||||
// ValidationBarrier is a barrier used to ensure proper validation order while
|
||||
// concurrently validating new announcements for channel edges, and the
|
||||
// attributes of channel edges. It uses this set of maps (protected by this
|
||||
// mutex) to track validation dependencies. For a given channel our
|
||||
// dependencies look like this: chanAnn <- chanUp <- nodeAnn. That is we must
|
||||
// validate the item on the left of the arrow before that on the right.
|
||||
type ValidationBarrier struct {
|
||||
// validationSemaphore is a channel of structs which is used as a
|
||||
// semaphore. Initially we'll fill this with a buffered channel of the
|
||||
// size of the number of active requests. Each new job will consume
|
||||
// from this channel, then restore the value upon completion.
|
||||
validationSemaphore chan struct{}
|
||||
|
||||
// chanAnnFinSignal is map that keep track of all the pending
|
||||
// ChannelAnnouncement like validation job going on. Once the job has
|
||||
// been completed, the channel will be closed unblocking any
|
||||
// dependants.
|
||||
chanAnnFinSignal map[lnwire.ShortChannelID]*validationSignals
|
||||
|
||||
// chanEdgeDependencies tracks any channel edge updates which should
|
||||
// wait until the completion of the ChannelAnnouncement before
|
||||
// proceeding. This is a dependency, as we can't validate the update
|
||||
// before we validate the announcement which creates the channel
|
||||
// itself.
|
||||
chanEdgeDependencies map[lnwire.ShortChannelID]*validationSignals
|
||||
|
||||
// nodeAnnDependencies tracks any pending NodeAnnouncement validation
|
||||
// jobs which should wait until the completion of the
|
||||
// ChannelAnnouncement before proceeding.
|
||||
nodeAnnDependencies map[route.Vertex]*validationSignals
|
||||
|
||||
quit chan struct{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewValidationBarrier creates a new instance of a validation barrier given
|
||||
// the total number of active requests, and a quit channel which will be used
|
||||
// to know when to kill pending, but unfilled jobs.
|
||||
func NewValidationBarrier(numActiveReqs int,
|
||||
quitChan chan struct{}) *ValidationBarrier {
|
||||
|
||||
v := &ValidationBarrier{
|
||||
chanAnnFinSignal: make(map[lnwire.ShortChannelID]*validationSignals),
|
||||
chanEdgeDependencies: make(map[lnwire.ShortChannelID]*validationSignals),
|
||||
nodeAnnDependencies: make(map[route.Vertex]*validationSignals),
|
||||
quit: quitChan,
|
||||
}
|
||||
|
||||
// We'll first initialize a set of semaphores to limit our concurrency
|
||||
// when validating incoming requests in parallel.
|
||||
v.validationSemaphore = make(chan struct{}, numActiveReqs)
|
||||
for i := 0; i < numActiveReqs; i++ {
|
||||
v.validationSemaphore <- struct{}{}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// InitJobDependencies will wait for a new job slot to become open, and then
|
||||
// sets up any dependent signals/trigger for the new job
|
||||
func (v *ValidationBarrier) InitJobDependencies(job interface{}) {
|
||||
// We'll wait for either a new slot to become open, or for the quit
|
||||
// channel to be closed.
|
||||
select {
|
||||
case <-v.validationSemaphore:
|
||||
case <-v.quit:
|
||||
}
|
||||
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
// Once a slot is open, we'll examine the message of the job, to see if
|
||||
// there need to be any dependent barriers set up.
|
||||
switch msg := job.(type) {
|
||||
|
||||
// If this is a channel announcement, then we'll need to set up den
|
||||
// tenancies, as we'll need to verify this before we verify any
|
||||
// ChannelUpdates for the same channel, or NodeAnnouncements of nodes
|
||||
// that are involved in this channel. This goes for both the wire
|
||||
// type,s and also the types that we use within the database.
|
||||
case *lnwire.ChannelAnnouncement:
|
||||
|
||||
// We ensure that we only create a new announcement signal iff,
|
||||
// one doesn't already exist, as there may be duplicate
|
||||
// announcements. We'll close this signal once the
|
||||
// ChannelAnnouncement has been validated. This will result in
|
||||
// all the dependent jobs being unlocked so they can finish
|
||||
// execution themselves.
|
||||
if _, ok := v.chanAnnFinSignal[msg.ShortChannelID]; !ok {
|
||||
// We'll create the channel that we close after we
|
||||
// validate this announcement. All dependants will
|
||||
// point to this same channel, so they'll be unblocked
|
||||
// at the same time.
|
||||
signals := &validationSignals{
|
||||
allow: make(chan struct{}),
|
||||
deny: make(chan struct{}),
|
||||
}
|
||||
|
||||
v.chanAnnFinSignal[msg.ShortChannelID] = signals
|
||||
v.chanEdgeDependencies[msg.ShortChannelID] = signals
|
||||
|
||||
v.nodeAnnDependencies[route.Vertex(msg.NodeID1)] = signals
|
||||
v.nodeAnnDependencies[route.Vertex(msg.NodeID2)] = signals
|
||||
}
|
||||
case *models.ChannelEdgeInfo:
|
||||
|
||||
shortID := lnwire.NewShortChanIDFromInt(msg.ChannelID)
|
||||
if _, ok := v.chanAnnFinSignal[shortID]; !ok {
|
||||
signals := &validationSignals{
|
||||
allow: make(chan struct{}),
|
||||
deny: make(chan struct{}),
|
||||
}
|
||||
|
||||
v.chanAnnFinSignal[shortID] = signals
|
||||
v.chanEdgeDependencies[shortID] = signals
|
||||
|
||||
v.nodeAnnDependencies[route.Vertex(msg.NodeKey1Bytes)] = signals
|
||||
v.nodeAnnDependencies[route.Vertex(msg.NodeKey2Bytes)] = signals
|
||||
}
|
||||
|
||||
// These other types don't have any dependants, so no further
|
||||
// initialization needs to be done beyond just occupying a job slot.
|
||||
case *models.ChannelEdgePolicy:
|
||||
return
|
||||
case *lnwire.ChannelUpdate:
|
||||
return
|
||||
case *lnwire.NodeAnnouncement:
|
||||
// TODO(roasbeef): node ann needs to wait on existing channel updates
|
||||
return
|
||||
case *channeldb.LightningNode:
|
||||
return
|
||||
case *lnwire.AnnounceSignatures:
|
||||
// TODO(roasbeef): need to wait on chan ann?
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// CompleteJob returns a free slot to the set of available job slots. This
|
||||
// should be called once a job has been fully completed. Otherwise, slots may
|
||||
// not be returned to the internal scheduling, causing a deadlock when a new
|
||||
// overflow job is attempted.
|
||||
func (v *ValidationBarrier) CompleteJob() {
|
||||
select {
|
||||
case v.validationSemaphore <- struct{}{}:
|
||||
case <-v.quit:
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForDependants will block until any jobs that this job dependants on have
|
||||
// finished executing. This allows us a graceful way to schedule goroutines
|
||||
// based on any pending uncompleted dependent jobs. If this job doesn't have an
|
||||
// active dependent, then this function will return immediately.
|
||||
func (v *ValidationBarrier) WaitForDependants(job interface{}) error {
|
||||
|
||||
var (
|
||||
signals *validationSignals
|
||||
ok bool
|
||||
jobDesc string
|
||||
)
|
||||
|
||||
// Acquire a lock to read ValidationBarrier.
|
||||
v.Lock()
|
||||
|
||||
switch msg := job.(type) {
|
||||
// Any ChannelUpdate or NodeAnnouncement jobs will need to wait on the
|
||||
// completion of any active ChannelAnnouncement jobs related to them.
|
||||
case *models.ChannelEdgePolicy:
|
||||
shortID := lnwire.NewShortChanIDFromInt(msg.ChannelID)
|
||||
signals, ok = v.chanEdgeDependencies[shortID]
|
||||
|
||||
jobDesc = fmt.Sprintf("job=lnwire.ChannelEdgePolicy, scid=%v",
|
||||
msg.ChannelID)
|
||||
|
||||
case *channeldb.LightningNode:
|
||||
vertex := route.Vertex(msg.PubKeyBytes)
|
||||
signals, ok = v.nodeAnnDependencies[vertex]
|
||||
|
||||
jobDesc = fmt.Sprintf("job=channeldb.LightningNode, pub=%s",
|
||||
vertex)
|
||||
|
||||
case *lnwire.ChannelUpdate:
|
||||
signals, ok = v.chanEdgeDependencies[msg.ShortChannelID]
|
||||
|
||||
jobDesc = fmt.Sprintf("job=lnwire.ChannelUpdate, scid=%v",
|
||||
msg.ShortChannelID.ToUint64())
|
||||
|
||||
case *lnwire.NodeAnnouncement:
|
||||
vertex := route.Vertex(msg.NodeID)
|
||||
signals, ok = v.nodeAnnDependencies[vertex]
|
||||
jobDesc = fmt.Sprintf("job=lnwire.NodeAnnouncement, pub=%s",
|
||||
vertex)
|
||||
|
||||
// Other types of jobs can be executed immediately, so we'll just
|
||||
// return directly.
|
||||
case *lnwire.AnnounceSignatures:
|
||||
// TODO(roasbeef): need to wait on chan ann?
|
||||
case *models.ChannelEdgeInfo:
|
||||
case *lnwire.ChannelAnnouncement:
|
||||
}
|
||||
|
||||
// Release the lock once the above read is finished.
|
||||
v.Unlock()
|
||||
|
||||
// If it's not ok, it means either the job is not a dependent type, or
|
||||
// it doesn't have a dependency signal. Either way, we can return
|
||||
// early.
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("Waiting for dependent on %s", jobDesc)
|
||||
|
||||
// If we do have an active job, then we'll wait until either the signal
|
||||
// is closed, or the set of jobs exits.
|
||||
select {
|
||||
case <-v.quit:
|
||||
return newErrf(ErrVBarrierShuttingDown,
|
||||
"validation barrier shutting down")
|
||||
|
||||
case <-signals.deny:
|
||||
log.Debugf("Signal deny for %s", jobDesc)
|
||||
return newErrf(ErrParentValidationFailed,
|
||||
"parent validation failed")
|
||||
|
||||
case <-signals.allow:
|
||||
log.Tracef("Signal allow for %s", jobDesc)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SignalDependants will allow/deny any jobs that are dependent on this job that
|
||||
// they can continue execution. If the job doesn't have any dependants, then
|
||||
// this function sill exit immediately.
|
||||
func (v *ValidationBarrier) SignalDependants(job interface{}, allow bool) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
switch msg := job.(type) {
|
||||
|
||||
// If we've just finished executing a ChannelAnnouncement, then we'll
|
||||
// close out the signal, and remove the signal from the map of active
|
||||
// ones. This will allow/deny any dependent jobs to continue execution.
|
||||
case *models.ChannelEdgeInfo:
|
||||
shortID := lnwire.NewShortChanIDFromInt(msg.ChannelID)
|
||||
finSignals, ok := v.chanAnnFinSignal[shortID]
|
||||
if ok {
|
||||
if allow {
|
||||
close(finSignals.allow)
|
||||
} else {
|
||||
close(finSignals.deny)
|
||||
}
|
||||
delete(v.chanAnnFinSignal, shortID)
|
||||
}
|
||||
case *lnwire.ChannelAnnouncement:
|
||||
finSignals, ok := v.chanAnnFinSignal[msg.ShortChannelID]
|
||||
if ok {
|
||||
if allow {
|
||||
close(finSignals.allow)
|
||||
} else {
|
||||
close(finSignals.deny)
|
||||
}
|
||||
delete(v.chanAnnFinSignal, msg.ShortChannelID)
|
||||
}
|
||||
|
||||
delete(v.chanEdgeDependencies, msg.ShortChannelID)
|
||||
|
||||
// For all other job types, we'll delete the tracking entries from the
|
||||
// map, as if we reach this point, then all dependants have already
|
||||
// finished executing and we can proceed.
|
||||
case *channeldb.LightningNode:
|
||||
delete(v.nodeAnnDependencies, route.Vertex(msg.PubKeyBytes))
|
||||
case *lnwire.NodeAnnouncement:
|
||||
delete(v.nodeAnnDependencies, route.Vertex(msg.NodeID))
|
||||
case *lnwire.ChannelUpdate:
|
||||
delete(v.chanEdgeDependencies, msg.ShortChannelID)
|
||||
case *models.ChannelEdgePolicy:
|
||||
shortID := lnwire.NewShortChanIDFromInt(msg.ChannelID)
|
||||
delete(v.chanEdgeDependencies, shortID)
|
||||
|
||||
case *lnwire.AnnounceSignatures:
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package routing_test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
)
|
||||
|
||||
// TestValidationBarrierSemaphore checks basic properties of the validation
|
||||
// barrier's semaphore wrt. enqueuing/dequeuing.
|
||||
func TestValidationBarrierSemaphore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
numTasks = 8
|
||||
numPendingTasks = 8
|
||||
timeout = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
quit := make(chan struct{})
|
||||
barrier := routing.NewValidationBarrier(numTasks, quit)
|
||||
|
||||
// Saturate the semaphore with jobs.
|
||||
for i := 0; i < numTasks; i++ {
|
||||
barrier.InitJobDependencies(nil)
|
||||
}
|
||||
|
||||
// Spawn additional tasks that will signal completion when added.
|
||||
jobAdded := make(chan struct{})
|
||||
for i := 0; i < numPendingTasks; i++ {
|
||||
go func() {
|
||||
barrier.InitJobDependencies(nil)
|
||||
jobAdded <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
// Check that no jobs are added while semaphore is full.
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
// Expected since no slots open.
|
||||
case <-jobAdded:
|
||||
t.Fatalf("job should not have been added")
|
||||
}
|
||||
|
||||
// Complete jobs one at a time and verify that they get added.
|
||||
for i := 0; i < numPendingTasks; i++ {
|
||||
barrier.CompleteJob()
|
||||
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
t.Fatalf("timeout waiting for job to be added")
|
||||
case <-jobAdded:
|
||||
// Expected since one slot opened up.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidationBarrierQuit checks that pending validation tasks will return an
|
||||
// error from WaitForDependants if the barrier's quit signal is canceled.
|
||||
func TestValidationBarrierQuit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
numTasks = 8
|
||||
timeout = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
quit := make(chan struct{})
|
||||
barrier := routing.NewValidationBarrier(2*numTasks, quit)
|
||||
|
||||
// Create a set of unique channel announcements that we will prep for
|
||||
// validation.
|
||||
anns := make([]*lnwire.ChannelAnnouncement, 0, numTasks)
|
||||
for i := 0; i < numTasks; i++ {
|
||||
anns = append(anns, &lnwire.ChannelAnnouncement{
|
||||
ShortChannelID: lnwire.NewShortChanIDFromInt(uint64(i)),
|
||||
NodeID1: nodeIDFromInt(uint64(2 * i)),
|
||||
NodeID2: nodeIDFromInt(uint64(2*i + 1)),
|
||||
})
|
||||
barrier.InitJobDependencies(anns[i])
|
||||
}
|
||||
|
||||
// Create a set of channel updates, that must wait until their
|
||||
// associated channel announcement has been verified.
|
||||
chanUpds := make([]*lnwire.ChannelUpdate, 0, numTasks)
|
||||
for i := 0; i < numTasks; i++ {
|
||||
chanUpds = append(chanUpds, &lnwire.ChannelUpdate{
|
||||
ShortChannelID: lnwire.NewShortChanIDFromInt(uint64(i)),
|
||||
})
|
||||
barrier.InitJobDependencies(chanUpds[i])
|
||||
}
|
||||
|
||||
// Spawn additional tasks that will send the error returned after
|
||||
// waiting for the announcements to finish. In the background, we will
|
||||
// iteratively queue the channel updates, which will send back the error
|
||||
// returned from waiting.
|
||||
jobErrs := make(chan error)
|
||||
for i := 0; i < numTasks; i++ {
|
||||
go func(ii int) {
|
||||
jobErrs <- barrier.WaitForDependants(chanUpds[ii])
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Check that no jobs are added while semaphore is full.
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
// Expected since no slots open.
|
||||
case <-jobErrs:
|
||||
t.Fatalf("job should not have been signaled")
|
||||
}
|
||||
|
||||
// Complete the first half of jobs, one at a time, verifying that they
|
||||
// get signaled. Then, quit the barrier and check that all others exit
|
||||
// with the correct error.
|
||||
for i := 0; i < numTasks; i++ {
|
||||
switch {
|
||||
// Signal completion for the first half of tasks, but only allow
|
||||
// dependents to be processed as well for the second quarter.
|
||||
case i < numTasks/4:
|
||||
barrier.SignalDependants(anns[i], false)
|
||||
barrier.CompleteJob()
|
||||
|
||||
case i < numTasks/2:
|
||||
barrier.SignalDependants(anns[i], true)
|
||||
barrier.CompleteJob()
|
||||
|
||||
// At midpoint, quit the validation barrier.
|
||||
case i == numTasks/2:
|
||||
close(quit)
|
||||
}
|
||||
|
||||
var err error
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
t.Fatalf("timeout waiting for job to be signaled")
|
||||
case err = <-jobErrs:
|
||||
}
|
||||
|
||||
switch {
|
||||
// First half should return without failure.
|
||||
case i < numTasks/4 && !routing.IsError(
|
||||
err, routing.ErrParentValidationFailed,
|
||||
):
|
||||
t.Fatalf("unexpected failure while waiting: %v", err)
|
||||
|
||||
case i >= numTasks/4 && i < numTasks/2 && err != nil:
|
||||
t.Fatalf("unexpected failure while waiting: %v", err)
|
||||
|
||||
// Last half should return the shutdown error.
|
||||
case i >= numTasks/2 && !routing.IsError(
|
||||
err, routing.ErrVBarrierShuttingDown,
|
||||
):
|
||||
t.Fatalf("expected failure after quitting: want %v, "+
|
||||
"got %v", routing.ErrVBarrierShuttingDown, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nodeIDFromInt creates a node ID by writing a uint64 to the first 8 bytes.
|
||||
func nodeIDFromInt(i uint64) [33]byte {
|
||||
var nodeID [33]byte
|
||||
binary.BigEndian.PutUint64(nodeID[:8], i)
|
||||
return nodeID
|
||||
}
|
||||
Reference in new issue
Block a user