diff --git a/lnrpc/peersrpc/peers_server.go b/lnrpc/peersrpc/peers_server.go index 5cfd7e589..9ca6f437a 100644 --- a/lnrpc/peersrpc/peers_server.go +++ b/lnrpc/peersrpc/peers_server.go @@ -10,6 +10,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" "google.golang.org/grpc" "gopkg.in/macaroon-bakery.v2/bakery" @@ -168,7 +169,7 @@ func (s *Server) UpdateNodeAnnouncement(_ context.Context, resp := &NodeAnnouncementUpdateResponse{} nodeModifiers := make([]netann.NodeAnnModifier, 0) - _, err := s.cfg.GetNodeAnnouncement() + currentNodeAnn, err := s.cfg.GetNodeAnnouncement() if err != nil { return nil, fmt.Errorf("unable to get current node "+ "announcement: %v", err) @@ -178,7 +179,24 @@ func (s *Server) UpdateNodeAnnouncement(_ context.Context, // TODO(positiveblue): apply color modifications - // TODO(positiveblue): apply alias modifications + if req.Alias != "" { + alias, err := lnwire.NewNodeAlias(req.Alias) + if err != nil { + return nil, fmt.Errorf("invalid alias value: %v", err) + } + if alias != currentNodeAnn.Alias { + resp.Ops = append(resp.Ops, &lnrpc.Op{ + Entity: "alias", + Actions: []string{ + fmt.Sprintf("changed to %v", alias), + }, + }) + nodeModifiers = append( + nodeModifiers, + netann.NodeAnnSetAlias(alias), + ) + } + } // TODO(positiveblue): apply addresses modifications diff --git a/lntest/itest/assertions.go b/lntest/itest/assertions.go index 248d3bcad..2110d6e9e 100644 --- a/lntest/itest/assertions.go +++ b/lntest/itest/assertions.go @@ -16,6 +16,7 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/peersrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntest" @@ -1856,3 +1857,33 @@ func assertAnchorOutputLost(t *harnessTest, node *lntest.HarnessNode, }, defaultTimeout) require.NoError(t.t, err, "anchor doesn't show as being lost") } + +// assertNodeAnnouncement compares that two node announcements match. +func assertNodeAnnouncement(t *harnessTest, n1, n2 *lnrpc.NodeUpdate) { + // Alias should match. + require.Equal(t.t, n1.Alias, n2.Alias, "alias don't match") +} + +// assertUpdateNodeAnnouncementResponse is a helper function to assert +// the response expected values. +func assertUpdateNodeAnnouncementResponse(t *harnessTest, + response *peersrpc.NodeAnnouncementUpdateResponse, + expectedOps map[string]int) { + + require.Equal( + t.t, len(response.Ops), len(expectedOps), + "unexpected number of Ops updating dave's node announcement", + ) + + ops := make(map[string]int, len(response.Ops)) + for _, op := range response.Ops { + ops[op.Entity] = len(op.Actions) + } + + for k, v := range expectedOps { + if v != ops[k] { + t.Fatalf("unexpected number of actions for operation "+ + "%s: got %d wanted %d", k, ops[k], v) + } + } +} diff --git a/lntest/itest/lnd_channel_graph_test.go b/lntest/itest/lnd_channel_graph_test.go index b0643ad9c..9910c62d1 100644 --- a/lntest/itest/lnd_channel_graph_test.go +++ b/lntest/itest/lnd_channel_graph_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "strings" "testing" "time" @@ -769,16 +770,80 @@ func subscribeGraphNotifications(ctxb context.Context, t *harnessTest, } } +// waitForNodeAnnUpdates monitors the nodeAnnUpdates until we get one for +// the expected node and asserts that has the expected information. +func waitForNodeAnnUpdates(graphSub graphSubscription, nodePubKey string, + expectedUpdate *lnrpc.NodeUpdate, t *harnessTest) { + + for { + select { + case graphUpdate := <-graphSub.updateChan: + for _, update := range graphUpdate.NodeUpdates { + if update.IdentityKey == nodePubKey { + assertNodeAnnouncement( + t, update, expectedUpdate, + ) + return + } + } + case err := <-graphSub.errChan: + t.Fatalf("unable to recv graph update: %v", err) + case <-time.After(defaultTimeout): + t.Fatalf("did not receive node ann update") + } + } +} + // testUpdateNodeAnnouncement ensures that the RPC endpoint validates // the requests correctly and that the new node announcement is brodcasted // with the right information after updating our node. func testUpdateNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() + // context timeout for the whole test. + ctxt, cancel := context.WithTimeout( + context.Background(), defaultTimeout, + ) + defer cancel() + + // Launch notification clients for alice, such that we can + // get notified when there are updates in the graph. + aliceSub := subscribeGraphNotifications(ctxt, t, net.Alice) + defer close(aliceSub.quit) var lndArgs []string dave := net.NewNode(t.t, "Dave", lndArgs) defer shutdownAndAssert(net, t, dave) + // Get dave default information so we can compare + // it lately with the brodcasted updates. + nodeInfoReq := &lnrpc.GetInfoRequest{} + resp, err := dave.GetInfo(ctxt, nodeInfoReq) + require.NoError(t.t, err, "unable to get dave's information") + + defaultDaveNodeAnn := &lnrpc.NodeUpdate{ + Alias: resp.Alias, + } + + // Dave must have an open channel before he can send a node + // announcement, so we open a channel with Bob. + net.ConnectNodes(t.t, net.Bob, dave) + + // Go ahead and open a channel between Bob and Dave. This + // ensures that Alice receives the node announcement from Bob as part of + // the announcement broadcast. + chanPoint := openChannelAndAssert( + t, net, net.Bob, dave, + lntest.OpenChannelParams{ + Amt: 1000000, + }, + ) + require.NoError(t.t, err, "unexpected error opening a channel") + + // Wait for Alice to receive dave's node announcement with the default + // values. + waitForNodeAnnUpdates( + aliceSub, dave.PubKeyStr, defaultDaveNodeAnn, t, + ) + // We cannot differentiate between requests with Alias = "" and requests // that do not provide that field. If a user sets Alias = "" in the request // the field will simply be ignored. The request must fail because no @@ -786,9 +851,46 @@ func testUpdateNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { invalidNodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{ Alias: "", } - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - _, err := dave.UpdateNodeAnnouncement(ctxt, invalidNodeAnnReq) + _, err = dave.UpdateNodeAnnouncement(ctxt, invalidNodeAnnReq) require.Error(t.t, err, "requests without modifiers should field") + + // Alias too long. + invalidNodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{ + Alias: strings.Repeat("a", 50), + } + + _, err = dave.UpdateNodeAnnouncement(ctxt, invalidNodeAnnReq) + require.Error(t.t, err, "failed to validate an invalid alias for an "+ + "update node announcement request") + + // Update Node. + newAlias := "new-alias" + + nodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{ + Alias: newAlias, + } + + response, err := dave.UpdateNodeAnnouncement(ctxt, nodeAnnReq) + require.NoError(t.t, err, "unable to update dave's node announcement") + + expectedOps := map[string]int{ + "alias": 1, + } + assertUpdateNodeAnnouncementResponse(t, response, expectedOps) + + // After updating the node we expect the update to contain + // the requested color, requested alias and the new added addresses. + newDaveNodeAnn := &lnrpc.NodeUpdate{ + Alias: newAlias, + } + + // We'll then wait for Alice to receive dave's node announcement + // with the new values. + waitForNodeAnnUpdates( + aliceSub, dave.PubKeyStr, newDaveNodeAnn, t, + ) + + // 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 d73b6692f..df13ca1b6 100644 --- a/netann/node_announcement.go +++ b/netann/node_announcement.go @@ -13,6 +13,14 @@ import ( // lnwire.NodeAnnouncement. type NodeAnnModifier func(*lnwire.NodeAnnouncement) +// NodeAnnSetAlias is a functional option that sets the alias of the +// given node announcment. +func NodeAnnSetAlias(alias lnwire.NodeAlias) func(*lnwire.NodeAnnouncement) { + return func(nodeAnn *lnwire.NodeAnnouncement) { + nodeAnn.Alias = alias + } +} + // NodeAnnSetAddrs is a functional option that allows updating the addresses of // the given node announcement. func NodeAnnSetAddrs(addrs []net.Addr) func(*lnwire.NodeAnnouncement) {