From aed0c2a90e6097a6fabec9af31a89de8f4b83b25 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 17 Apr 2019 13:25:56 -0700 Subject: [PATCH] discovery: support optional message fields when processing announcements In this commit, we extend the gossiper with support for external callers to provide optional fields that can serve as useful when processing a specific network announcement. This will serve useful for light clients, which are unable to obtain the channel point and capacity for a given channel, but can provide them manually for their own set of channels. --- discovery/gossiper.go | 69 +++++++++++++++++++++++++++++++----- discovery/gossiper_test.go | 71 +++++++++++++++++++++++++++++++++----- 2 files changed, 124 insertions(+), 16 deletions(-) diff --git a/discovery/gossiper.go b/discovery/gossiper.go index aad48eb85..ca1e46859 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -34,12 +35,49 @@ var ( ErrGossipSyncerNotFound = errors.New("gossip syncer not found") ) +// optionalMsgFields is a set of optional message fields that external callers +// can provide that serve useful when processing a specific network +// announcement. +type optionalMsgFields struct { + capacity *btcutil.Amount + channelPoint *wire.OutPoint +} + +// apply applies the optional fields within the functional options. +func (f *optionalMsgFields) apply(optionalMsgFields ...OptionalMsgField) { + for _, optionalMsgField := range optionalMsgFields { + optionalMsgField(f) + } +} + +// OptionalMsgField is a functional option parameter that can be used to provide +// external information that is not included within a network message but serves +// useful when processing it. +type OptionalMsgField func(*optionalMsgFields) + +// ChannelCapacity is an optional field that lets the gossiper know of the +// capacity of a channel. +func ChannelCapacity(capacity btcutil.Amount) OptionalMsgField { + return func(f *optionalMsgFields) { + f.capacity = &capacity + } +} + +// ChannelPoint is an optional field that lets the gossiper know of the outpoint +// of a channel. +func ChannelPoint(op wire.OutPoint) OptionalMsgField { + return func(f *optionalMsgFields) { + f.channelPoint = &op + } +} + // networkMsg couples a routing related wire message with the peer that // originally sent it. type networkMsg struct { - peer lnpeer.Peer - source *btcec.PublicKey - msg lnwire.Message + peer lnpeer.Peer + source *btcec.PublicKey + msg lnwire.Message + optionalMsgFields *optionalMsgFields isRemote bool @@ -572,13 +610,17 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message, // entire channel announcement and update messages will be re-constructed and // broadcast to the rest of the network. func (d *AuthenticatedGossiper) ProcessLocalAnnouncement(msg lnwire.Message, - source *btcec.PublicKey) chan error { + source *btcec.PublicKey, optionalFields ...OptionalMsgField) chan error { + + optionalMsgFields := &optionalMsgFields{} + optionalMsgFields.apply(optionalFields...) nMsg := &networkMsg{ - msg: msg, - isRemote: false, - source: source, - err: make(chan error, 1), + msg: msg, + optionalMsgFields: optionalMsgFields, + isRemote: false, + source: source, + err: make(chan error, 1), } select { @@ -1605,6 +1647,17 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement( ExtraOpaqueData: msg.ExtraOpaqueData, } + // If there were any optional message fields provided, we'll + // include them in its serialized disk representation now. + if nMsg.optionalMsgFields != nil { + if nMsg.optionalMsgFields.capacity != nil { + edge.Capacity = *nMsg.optionalMsgFields.capacity + } + if nMsg.optionalMsgFields.channelPoint != nil { + edge.ChannelPoint = *nMsg.optionalMsgFields.channelPoint + } + } + // We will add the edge to the channel router. If the nodes // present in this channel are not present in the database, a // partial node will be added to represent each node while we diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index 02c26f4d0..51d1aad47 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -146,9 +146,6 @@ func (r *mockGraphSource) AddEdge(info *channeldb.ChannelEdgeInfo) error { return errors.New("info already exist") } - // Usually, the capacity is fetched in the router from the funding txout. - // Since the mockGraphSource can't access the txout, assign a default value. - info.Capacity = maxBtcFundingAmount r.infos[info.ChannelID] = *info return nil } @@ -3267,18 +3264,21 @@ func TestSendChannelUpdateReliably(t *testing.T) { } func sendLocalMsg(t *testing.T, ctx *testCtx, msg lnwire.Message, - localPub *btcec.PublicKey) { + localPub *btcec.PublicKey, optionalMsgFields ...OptionalMsgField) { t.Helper() + var err error select { - case err := <-ctx.gossiper.ProcessLocalAnnouncement(msg, localPub): - if err != nil { - t.Fatalf("unable to process channel msg: %v", err) - } + case err = <-ctx.gossiper.ProcessLocalAnnouncement( + msg, localPub, optionalMsgFields..., + ): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } + if err != nil { + t.Fatalf("unable to process channel msg: %v", err) + } } func sendRemoteMsg(t *testing.T, ctx *testCtx, msg lnwire.Message, @@ -3482,6 +3482,61 @@ out: } } +// TestProcessChannelAnnouncementOptionalMsgFields ensures that the gossiper can +// properly handled optional message fields provided by the caller when +// processing a channel announcement. +func TestProcessChannelAnnouncementOptionalMsgFields(t *testing.T) { + t.Parallel() + + // We'll start by creating our test context and a set of test channel + // announcements. + ctx, cleanup, err := createTestCtx(0) + if err != nil { + t.Fatalf("unable to create test context: %v", err) + } + defer cleanup() + + chanAnn1 := createAnnouncementWithoutProof(100) + chanAnn2 := createAnnouncementWithoutProof(101) + localKey := nodeKeyPriv1.PubKey() + + // assertOptionalMsgFields is a helper closure that ensures the optional + // message fields were set as intended. + assertOptionalMsgFields := func(chanID lnwire.ShortChannelID, + capacity btcutil.Amount, channelPoint wire.OutPoint) { + + t.Helper() + + edge, _, _, err := ctx.router.GetChannelByID(chanID) + if err != nil { + t.Fatalf("unable to get channel by id: %v", err) + } + if edge.Capacity != capacity { + t.Fatalf("expected capacity %v, got %v", capacity, + edge.Capacity) + } + if edge.ChannelPoint != channelPoint { + t.Fatalf("expected channel point %v, got %v", + channelPoint, edge.ChannelPoint) + } + } + + // We'll process the first announcement without any optional fields. We + // should see the channel's capacity and outpoint have a zero value. + sendLocalMsg(t, ctx, chanAnn1, localKey) + assertOptionalMsgFields(chanAnn1.ShortChannelID, 0, wire.OutPoint{}) + + // Providing the capacity and channel point as optional fields should + // propagate them all the way down to the router. + capacity := btcutil.Amount(1000) + channelPoint := wire.OutPoint{Index: 1} + sendLocalMsg( + t, ctx, chanAnn2, localKey, ChannelCapacity(capacity), + ChannelPoint(channelPoint), + ) + assertOptionalMsgFields(chanAnn2.ShortChannelID, capacity, channelPoint) +} + func assertMessage(t *testing.T, expected, got lnwire.Message) { t.Helper()