From ae2aa5671f61b2642862b18353a84f41c624ea0a Mon Sep 17 00:00:00 2001 From: positiveblue Date: Sun, 2 Jan 2022 15:56:04 -0800 Subject: [PATCH] lnrpc/peers: handle feature bit changes in updateNodeAnnouncement --- feature/manager.go | 5 ++ lnrpc/peersrpc/peers_server.go | 79 +++++++++++++++++++++++++- lntest/itest/lnd_channel_graph_test.go | 75 ++++++++++++++++++++++++ netann/node_announcement.go | 8 +++ server.go | 5 ++ 5 files changed, 171 insertions(+), 1 deletion(-) diff --git a/feature/manager.go b/feature/manager.go index 87c85f04a..f69d2c49f 100644 --- a/feature/manager.go +++ b/feature/manager.go @@ -141,6 +141,11 @@ func (m *Manager) GetRaw(set Set) *lnwire.RawFeatureVector { return lnwire.NewRawFeatureVector() } +// SetRaw sets a new raw feature vector for the given set. +func (m *Manager) SetRaw(set Set, raw *lnwire.RawFeatureVector) { + m.fsets[set] = raw +} + // Get returns a feature vector for the passed set. If no set is known, an empty // feature vector is returned. func (m *Manager) Get(set Set) *lnwire.FeatureVector { diff --git a/lnrpc/peersrpc/peers_server.go b/lnrpc/peersrpc/peers_server.go index 16cc20af6..84516f7dc 100644 --- a/lnrpc/peersrpc/peers_server.go +++ b/lnrpc/peersrpc/peers_server.go @@ -10,6 +10,7 @@ import ( "sync/atomic" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/lightningnetwork/lnd/feature" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" @@ -241,6 +242,68 @@ func (s *Server) updateAddresses(currentAddresses []net.Addr, return newAddrs, ops, nil } +// updateFeatures computes the new raw SetNodeAnn after executing the update +// actions. +func (s *Server) updateFeatures(currentfeatures *lnwire.RawFeatureVector, + updates []*UpdateFeatureAction) (*lnwire.RawFeatureVector, + *lnrpc.Op, error) { + + ops := &lnrpc.Op{Entity: "features"} + raw := currentfeatures.Clone() + + for _, update := range updates { + bit := lnwire.FeatureBit(update.FeatureBit) + + switch update.Action { + case UpdateAction_ADD: + if raw.IsSet(bit) { + return nil, nil, fmt.Errorf( + "invalid add action for bit %v, "+ + "bit is already set", + update.FeatureBit, + ) + } + raw.Set(bit) + ops.Actions = append( + ops.Actions, + fmt.Sprintf("%s set", lnwire.Features[bit]), + ) + + case UpdateAction_REMOVE: + if !raw.IsSet(bit) { + return nil, nil, fmt.Errorf( + "invalid remove action for bit %v, "+ + "bit is already unset", + update.FeatureBit, + ) + } + raw.Unset(bit) + ops.Actions = append( + ops.Actions, + fmt.Sprintf("%s unset", lnwire.Features[bit]), + ) + + default: + return nil, nil, fmt.Errorf( + "invalid update action (%v) for bit %v", + update.Action, + update.FeatureBit, + ) + } + } + + // Validate our new SetNodeAnn. + fv := lnwire.NewFeatureVector(raw, lnwire.Features) + if err := feature.ValidateDeps(fv); err != nil { + return nil, nil, fmt.Errorf( + "invalid feature set (SetNodeAnn): %v", + err, + ) + } + + return raw, ops, nil +} + // UpdateNodeAnnouncement allows the caller to update the node parameters // and broadcasts a new version of the node announcement to its peers. func (s *Server) UpdateNodeAnnouncement(_ context.Context, @@ -256,7 +319,21 @@ func (s *Server) UpdateNodeAnnouncement(_ context.Context, "announcement: %v", err) } - // TODO(positiveblue): apply feature bit modifications + if len(req.FeatureUpdates) > 0 { + features, ops, err := s.updateFeatures( + currentNodeAnn.Features, + req.FeatureUpdates, + ) + if err != nil { + return nil, fmt.Errorf("error trying to update node "+ + "features: %w", err) + } + resp.Ops = append(resp.Ops, ops) + nodeModifiers = append( + nodeModifiers, + netann.NodeAnnSetFeatures(features), + ) + } if req.Color != "" { color, err := lncfg.ParseHexColor(req.Color) diff --git a/lntest/itest/lnd_channel_graph_test.go b/lntest/itest/lnd_channel_graph_test.go index 212ae00f1..c001c5f0c 100644 --- a/lntest/itest/lnd_channel_graph_test.go +++ b/lntest/itest/lnd_channel_graph_test.go @@ -841,6 +841,15 @@ func testUpdateNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { ) } + // This feature bit is used to test that our endpoint sets/unsets + // feature bits properly. If the current FeatureBit is set by default + // update this one for another one unset by default at random. + featureBit := lnrpc.FeatureBit_WUMBO_CHANNELS_REQ + featureIdx := uint32(featureBit) + if _, ok := resp.Features[featureIdx]; ok { + t.Fatalf("unexpected feature bit enabled by default") + } + defaultDaveNodeAnn := &lnrpc.NodeUpdate{ Alias: resp.Alias, Color: resp.Color, @@ -912,16 +921,25 @@ func testUpdateNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { }, } + updateFeatureActions := []*peersrpc.UpdateFeatureAction{ + { + Action: peersrpc.UpdateAction_ADD, + FeatureBit: featureBit, + }, + } + nodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{ Alias: newAlias, Color: newColor, AddressUpdates: updateAddressActions, + FeatureUpdates: updateFeatureActions, } response, err := dave.UpdateNodeAnnouncement(ctxt, nodeAnnReq) require.NoError(t.t, err, "unable to update dave's node announcement") expectedOps := map[string]int{ + "features": 1, "color": 1, "alias": 1, "addresses": 3, @@ -951,6 +969,63 @@ func testUpdateNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { aliceSub, dave.PubKeyStr, newDaveNodeAnn, t, ) + // Check that the feature bit was set correctly. + resp, err = dave.GetInfo(ctxt, nodeInfoReq) + require.NoError(t.t, err, "unable to get dave's information") + + if _, ok := resp.Features[featureIdx]; !ok { + t.Fatalf("failed to set feature bit") + } + + // Check that we cannot set a feature bit that is already set. + nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{ + FeatureUpdates: updateFeatureActions, + } + + _, err = dave.UpdateNodeAnnouncement(ctxt, nodeAnnReq) + require.Error( + t.t, err, "missing expected error: cannot set a feature bit "+ + "that is already set", + ) + + // Check that we can unset feature bits. + updateFeatureActions = []*peersrpc.UpdateFeatureAction{ + { + Action: peersrpc.UpdateAction_REMOVE, + FeatureBit: featureBit, + }, + } + + nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{ + FeatureUpdates: updateFeatureActions, + } + + response, err = dave.UpdateNodeAnnouncement(ctxt, nodeAnnReq) + require.NoError(t.t, err, "unable to update dave's node announcement") + + expectedOps = map[string]int{ + "features": 1, + } + assertUpdateNodeAnnouncementResponse(t, response, expectedOps) + + resp, err = dave.GetInfo(ctxt, nodeInfoReq) + require.NoError(t.t, err, "unable to get dave's information") + + if _, ok := resp.Features[featureIdx]; ok { + t.Fatalf("failed to unset feature bit") + } + + // Check that we cannot unset a feature bit that is already unset. + nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{ + FeatureUpdates: updateFeatureActions, + } + + _, err = dave.UpdateNodeAnnouncement(ctxt, nodeAnnReq) + require.Error( + t.t, err, "missing expected error: cannot unset a feature bit "+ + "that is already unset", + ) + // Close the channel between Bob and Dave. closeChannelAndAssert(t, net, net.Bob, chanPoint, false) } diff --git a/netann/node_announcement.go b/netann/node_announcement.go index e0fbb9df3..42296bf22 100644 --- a/netann/node_announcement.go +++ b/netann/node_announcement.go @@ -38,6 +38,14 @@ func NodeAnnSetColor(newColor color.RGBA) func(*lnwire.NodeAnnouncement) { } } +// NodeAnnSetFeatures is a functional option that allows updating the features of +// the given node announcement. +func NodeAnnSetFeatures(features *lnwire.RawFeatureVector) func(*lnwire.NodeAnnouncement) { + return func(nodeAnn *lnwire.NodeAnnouncement) { + nodeAnn.Features = features + } +} + // NodeAnnSetTimestamp is a functional option that sets the timestamp of the // announcement to the current time, or increments it if the timestamp is // already in the future. diff --git a/server.go b/server.go index ad08fe4b0..c4110bcc1 100644 --- a/server.go +++ b/server.go @@ -2771,6 +2771,11 @@ func (s *server) updateAndBrodcastSelfNode( return fmt.Errorf("can't set self node: %v", err) } + // Update the feature bits for the SetNodeAnn in case they changed. + s.featureMgr.SetRaw( + feature.SetNodeAnn, selfNode.Features.RawFeatureVector, + ) + // Finally, propagate it to the nodes in the network. err = s.BroadcastMessage(nil, &newNodeAnn) if err != nil {