diff --git a/chan_series.go b/chan_series.go index 27a1c3e6..83b05b02 100644 --- a/chan_series.go +++ b/chan_series.go @@ -89,7 +89,7 @@ func (c *chanSeries) UpdatesInHorizon(chain chainhash.Hash, return nil, err } for _, nodeAnn := range nodeAnnsInHorizon { - nodeUpdate, err := makeNodeAnn(&nodeAnn) + nodeUpdate, err := nodeAnn.NodeAnnouncement(true) if err != nil { return nil, err } @@ -151,25 +151,6 @@ func (c *chanSeries) FilterChannelRange(chain chainhash.Hash, return chanResp, nil } -func makeNodeAnn(n *channeldb.LightningNode) (*lnwire.NodeAnnouncement, error) { - alias, _ := lnwire.NewNodeAlias(n.Alias) - - wireSig, err := lnwire.NewSigFromRawSignature(n.AuthSigBytes) - if err != nil { - return nil, err - } - return &lnwire.NodeAnnouncement{ - Signature: wireSig, - Timestamp: uint32(n.LastUpdate.Unix()), - Addresses: n.Addresses, - NodeID: n.PubKeyBytes, - Features: n.Features.RawFeatureVector, - RGBColor: n.Color, - Alias: alias, - ExtraOpaqueData: n.ExtraOpaqueData, - }, nil -} - // FetchChanAnns returns a full set of channel announcements as well as their // updates that match the set of specified short channel ID's. We'll use this // to reply to a QueryShortChanIDs message sent by a remote peer. The response @@ -221,7 +202,7 @@ func (c *chanSeries) FetchChanAnns(chain chainhash.Hash, nodePub := channel.Policy1.Node.PubKeyBytes hasNodeAnn := channel.Policy1.Node.HaveNodeAnnouncement if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn { - nodeAnn, err := makeNodeAnn(channel.Policy1.Node) + nodeAnn, err := channel.Policy1.Node.NodeAnnouncement(true) if err != nil { return nil, err } @@ -238,7 +219,7 @@ func (c *chanSeries) FetchChanAnns(chain chainhash.Hash, nodePub := channel.Policy2.Node.PubKeyBytes hasNodeAnn := channel.Policy2.Node.HaveNodeAnnouncement if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn { - nodeAnn, err := makeNodeAnn(channel.Policy2.Node) + nodeAnn, err := channel.Policy2.Node.NodeAnnouncement(true) if err != nil { return nil, err } diff --git a/channeldb/graph.go b/channeldb/graph.go index 14968e46..eb681395 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -1767,6 +1767,43 @@ func (l *LightningNode) AddPubKey(key *btcec.PublicKey) { copy(l.PubKeyBytes[:], key.SerializeCompressed()) } +// NodeAnnouncement retrieves the latest node announcement of the node. +func (l *LightningNode) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement, + error) { + + if !l.HaveNodeAnnouncement { + return nil, fmt.Errorf("node does not have node announcement") + } + + alias, err := lnwire.NewNodeAlias(l.Alias) + if err != nil { + return nil, err + } + + nodeAnn := &lnwire.NodeAnnouncement{ + Features: l.Features.RawFeatureVector, + NodeID: l.PubKeyBytes, + RGBColor: l.Color, + Alias: alias, + Addresses: l.Addresses, + Timestamp: uint32(l.LastUpdate.Unix()), + ExtraOpaqueData: l.ExtraOpaqueData, + } + + if !signed { + return nodeAnn, nil + } + + sig, err := lnwire.NewSigFromRawSignature(l.AuthSigBytes) + if err != nil { + return nil, err + } + + nodeAnn.Signature = sig + + return nodeAnn, nil +} + // FetchLightningNode attempts to look up a target node by its identity public // key. If the node isn't found in the database, then ErrGraphNodeNotFound is // returned. diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 783fd756..641f4c9e 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -270,27 +270,6 @@ func (d *AuthenticatedGossiper) SynchronizeNode(syncPeer lnpeer.Peer) error { // containing all the messages to be sent to the target peer. var announceMessages []lnwire.Message - makeNodeAnn := func(n *channeldb.LightningNode) ( - *lnwire.NodeAnnouncement, error) { - - alias, _ := lnwire.NewNodeAlias(n.Alias) - - wireSig, err := lnwire.NewSigFromRawSignature(n.AuthSigBytes) - if err != nil { - return nil, err - } - return &lnwire.NodeAnnouncement{ - Signature: wireSig, - Timestamp: uint32(n.LastUpdate.Unix()), - Addresses: n.Addresses, - NodeID: n.PubKeyBytes, - Features: n.Features.RawFeatureVector, - RGBColor: n.Color, - Alias: alias, - ExtraOpaqueData: n.ExtraOpaqueData, - }, nil - } - // We'll use this map to ensure we don't send the same node // announcement more than one time as one node may have many channel // anns we'll need to send. @@ -330,7 +309,7 @@ func (d *AuthenticatedGossiper) SynchronizeNode(syncPeer lnpeer.Peer) error { nodePub := e1.Node.PubKeyBytes hasNodeAnn := e1.Node.HaveNodeAnnouncement if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn { - nodeAnn, err := makeNodeAnn(e1.Node) + nodeAnn, err := e1.Node.NodeAnnouncement(true) if err != nil { return err } @@ -352,7 +331,7 @@ func (d *AuthenticatedGossiper) SynchronizeNode(syncPeer lnpeer.Peer) error { nodePub := e2.Node.PubKeyBytes hasNodeAnn := e2.Node.HaveNodeAnnouncement if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn { - nodeAnn, err := makeNodeAnn(e2.Node) + nodeAnn, err := e2.Node.NodeAnnouncement(true) if err != nil { return err } diff --git a/lnd_test.go b/lnd_test.go index 57fc0f34..9a72e994 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -7858,6 +7858,8 @@ func testNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { advertisedAddrs := []string{ "192.168.1.1:8333", "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8337", + "bkb6azqggsaiskzi.onion:9735", + "fomvuglh6h6vcag73xo5t5gv56ombih3zr2xvplkpbfd7wrog4swjwid.onion:1234", } var lndArgs []string diff --git a/lnwallet/fee_estimator.go b/lnwallet/fee_estimator.go index 24adee93..1f63a19f 100644 --- a/lnwallet/fee_estimator.go +++ b/lnwallet/fee_estimator.go @@ -229,7 +229,8 @@ func (b *BtcdFeeEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, erro // Finally, we'll enforce our fee floor. if satPerKw < b.minFeePerKW { walletLog.Debugf("Estimated fee rate of %v sat/kw is too low, "+ - "using fee floor of %v sat/kw instead", b.minFeePerKW) + "using fee floor of %v sat/kw instead", satPerKw, + b.minFeePerKW) satPerKw = b.minFeePerKW } diff --git a/server.go b/server.go index 5385b245..4a885b7d 100644 --- a/server.go +++ b/server.go @@ -440,14 +440,19 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, chanGraph := chanDB.ChannelGraph() - // Parse node color from configuration. + // We'll now reconstruct a node announcement based on our current + // configuration so we can send it out as a sort of heart beat within + // the network. + // + // We'll start by parsing the node color from configuration. color, err := parseHexColor(cfg.Color) if err != nil { srvrLog.Errorf("unable to parse color: %v\n", err) return nil, err } - // If no alias is provided, default to first 10 characters of public key + // If no alias is provided, default to first 10 characters of public + // key. alias := cfg.Alias if alias == "" { alias = hex.EncodeToString(serializedPubKey[:10]) @@ -466,19 +471,16 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, } copy(selfNode.PubKeyBytes[:], privKey.PubKey().SerializeCompressed()) - // If our information has changed since our last boot, then we'll - // re-sign our node announcement so a fresh authenticated version of it - // can be propagated throughout the network upon startup. - // - // TODO(roasbeef): don't always set timestamp above to _now. - nodeAnn := &lnwire.NodeAnnouncement{ - Timestamp: uint32(selfNode.LastUpdate.Unix()), - Addresses: selfNode.Addresses, - NodeID: selfNode.PubKeyBytes, - Alias: nodeAlias, - Features: selfNode.Features.RawFeatureVector, - RGBColor: color, + // Based on the disk representation of the node announcement generated + // above, we'll generate a node announcement that can go out on the + // network so we can properly sign it. + nodeAnn, err := selfNode.NodeAnnouncement(false) + if err != nil { + return nil, fmt.Errorf("unable to gen self node ann: %v", err) } + + // With the announcement generated, we'll sign it to properly + // authenticate the message on the network. authSig, err := discovery.SignAnnouncement( s.nodeSigner, s.identityPriv.PubKey(), nodeAnn, ) @@ -486,18 +488,21 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, return nil, fmt.Errorf("unable to generate signature for "+ "self node announcement: %v", err) } - selfNode.AuthSigBytes = authSig.Serialize() - s.currentNodeAnn = nodeAnn - - if err := chanGraph.SetSourceNode(selfNode); err != nil { - return nil, fmt.Errorf("can't set self node: %v", err) - } - - nodeAnn.Signature, err = lnwire.NewSigFromRawSignature(selfNode.AuthSigBytes) + nodeAnn.Signature, err = lnwire.NewSigFromRawSignature( + selfNode.AuthSigBytes, + ) if err != nil { return nil, err } + + // Finally, we'll update the representation on disk, and update our + // cached in-memory version as well. + if err := chanGraph.SetSourceNode(selfNode); err != nil { + return nil, fmt.Errorf("can't set self node: %v", err) + } + s.currentNodeAnn = nodeAnn + s.chanRouter, err = routing.New(routing.Config{ Graph: chanGraph, Chain: cc.chainIO, @@ -1512,7 +1517,32 @@ func (s *server) initTorController() error { // Now that the onion service has been created, we'll add the onion // address it can be reached at to our list of advertised addresses. - s.currentNodeAnn.Addresses = append(s.currentNodeAnn.Addresses, addr) + newNodeAnn, err := s.genNodeAnnouncement( + true, func(currentAnn *lnwire.NodeAnnouncement) { + currentAnn.Addresses = append(currentAnn.Addresses, addr) + }, + ) + if err != nil { + return fmt.Errorf("Unable to generate new node "+ + "announcement: %v", err) + } + + // Finally, we'll update the on-disk version of our announcement so it + // will eventually propagate to nodes in the network. + selfNode := &channeldb.LightningNode{ + HaveNodeAnnouncement: true, + LastUpdate: time.Unix(int64(newNodeAnn.Timestamp), 0), + Addresses: newNodeAnn.Addresses, + Alias: newNodeAnn.Alias.String(), + Features: lnwire.NewFeatureVector( + newNodeAnn.Features, lnwire.GlobalFeatures, + ), + Color: newNodeAnn.RGBColor, + AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(), + } + if err := s.chanDB.ChannelGraph().SetSourceNode(selfNode); err != nil { + return fmt.Errorf("can't set self node: %v", err) + } return nil } @@ -1526,27 +1556,36 @@ func (s *server) genNodeAnnouncement(refresh bool, s.mu.Lock() defer s.mu.Unlock() + // If we don't need to refresh the announcement, then we can return a + // copy of our cached version. if !refresh { return *s.currentNodeAnn, nil } + // Now that we know we need to update our copy, we'll apply all the + // function updates that'll mutate the current version of our node + // announcement. for _, update := range updates { update(s.currentNodeAnn) } + // We'll now update the timestamp, ensuring that with each update, the + // timestamp monotonically increases. newStamp := uint32(time.Now().Unix()) if newStamp <= s.currentNodeAnn.Timestamp { newStamp = s.currentNodeAnn.Timestamp + 1 } - s.currentNodeAnn.Timestamp = newStamp + + // Now that the announcement is fully updated, we'll generate a new + // signature over the announcement to ensure nodes on the network + // accepted the new authenticated announcement. sig, err := discovery.SignAnnouncement( s.nodeSigner, s.identityPriv.PubKey(), s.currentNodeAnn, ) if err != nil { return lnwire.NodeAnnouncement{}, err } - s.currentNodeAnn.Signature, err = lnwire.NewSigFromSignature(sig) if err != nil { return lnwire.NodeAnnouncement{}, err