From f1299fdd5780b8366b3160b5c0bc7664c57115af Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Tue, 2 Jul 2024 11:30:12 +0200 Subject: [PATCH 1/8] lnrpc: add create_missing_edge flag --- lnrpc/lightning.pb.go | 20 +++++++++++++++++++- lnrpc/lightning.proto | 9 +++++++++ lnrpc/lightning.swagger.json | 4 ++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index 2f8a9c42a..eabe952b2 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -15183,6 +15183,14 @@ type PolicyUpdateRequest struct { // Optional inbound fee. If unset, the previously set value will be // retained [EXPERIMENTAL]. InboundFee *InboundFee `protobuf:"bytes,10,opt,name=inbound_fee,json=inboundFee,proto3" json:"inbound_fee,omitempty"` + // Under unknown circumstances a channel can exist with a missing edge in + // the graph database. This can cause an 'edge not found' error when calling + // `getchaninfo` and/or cause the default channel policy to be used during + // forwards. Setting this flag will recreate the edge if not found, allowing + // updating this channel policy and fixing the missing edge problem for this + // channel permanently. For fields not set in this command, the default + // policy will be created. + CreateMissingEdge bool `protobuf:"varint,11,opt,name=create_missing_edge,json=createMissingEdge,proto3" json:"create_missing_edge,omitempty"` } func (x *PolicyUpdateRequest) Reset() { @@ -15294,6 +15302,13 @@ func (x *PolicyUpdateRequest) GetInboundFee() *InboundFee { return nil } +func (x *PolicyUpdateRequest) GetCreateMissingEdge() bool { + if x != nil { + return x.CreateMissingEdge + } + return false +} + type isPolicyUpdateRequest_Scope interface { isPolicyUpdateRequest_Scope() } @@ -20763,7 +20778,7 @@ var file_lightning_proto_rawDesc = []byte{ 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x62, 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0a, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x22, 0xaa, 0x03, 0x0a, + 0x52, 0x0a, 0x66, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x22, 0xda, 0x03, 0x0a, 0x13, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x12, 0x34, @@ -20790,6 +20805,9 @@ var file_lightning_proto_rawDesc = []byte{ 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x65, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x65, 0x65, + 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6e, 0x67, 0x5f, 0x65, 0x64, 0x67, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x45, 0x64, 0x67, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index 3d554352b..4ea163f20 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -4558,6 +4558,15 @@ message PolicyUpdateRequest { // Optional inbound fee. If unset, the previously set value will be // retained [EXPERIMENTAL]. InboundFee inbound_fee = 10; + + // Under unknown circumstances a channel can exist with a missing edge in + // the graph database. This can cause an 'edge not found' error when calling + // `getchaninfo` and/or cause the default channel policy to be used during + // forwards. Setting this flag will recreate the edge if not found, allowing + // updating this channel policy and fixing the missing edge problem for this + // channel permanently. For fields not set in this command, the default + // policy will be created. + bool create_missing_edge = 11; } enum UpdateFailure { diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index 64c36d17d..c4774334e 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -6751,6 +6751,10 @@ "inbound_fee": { "$ref": "#/definitions/lnrpcInboundFee", "description": "Optional inbound fee. If unset, the previously set value will be\nretained [EXPERIMENTAL]." + }, + "create_missing_edge": { + "type": "boolean", + "description": "Under unknown circumstances a channel can exist with a missing edge in\nthe graph database. This can cause an 'edge not found' error when calling\n`getchaninfo` and/or cause the default channel policy to be used during\nforwards. Setting this flag will recreate the edge if not found, allowing\nupdating this channel policy and fixing the missing edge problem for this\nchannel permanently. For fields not set in this command, the default\npolicy will be created." } } }, From aa2ddf77d0e4cd0c24aa680aed312ebe7013016a Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Tue, 2 Jul 2024 11:35:06 +0200 Subject: [PATCH 2/8] lncli: add create_missing_edge --- cmd/commands/commands.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/cmd/commands/commands.go b/cmd/commands/commands.go index de13022f0..ff20701e6 100644 --- a/cmd/commands/commands.go +++ b/cmd/commands/commands.go @@ -2359,6 +2359,21 @@ var updateChannelPolicyCommand = cli.Command{ "channels will be updated. Takes the form of " + "txid:output_index", }, + cli.BoolFlag{ + Name: "create_missing_edge", + Usage: "Under unknown circumstances a channel can " + + "exist with a missing edge in the graph " + + "database. This can cause an 'edge not " + + "found' error when calling `getchaninfo` " + + "and/or cause the default channel policy to " + + "be used during forwards. Setting this flag " + + "will recreate the edge if not found, " + + "allowing updating this channel policy and " + + "fixing the missing edge problem for this " + + "channel permanently. For fields not set in " + + "this command, the default policy will be " + + "created.", + }, }, Action: actionDecorator(updateChannelPolicy), } @@ -2518,11 +2533,14 @@ func updateChannelPolicy(ctx *cli.Context) error { } } + createMissingEdge := ctx.Bool("create_missing_edge") + req := &lnrpc.PolicyUpdateRequest{ - BaseFeeMsat: baseFee, - TimeLockDelta: uint32(timeLockDelta), - MaxHtlcMsat: ctx.Uint64("max_htlc_msat"), - InboundFee: inboundFee, + BaseFeeMsat: baseFee, + TimeLockDelta: uint32(timeLockDelta), + MaxHtlcMsat: ctx.Uint64("max_htlc_msat"), + InboundFee: inboundFee, + CreateMissingEdge: createMissingEdge, } if ctx.IsSet("min_htlc_msat") { From bb4d3db8bc3693dfbd999b96f06dfee41b674a2c Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 3 Jun 2024 12:08:29 +0200 Subject: [PATCH 3/8] localchans: recreate missing edge if not found If a node contains a channel, but doesn't have a corresponding edge in the graph database, updating the channel policy would fail. In this commit the edge is recreated if the channel exists. This ensures a node can recover from a missing edge in the graph database by calling updatechanpolicy. --- log.go | 2 + routing/localchans/log.go | 31 +++++++ routing/localchans/manager.go | 138 +++++++++++++++++++++++++++-- routing/localchans/manager_test.go | 6 ++ rpcserver.go | 2 +- server.go | 5 ++ 6 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 routing/localchans/log.go diff --git a/log.go b/log.go index c88208ef3..343d86f38 100644 --- a/log.go +++ b/log.go @@ -46,6 +46,7 @@ import ( "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/blindedpath" + "github.com/lightningnetwork/lnd/routing/localchans" "github.com/lightningnetwork/lnd/rpcperms" "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/sweep" @@ -172,6 +173,7 @@ func SetupLoggers(root *build.SubLoggerManager, interceptor signal.Interceptor) AddSubLogger(root, "CHFD", interceptor, chanfunding.UseLogger) AddSubLogger(root, "PEER", interceptor, peer.UseLogger) AddSubLogger(root, "CHCL", interceptor, chancloser.UseLogger) + AddSubLogger(root, "LCHN", interceptor, localchans.UseLogger) AddSubLogger(root, routing.Subsystem, interceptor, routing.UseLogger) AddSubLogger(root, routerrpc.Subsystem, interceptor, routerrpc.UseLogger) diff --git a/routing/localchans/log.go b/routing/localchans/log.go new file mode 100644 index 000000000..fdd233c04 --- /dev/null +++ b/routing/localchans/log.go @@ -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 +} diff --git a/routing/localchans/manager.go b/routing/localchans/manager.go index f0f9b88de..25df1002a 100644 --- a/routing/localchans/manager.go +++ b/routing/localchans/manager.go @@ -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( @@ -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,7 @@ 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( + processChan := func( tx kvdb.RTx, info *models.ChannelEdgeInfo, edge *models.ChannelEdgePolicy) error { @@ -125,7 +135,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 +170,56 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy, "not yet confirmed", )) + case createMissingEdge: + // 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. + log.Warnf("Missing edge for active channel (%s) "+ + "during policy update. Recreating edge with "+ + "default policy.", + channel.FundingOutpoint.String()) + + 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) + + f := lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN + failedUpdates = append(failedUpdates, + makeFailureItem(chanPoint, f, + "could not update policies", + )) + } + + // Insert the edge into the database to avoid `edge not + // found` errors during policy update propagation. + err = r.AddEdge(info) + if err != nil { + log.Warnf("Attempt to add missing edge for "+ + "channel (%s) errored with: %v", + channel.FundingOutpoint.String(), err) + + f := lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN + failedUpdates = append(failedUpdates, + makeFailureItem(chanPoint, f, + "could not add edge", + )) + } + + err = processChan(nil, info, edge) + if err != nil { + return nil, err + } + 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,6 +244,68 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy, return failedUpdates, 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, diff --git a/routing/localchans/manager_test.go b/routing/localchans/manager_test.go index 7594eef04..a60d782c8 100644 --- a/routing/localchans/manager_test.go +++ b/routing/localchans/manager_test.go @@ -156,6 +156,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 +174,7 @@ func TestManager(t *testing.T) { }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, + createMissingEdge: false, expectedNumUpdates: 1, expectedUpdateFailures: []lnrpc.UpdateFailure{}, expectErr: nil, @@ -190,6 +192,7 @@ func TestManager(t *testing.T) { }, }, specifiedChanPoints: []wire.OutPoint{}, + createMissingEdge: false, expectedNumUpdates: 1, expectedUpdateFailures: []lnrpc.UpdateFailure{}, expectErr: nil, @@ -207,6 +210,7 @@ func TestManager(t *testing.T) { }, }, specifiedChanPoints: []wire.OutPoint{chanPointMissing}, + createMissingEdge: false, expectedNumUpdates: 0, expectedUpdateFailures: []lnrpc.UpdateFailure{ lnrpc.UpdateFailure_UPDATE_FAILURE_NOT_FOUND, @@ -228,6 +232,7 @@ func TestManager(t *testing.T) { }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, + createMissingEdge: false, expectedNumUpdates: 1, expectedUpdateFailures: []lnrpc.UpdateFailure{}, expectErr: nil, @@ -242,6 +247,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) { diff --git a/rpcserver.go b/rpcserver.go index 43d922c38..cace7fb94 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -7762,7 +7762,7 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context, // With the scope resolved, we'll now send this to the local channel // manager so it can propagate the new policy for our target channel(s). failedUpdates, err := r.server.localChanMgr.UpdatePolicy(chanPolicy, - targetChans...) + req.CreateMissingEdge, targetChans...) if err != nil { return nil, err } diff --git a/server.go b/server.go index 73456735a..105484c20 100644 --- a/server.go +++ b/server.go @@ -1112,10 +1112,15 @@ func newServer(cfg *Config, listenAddrs []net.Addr, //nolint:lll s.localChanMgr = &localchans.Manager{ + SelfPub: nodeKeyDesc.PubKey, + DefaultRoutingPolicy: cc.RoutingPolicy, ForAllOutgoingChannels: s.graphBuilder.ForAllOutgoingChannels, PropagateChanPolicyUpdate: s.authGossiper.PropagateChanPolicyUpdate, UpdateForwardingPolicies: s.htlcSwitch.UpdateForwardingPolicies, FetchChannel: s.chanStateDB.FetchChannel, + AddEdge: func(edge *models.ChannelEdgeInfo) error { + return s.graphBuilder.AddEdge(edge) + }, } utxnStore, err := contractcourt.NewNurseryStore( From 5e5291d044d5b2234da06abaccd85a3a4c2b0b7f Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 4 Jul 2024 14:05:06 +0200 Subject: [PATCH 4/8] localchans: add test for createEdge and manager --- routing/localchans/manager_test.go | 246 +++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) diff --git a/routing/localchans/manager_test.go b/routing/localchans/manager_test.go index a60d782c8..f4512fab2 100644 --- a/routing/localchans/manager_test.go +++ b/routing/localchans/manager_test.go @@ -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. @@ -237,6 +279,34 @@ func TestManager(t *testing.T) { 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 { @@ -264,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) +} From 7d9d100e94cab9fc5adce139fe2a9a8b3dd77538 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 31 Oct 2024 12:56:44 +0100 Subject: [PATCH 5/8] localchans: add policy when missing --- routing/localchans/manager.go | 50 ++++++++++++++++++++++------------- server.go | 20 +++++++++++--- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/routing/localchans/manager.go b/routing/localchans/manager.go index 25df1002a..5b35b346b 100644 --- a/routing/localchans/manager.go +++ b/routing/localchans/manager.go @@ -39,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 @@ -82,6 +82,7 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy, var edgesToUpdate []discovery.EdgeWithInfo policiesToUpdate := make(map[wire.OutPoint]models.ForwardingPolicy) + // NOTE: edge may be nil when this function is called. processChan := func( tx kvdb.RTx, info *models.ChannelEdgeInfo, @@ -99,7 +100,9 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy, delete(unprocessedChans, info.ChannelPoint) // Apply the new policy to the edge. - err := r.updateEdge(tx, info.ChannelPoint, edge, newSchema) + edge, err := r.updateEdge( + tx, info.ChannelPoint, edge, newSchema, + ) if err != nil { failedUpdates = append(failedUpdates, makeFailureItem(info.ChannelPoint, @@ -306,10 +309,26 @@ func (r *Manager) createEdge(channel *channeldb.OpenChannel, return info, edge, nil } -// updateEdge updates the given edge with the new schema. +// updateEdge updates the given edge with the new schema. The edge parameter may +// be nil, in that case a new channel policy is returned. In other cases the +// passed in channel policy is returned after modification. func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint, edge *models.ChannelEdgePolicy, - newSchema routing.ChannelPolicy) error { + newSchema routing.ChannelPolicy) (*models.ChannelEdgePolicy, error) { + + channel, err := r.FetchChannel(tx, chanPoint) + if err != nil { + return nil, err + } + + // If due to some unforeseen circumstances the policy doesn't exist, + // recreate it here. + if edge == nil { + _, edge, err = r.createEdge(channel, time.Now()) + if err != nil { + return nil, err + } + } // Update forwarding fee scheme and required time lock delta. edge.FeeBaseMSat = newSchema.BaseFee @@ -318,7 +337,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( @@ -326,15 +345,15 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint, ) }) if err != nil { - return err + return nil, err } 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 + return nil, err } // We now update the edge max htlc value. @@ -367,19 +386,19 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint, // Validate htlc amount constraints. switch { case edge.MinHTLC < amtMin: - return fmt.Errorf( + return nil, fmt.Errorf( "min htlc amount of %v is below min htlc parameter of %v", edge.MinHTLC, amtMin, ) case edge.MaxHTLC > amtMax: - return fmt.Errorf( + return nil, fmt.Errorf( "max htlc size of %v is above max pending amount of %v", edge.MaxHTLC, amtMax, ) case edge.MinHTLC > edge.MaxHTLC: - return fmt.Errorf( + return nil, fmt.Errorf( "min_htlc %v greater than max_htlc %v", edge.MinHTLC, edge.MaxHTLC, ) @@ -388,19 +407,14 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint, // Clear signature to help prevent usage of the previous signature. edge.SetSigBytes(nil) - return nil + return edge, nil } // 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 diff --git a/server.go b/server.go index 105484c20..212fddebc 100644 --- a/server.go +++ b/server.go @@ -1110,11 +1110,25 @@ func newServer(cfg *Config, listenAddrs []net.Addr, ScidCloser: scidCloserMan, }, nodeKeyDesc) + selfVertex := route.Vertex(nodeKeyDesc.PubKey.SerializeCompressed()) //nolint:lll s.localChanMgr = &localchans.Manager{ - SelfPub: nodeKeyDesc.PubKey, - DefaultRoutingPolicy: cc.RoutingPolicy, - ForAllOutgoingChannels: s.graphBuilder.ForAllOutgoingChannels, + SelfPub: nodeKeyDesc.PubKey, + DefaultRoutingPolicy: cc.RoutingPolicy, + ForAllOutgoingChannels: func(cb func(kvdb.RTx, + *models.ChannelEdgeInfo, *models.ChannelEdgePolicy) error) error { + + return s.graphDB.ForEachNodeChannel(selfVertex, + func(tx kvdb.RTx, c *models.ChannelEdgeInfo, + e *models.ChannelEdgePolicy, + _ *models.ChannelEdgePolicy) error { + + // NOTE: The invoked callback here may + // receive a nil channel policy. + return cb(tx, c, e) + }, + ) + }, PropagateChanPolicyUpdate: s.authGossiper.PropagateChanPolicyUpdate, UpdateForwardingPolicies: s.htlcSwitch.UpdateForwardingPolicies, FetchChannel: s.chanStateDB.FetchChannel, From c84126309d81eb3973c490b48eb7787f208bf9fc Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 2 Aug 2024 14:26:09 +0200 Subject: [PATCH 6/8] channeldb: skip nil scheduler options This is a robustness option to ensure LND doesn't crash when this function is accidentally called with `AddChannelEdge(edge, nil)`. --- channeldb/graph.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/channeldb/graph.go b/channeldb/graph.go index 86cbe9aa9..d7a4480d0 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -1023,6 +1023,10 @@ func (c *ChannelGraph) AddChannelEdge(edge *models.ChannelEdgeInfo, } for _, f := range op { + if f == nil { + return fmt.Errorf("nil scheduler option was used") + } + f(r) } From 20f7c7380149f1bc61d609983ccaee53a926757b Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Tue, 29 Oct 2024 23:07:02 +0100 Subject: [PATCH 7/8] docs: update release notes --- docs/release-notes/release-notes-0.18.4.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes/release-notes-0.18.4.md b/docs/release-notes/release-notes-0.18.4.md index f6a2f3967..a4b0ed859 100644 --- a/docs/release-notes/release-notes-0.18.4.md +++ b/docs/release-notes/release-notes-0.18.4.md @@ -71,6 +71,10 @@ types](https://github.com/lightningnetwork/lnd/pull/8960). ## lncli Additions +* [`updatechanpolicy`](https://github.com/lightningnetwork/lnd/pull/8805) will + now update the channel policy if the edge was not found in the graph + database if the `create_missing_edge` flag is set. + # Improvements ## Functional Updates ## RPC Updates From 266531271b62d1cee33babb035be1e1123006f1f Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 21 Nov 2024 10:51:13 +0100 Subject: [PATCH 8/8] localchans: do error if an edge policy is missing --- routing/localchans/manager.go | 146 ++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 58 deletions(-) diff --git a/routing/localchans/manager.go b/routing/localchans/manager.go index 5b35b346b..2639928e0 100644 --- a/routing/localchans/manager.go +++ b/routing/localchans/manager.go @@ -99,8 +99,22 @@ 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. - edge, err := r.updateEdge( + err := r.updateEdge( tx, info.ChannelPoint, edge, newSchema, ) if err != nil { @@ -173,49 +187,30 @@ 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: - // 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. log.Warnf("Missing edge for active channel (%s) "+ "during policy update. Recreating edge with "+ "default policy.", channel.FundingOutpoint.String()) - 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) - - f := lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN - failedUpdates = append(failedUpdates, - makeFailureItem(chanPoint, f, - "could not update policies", - )) - } - - // Insert the edge into the database to avoid `edge not - // found` errors during policy update propagation. - err = r.AddEdge(info) - if err != nil { - log.Warnf("Attempt to add missing edge for "+ - "channel (%s) errored with: %v", - channel.FundingOutpoint.String(), err) - - f := lnrpc.UpdateFailure_UPDATE_FAILURE_UNKNOWN - failedUpdates = append(failedUpdates, - makeFailureItem(chanPoint, f, - "could not add edge", - )) - } - - err = processChan(nil, info, edge) - if err != nil { - return nil, err + 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: @@ -247,6 +242,52 @@ 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, @@ -309,25 +350,14 @@ func (r *Manager) createEdge(channel *channeldb.OpenChannel, return info, edge, nil } -// updateEdge updates the given edge with the new schema. The edge parameter may -// be nil, in that case a new channel policy is returned. In other cases the -// passed in channel policy is returned after modification. +// 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) (*models.ChannelEdgePolicy, error) { + newSchema routing.ChannelPolicy) error { channel, err := r.FetchChannel(tx, chanPoint) if err != nil { - return nil, err - } - - // If due to some unforeseen circumstances the policy doesn't exist, - // recreate it here. - if edge == nil { - _, edge, err = r.createEdge(channel, time.Now()) - if err != nil { - return nil, err - } + return err } // Update forwarding fee scheme and required time lock delta. @@ -345,7 +375,7 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint, ) }) if err != nil { - return nil, err + return err } edge.TimeLockDelta = uint16(newSchema.TimeLockDelta) @@ -353,7 +383,7 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint, // Retrieve negotiated channel htlc amt limits. amtMin, amtMax, err := r.getHtlcAmtLimits(channel) if err != nil { - return nil, err + return err } // We now update the edge max htlc value. @@ -386,19 +416,19 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint, // Validate htlc amount constraints. switch { case edge.MinHTLC < amtMin: - return nil, fmt.Errorf( + return fmt.Errorf( "min htlc amount of %v is below min htlc parameter of %v", edge.MinHTLC, amtMin, ) case edge.MaxHTLC > amtMax: - return nil, fmt.Errorf( + return fmt.Errorf( "max htlc size of %v is above max pending amount of %v", edge.MaxHTLC, amtMax, ) case edge.MinHTLC > edge.MaxHTLC: - return nil, fmt.Errorf( + return fmt.Errorf( "min_htlc %v greater than max_htlc %v", edge.MinHTLC, edge.MaxHTLC, ) @@ -407,7 +437,7 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint, // Clear signature to help prevent usage of the previous signature. edge.SetSigBytes(nil) - return edge, nil + return nil } // getHtlcAmtLimits retrieves the negotiated channel min and max htlc amount