Merge pull request #8805 from breez/jssdwt-insert-edge-when-not-found

localchans: recreate missing edge if not found
This commit is contained in:
Oliver Gugger
2024-11-22 08:42:51 +01:00
committed by GitHub
12 changed files with 554 additions and 23 deletions

31
routing/localchans/log.go Normal file
View File

@@ -0,0 +1,31 @@
package localchans
import (
"github.com/btcsuite/btclog/v2"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
const Subsystem = "LCHN"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}

View File

@@ -1,10 +1,13 @@
package localchans
import (
"bytes"
"errors"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
@@ -19,6 +22,12 @@ import (
// Manager manages the node's local channels. The only operation that is
// currently implemented is updating forwarding policies.
type Manager struct {
// SelfPub contains the public key of the local node.
SelfPub *btcec.PublicKey
// DefaultRoutingPolicy is the default routing policy.
DefaultRoutingPolicy models.ForwardingPolicy
// UpdateForwardingPolicies is used by the manager to update active
// links with a new policy.
UpdateForwardingPolicies func(
@@ -30,7 +39,7 @@ type Manager struct {
edgesToUpdate []discovery.EdgeWithInfo) error
// ForAllOutgoingChannels is required to iterate over all our local
// channels.
// channels. The ChannelEdgePolicy parameter may be nil.
ForAllOutgoingChannels func(cb func(kvdb.RTx,
*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy) error) error
@@ -40,6 +49,9 @@ type Manager struct {
FetchChannel func(tx kvdb.RTx, chanPoint wire.OutPoint) (
*channeldb.OpenChannel, error)
// AddEdge is used to add edge/channel to the topology of the router.
AddEdge func(edge *models.ChannelEdgeInfo) error
// policyUpdateLock ensures that the database and the link do not fall
// out of sync if there are concurrent fee update calls. Without it,
// there is a chance that policy A updates the database, then policy B
@@ -51,7 +63,8 @@ type Manager struct {
// UpdatePolicy updates the policy for the specified channels on disk and in
// the active links.
func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
chanPoints ...wire.OutPoint) ([]*lnrpc.FailedUpdate, error) {
createMissingEdge bool, chanPoints ...wire.OutPoint) (
[]*lnrpc.FailedUpdate, error) {
r.policyUpdateLock.Lock()
defer r.policyUpdateLock.Unlock()
@@ -69,10 +82,8 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
var edgesToUpdate []discovery.EdgeWithInfo
policiesToUpdate := make(map[wire.OutPoint]models.ForwardingPolicy)
// Next, we'll loop over all the outgoing channels the router knows of.
// If we have a filter then we'll only collected those channels,
// otherwise we'll collect them all.
err := r.ForAllOutgoingChannels(func(
// NOTE: edge may be nil when this function is called.
processChan := func(
tx kvdb.RTx,
info *models.ChannelEdgeInfo,
edge *models.ChannelEdgePolicy) error {
@@ -88,8 +99,24 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
// will be used to report invalid channels later on.
delete(unprocessedChans, info.ChannelPoint)
if edge == nil {
log.Errorf("Got nil channel edge policy when updating "+
"a channel. Channel point: %v",
info.ChannelPoint.String())
failedUpdates = append(failedUpdates, makeFailureItem(
info.ChannelPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_NOT_FOUND,
"edge policy not found",
))
return nil
}
// Apply the new policy to the edge.
err := r.updateEdge(tx, info.ChannelPoint, edge, newSchema)
err := r.updateEdge(
tx, info.ChannelPoint, edge, newSchema,
)
if err != nil {
failedUpdates = append(failedUpdates,
makeFailureItem(info.ChannelPoint,
@@ -125,7 +152,12 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
}
return nil
})
}
// Next, we'll loop over all the outgoing channels the router knows of.
// If we have a filter then we'll only collect those channels, otherwise
// we'll collect them all.
err := r.ForAllOutgoingChannels(processChan)
if err != nil {
return nil, err
}
@@ -155,7 +187,37 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
"not yet confirmed",
))
// If the edge was not found, but the channel is found, that
// means the edge is missing in the graph database and should be
// recreated. The edge and policy are created in-memory. The
// edge is inserted in createEdge below and the policy will be
// added to the graph in the PropagateChanPolicyUpdate call
// below.
case createMissingEdge:
log.Warnf("Missing edge for active channel (%s) "+
"during policy update. Recreating edge with "+
"default policy.",
channel.FundingOutpoint.String())
info, edge, failedUpdate := r.createMissingEdge(
channel, newSchema,
)
if failedUpdate == nil {
err = processChan(nil, info, edge)
if err != nil {
return nil, err
}
} else {
failedUpdates = append(
failedUpdates, failedUpdate,
)
}
default:
log.Warnf("Missing edge for active channel (%s) "+
"during policy update. Could not update "+
"policy.", channel.FundingOutpoint.String())
failedUpdates = append(failedUpdates,
makeFailureItem(chanPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN,
@@ -180,11 +242,124 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
return failedUpdates, nil
}
func (r *Manager) createMissingEdge(channel *channeldb.OpenChannel,
newSchema routing.ChannelPolicy) (*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *lnrpc.FailedUpdate) {
info, edge, err := r.createEdge(channel, time.Now())
if err != nil {
log.Errorf("Failed to recreate missing edge "+
"for channel (%s): %v",
channel.FundingOutpoint.String(), err)
return nil, nil, makeFailureItem(
channel.FundingOutpoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN,
"could not update policies",
)
}
// Validate the newly created edge policy with the user defined new
// schema before adding the edge to the database.
err = r.updateEdge(nil, channel.FundingOutpoint, edge, newSchema)
if err != nil {
return nil, nil, makeFailureItem(
info.ChannelPoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_INVALID_PARAMETER,
err.Error(),
)
}
// Insert the edge into the database to avoid `edge not
// found` errors during policy update propagation.
err = r.AddEdge(info)
if err != nil {
log.Errorf("Attempt to add missing edge for "+
"channel (%s) errored with: %v",
channel.FundingOutpoint.String(), err)
return nil, nil, makeFailureItem(
channel.FundingOutpoint,
lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN,
"could not add edge",
)
}
return info, edge, nil
}
// createEdge recreates an edge and policy from an open channel in-memory.
func (r *Manager) createEdge(channel *channeldb.OpenChannel,
timestamp time.Time) (*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, error) {
nodeKey1Bytes := r.SelfPub.SerializeCompressed()
nodeKey2Bytes := channel.IdentityPub.SerializeCompressed()
bitcoinKey1Bytes := channel.LocalChanCfg.MultiSigKey.PubKey.
SerializeCompressed()
bitcoinKey2Bytes := channel.RemoteChanCfg.MultiSigKey.PubKey.
SerializeCompressed()
channelFlags := lnwire.ChanUpdateChanFlags(0)
// Make it such that node_id_1 is the lexicographically-lesser of the
// two compressed keys sorted in ascending lexicographic order.
if bytes.Compare(nodeKey2Bytes, nodeKey1Bytes) < 0 {
nodeKey1Bytes, nodeKey2Bytes = nodeKey2Bytes, nodeKey1Bytes
bitcoinKey1Bytes, bitcoinKey2Bytes = bitcoinKey2Bytes,
bitcoinKey1Bytes
channelFlags = 1
}
var featureBuf bytes.Buffer
err := lnwire.NewRawFeatureVector().Encode(&featureBuf)
if err != nil {
return nil, nil, fmt.Errorf("unable to encode features: %w",
err)
}
info := &models.ChannelEdgeInfo{
ChannelID: channel.ShortChanID().ToUint64(),
ChainHash: channel.ChainHash,
Features: featureBuf.Bytes(),
Capacity: channel.Capacity,
ChannelPoint: channel.FundingOutpoint,
}
copy(info.NodeKey1Bytes[:], nodeKey1Bytes)
copy(info.NodeKey2Bytes[:], nodeKey2Bytes)
copy(info.BitcoinKey1Bytes[:], bitcoinKey1Bytes)
copy(info.BitcoinKey2Bytes[:], bitcoinKey2Bytes)
// Construct a dummy channel edge policy with default values that will
// be updated with the new values in the call to processChan below.
timeLockDelta := uint16(r.DefaultRoutingPolicy.TimeLockDelta)
edge := &models.ChannelEdgePolicy{
ChannelID: channel.ShortChanID().ToUint64(),
LastUpdate: timestamp,
TimeLockDelta: timeLockDelta,
ChannelFlags: channelFlags,
MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc,
FeeBaseMSat: r.DefaultRoutingPolicy.BaseFee,
FeeProportionalMillionths: r.DefaultRoutingPolicy.FeeRate,
MinHTLC: r.DefaultRoutingPolicy.MinHTLCOut,
MaxHTLC: r.DefaultRoutingPolicy.MaxHTLC,
}
copy(edge.ToNode[:], channel.IdentityPub.SerializeCompressed())
return info, edge, nil
}
// updateEdge updates the given edge with the new schema.
func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint,
edge *models.ChannelEdgePolicy,
newSchema routing.ChannelPolicy) error {
channel, err := r.FetchChannel(tx, chanPoint)
if err != nil {
return err
}
// Update forwarding fee scheme and required time lock delta.
edge.FeeBaseMSat = newSchema.BaseFee
edge.FeeProportionalMillionths = lnwire.MilliSatoshi(
@@ -192,7 +367,7 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint,
)
// If inbound fees are set, we update the edge with them.
err := fn.MapOptionZ(newSchema.InboundFee,
err = fn.MapOptionZ(newSchema.InboundFee,
func(f models.InboundFee) error {
inboundWireFee := f.ToWire()
return edge.ExtraOpaqueData.PackRecords(
@@ -206,7 +381,7 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint,
edge.TimeLockDelta = uint16(newSchema.TimeLockDelta)
// Retrieve negotiated channel htlc amt limits.
amtMin, amtMax, err := r.getHtlcAmtLimits(tx, chanPoint)
amtMin, amtMax, err := r.getHtlcAmtLimits(channel)
if err != nil {
return err
}
@@ -267,14 +442,9 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint,
// getHtlcAmtLimits retrieves the negotiated channel min and max htlc amount
// constraints.
func (r *Manager) getHtlcAmtLimits(tx kvdb.RTx, chanPoint wire.OutPoint) (
func (r *Manager) getHtlcAmtLimits(ch *channeldb.OpenChannel) (
lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
ch, err := r.FetchChannel(tx, chanPoint)
if err != nil {
return 0, 0, err
}
// The max htlc policy field must be less than or equal to the channel
// capacity AND less than or equal to the max in-flight HTLC value.
// Since the latter is always less than or equal to the former, just

View File

@@ -1,14 +1,19 @@
package localchans
import (
"encoding/hex"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
@@ -35,6 +40,18 @@ func TestManager(t *testing.T) {
channelSet []channel
)
sp := [33]byte{}
_, _ = hex.Decode(sp[:], []byte("028d7500dd4c12685d1f568b4c2b5048e85"+
"34b873319f3a8daa612b469132ec7f7"))
rp := [33]byte{}
_, _ = hex.Decode(rp[:], []byte("034f355bdcb7cc0af728ef3cceb9615d906"+
"84bb5b2ca5f859ab0f0b704075871aa"))
selfpub, _ := btcec.ParsePubKey(sp[:])
remotepub, _ := btcec.ParsePubKey(rp[:])
localMultisigPrivKey, _ := btcec.NewPrivateKey()
localMultisigKey := localMultisigPrivKey.PubKey()
remoteMultisigPrivKey, _ := btcec.NewPrivateKey()
remoteMultisigKey := remoteMultisigPrivKey.PubKey()
newPolicy := routing.ChannelPolicy{
FeeSchema: routing.FeeSchema{
BaseFee: 100,
@@ -131,17 +148,42 @@ func TestManager(t *testing.T) {
}
return &channeldb.OpenChannel{
FundingOutpoint: chanPointValid,
IdentityPub: remotepub,
LocalChanCfg: channeldb.ChannelConfig{
ChannelStateBounds: bounds,
MultiSigKey: keychain.KeyDescriptor{
PubKey: localMultisigKey,
},
},
RemoteChanCfg: channeldb.ChannelConfig{
ChannelStateBounds: bounds,
MultiSigKey: keychain.KeyDescriptor{
PubKey: remoteMultisigKey,
},
},
}, nil
}
addEdge := func(edge *models.ChannelEdgeInfo) error {
return nil
}
manager := Manager{
UpdateForwardingPolicies: updateForwardingPolicies,
PropagateChanPolicyUpdate: propagateChanPolicyUpdate,
ForAllOutgoingChannels: forAllOutgoingChannels,
FetchChannel: fetchChannel,
SelfPub: selfpub,
DefaultRoutingPolicy: models.ForwardingPolicy{
MinHTLCOut: minHTLC,
MaxHTLC: maxPendingAmount,
BaseFee: lnwire.MilliSatoshi(1000),
FeeRate: lnwire.MilliSatoshi(0),
InboundFee: models.InboundFee{},
TimeLockDelta: 80,
},
AddEdge: addEdge,
}
// Policy with no max htlc value.
@@ -156,6 +198,7 @@ func TestManager(t *testing.T) {
newPolicy routing.ChannelPolicy
channelSet []channel
specifiedChanPoints []wire.OutPoint
createMissingEdge bool
expectedNumUpdates int
expectedUpdateFailures []lnrpc.UpdateFailure
expectErr error
@@ -173,6 +216,7 @@ func TestManager(t *testing.T) {
},
},
specifiedChanPoints: []wire.OutPoint{chanPointValid},
createMissingEdge: false,
expectedNumUpdates: 1,
expectedUpdateFailures: []lnrpc.UpdateFailure{},
expectErr: nil,
@@ -190,6 +234,7 @@ func TestManager(t *testing.T) {
},
},
specifiedChanPoints: []wire.OutPoint{},
createMissingEdge: false,
expectedNumUpdates: 1,
expectedUpdateFailures: []lnrpc.UpdateFailure{},
expectErr: nil,
@@ -207,6 +252,7 @@ func TestManager(t *testing.T) {
},
},
specifiedChanPoints: []wire.OutPoint{chanPointMissing},
createMissingEdge: false,
expectedNumUpdates: 0,
expectedUpdateFailures: []lnrpc.UpdateFailure{
lnrpc.UpdateFailure_UPDATE_FAILURE_NOT_FOUND,
@@ -228,10 +274,39 @@ func TestManager(t *testing.T) {
},
},
specifiedChanPoints: []wire.OutPoint{chanPointValid},
createMissingEdge: false,
expectedNumUpdates: 1,
expectedUpdateFailures: []lnrpc.UpdateFailure{},
expectErr: nil,
},
{
// Here, the edge is missing, causing the edge to be
// recreated.
name: "missing edge recreated",
currentPolicy: models.ChannelEdgePolicy{},
newPolicy: newPolicy,
channelSet: []channel{},
specifiedChanPoints: []wire.OutPoint{chanPointValid},
createMissingEdge: true,
expectedNumUpdates: 1,
expectedUpdateFailures: []lnrpc.UpdateFailure{},
expectErr: nil,
},
{
// Here, the edge is missing, but the edge will not be
// recreated, because createMissingEdge is false.
name: "missing edge ignored",
currentPolicy: models.ChannelEdgePolicy{},
newPolicy: newPolicy,
channelSet: []channel{},
specifiedChanPoints: []wire.OutPoint{chanPointValid},
createMissingEdge: false,
expectedNumUpdates: 0,
expectedUpdateFailures: []lnrpc.UpdateFailure{
lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN,
},
expectErr: nil,
},
}
for _, test := range tests {
@@ -242,6 +317,7 @@ func TestManager(t *testing.T) {
expectedNumUpdates = test.expectedNumUpdates
failedUpdates, err := manager.UpdatePolicy(test.newPolicy,
test.createMissingEdge,
test.specifiedChanPoints...)
if len(failedUpdates) != len(test.expectedUpdateFailures) {
@@ -258,3 +334,179 @@ func TestManager(t *testing.T) {
})
}
}
// Tests creating a new edge in the manager where the local pubkey is the
// lexicographically lesser or the two.
func TestCreateEdgeLower(t *testing.T) {
sp := [33]byte{}
_, _ = hex.Decode(sp[:], []byte("028d7500dd4c12685d1f568b4c2b5048e85"+
"34b873319f3a8daa612b469132ec7f7"))
rp := [33]byte{}
_, _ = hex.Decode(rp[:], []byte("034f355bdcb7cc0af728ef3cceb9615d906"+
"84bb5b2ca5f859ab0f0b704075871aa"))
selfpub, _ := btcec.ParsePubKey(sp[:])
remotepub, _ := btcec.ParsePubKey(rp[:])
localMultisigPrivKey, _ := btcec.NewPrivateKey()
localMultisigKey := localMultisigPrivKey.PubKey()
remoteMultisigPrivKey, _ := btcec.NewPrivateKey()
remoteMultisigKey := remoteMultisigPrivKey.PubKey()
timestamp := time.Now()
defaultPolicy := models.ForwardingPolicy{
MinHTLCOut: 1,
MaxHTLC: 2,
BaseFee: 3,
FeeRate: 4,
InboundFee: models.InboundFee{
Base: 5,
Rate: 6,
},
TimeLockDelta: 7,
}
channel := &channeldb.OpenChannel{
IdentityPub: remotepub,
LocalChanCfg: channeldb.ChannelConfig{
MultiSigKey: keychain.KeyDescriptor{
PubKey: localMultisigKey,
},
},
RemoteChanCfg: channeldb.ChannelConfig{
MultiSigKey: keychain.KeyDescriptor{
PubKey: remoteMultisigKey,
},
},
ShortChannelID: lnwire.NewShortChanIDFromInt(8),
ChainHash: *chaincfg.RegressionNetParams.GenesisHash,
Capacity: 9,
FundingOutpoint: wire.OutPoint{
Hash: chainhash.Hash([32]byte{}),
Index: 0,
},
}
expectedInfo := &models.ChannelEdgeInfo{
ChannelID: 8,
ChainHash: channel.ChainHash,
Features: []byte{0, 0},
Capacity: 9,
ChannelPoint: channel.FundingOutpoint,
NodeKey1Bytes: sp,
NodeKey2Bytes: rp,
BitcoinKey1Bytes: [33]byte(
localMultisigKey.SerializeCompressed()),
BitcoinKey2Bytes: [33]byte(
remoteMultisigKey.SerializeCompressed()),
AuthProof: nil,
ExtraOpaqueData: nil,
}
expectedEdge := &models.ChannelEdgePolicy{
ChannelID: 8,
LastUpdate: timestamp,
TimeLockDelta: 7,
ChannelFlags: 0,
MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc,
FeeBaseMSat: 3,
FeeProportionalMillionths: 4,
MinHTLC: 1,
MaxHTLC: 2,
SigBytes: nil,
ToNode: rp,
ExtraOpaqueData: nil,
}
manager := Manager{
SelfPub: selfpub,
DefaultRoutingPolicy: defaultPolicy,
}
info, edge, err := manager.createEdge(channel, timestamp)
require.NoError(t, err)
require.Equal(t, expectedInfo, info)
require.Equal(t, expectedEdge, edge)
}
// Tests creating a new edge in the manager where the local pubkey is the
// lexicographically higher or the two.
func TestCreateEdgeHigher(t *testing.T) {
sp := [33]byte{}
_, _ = hex.Decode(sp[:], []byte("034f355bdcb7cc0af728ef3cceb9615d906"+
"84bb5b2ca5f859ab0f0b704075871aa"))
rp := [33]byte{}
_, _ = hex.Decode(rp[:], []byte("028d7500dd4c12685d1f568b4c2b5048e85"+
"34b873319f3a8daa612b469132ec7f7"))
selfpub, _ := btcec.ParsePubKey(sp[:])
remotepub, _ := btcec.ParsePubKey(rp[:])
localMultisigPrivKey, _ := btcec.NewPrivateKey()
localMultisigKey := localMultisigPrivKey.PubKey()
remoteMultisigPrivKey, _ := btcec.NewPrivateKey()
remoteMultisigKey := remoteMultisigPrivKey.PubKey()
timestamp := time.Now()
defaultPolicy := models.ForwardingPolicy{
MinHTLCOut: 1,
MaxHTLC: 2,
BaseFee: 3,
FeeRate: 4,
InboundFee: models.InboundFee{
Base: 5,
Rate: 6,
},
TimeLockDelta: 7,
}
channel := &channeldb.OpenChannel{
IdentityPub: remotepub,
LocalChanCfg: channeldb.ChannelConfig{
MultiSigKey: keychain.KeyDescriptor{
PubKey: localMultisigKey,
},
},
RemoteChanCfg: channeldb.ChannelConfig{
MultiSigKey: keychain.KeyDescriptor{
PubKey: remoteMultisigKey,
},
},
ShortChannelID: lnwire.NewShortChanIDFromInt(8),
ChainHash: *chaincfg.RegressionNetParams.GenesisHash,
Capacity: 9,
FundingOutpoint: wire.OutPoint{
Hash: chainhash.Hash([32]byte{}),
Index: 0,
},
}
expectedInfo := &models.ChannelEdgeInfo{
ChannelID: 8,
ChainHash: channel.ChainHash,
Features: []byte{0, 0},
Capacity: 9,
ChannelPoint: channel.FundingOutpoint,
NodeKey1Bytes: rp,
NodeKey2Bytes: sp,
BitcoinKey1Bytes: [33]byte(
remoteMultisigKey.SerializeCompressed()),
BitcoinKey2Bytes: [33]byte(
localMultisigKey.SerializeCompressed()),
AuthProof: nil,
ExtraOpaqueData: nil,
}
expectedEdge := &models.ChannelEdgePolicy{
ChannelID: 8,
LastUpdate: timestamp,
TimeLockDelta: 7,
ChannelFlags: 1,
MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc,
FeeBaseMSat: 3,
FeeProportionalMillionths: 4,
MinHTLC: 1,
MaxHTLC: 2,
SigBytes: nil,
ToNode: rp,
ExtraOpaqueData: nil,
}
manager := Manager{
SelfPub: selfpub,
DefaultRoutingPolicy: defaultPolicy,
}
info, edge, err := manager.createEdge(channel, timestamp)
require.NoError(t, err)
require.Equal(t, expectedInfo, info)
require.Equal(t, expectedEdge, edge)
}