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.
This commit is contained in:
Wilmer Paulino 2019-04-17 13:25:56 -07:00
parent 5d3621cc83
commit aed0c2a90e
No known key found for this signature in database
GPG Key ID: 6DF57B9F9514972F
2 changed files with 124 additions and 16 deletions

@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
@ -34,12 +35,49 @@ var (
ErrGossipSyncerNotFound = errors.New("gossip syncer not found") 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 // networkMsg couples a routing related wire message with the peer that
// originally sent it. // originally sent it.
type networkMsg struct { type networkMsg struct {
peer lnpeer.Peer peer lnpeer.Peer
source *btcec.PublicKey source *btcec.PublicKey
msg lnwire.Message msg lnwire.Message
optionalMsgFields *optionalMsgFields
isRemote bool isRemote bool
@ -572,13 +610,17 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message,
// entire channel announcement and update messages will be re-constructed and // entire channel announcement and update messages will be re-constructed and
// broadcast to the rest of the network. // broadcast to the rest of the network.
func (d *AuthenticatedGossiper) ProcessLocalAnnouncement(msg lnwire.Message, 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{ nMsg := &networkMsg{
msg: msg, msg: msg,
isRemote: false, optionalMsgFields: optionalMsgFields,
source: source, isRemote: false,
err: make(chan error, 1), source: source,
err: make(chan error, 1),
} }
select { select {
@ -1605,6 +1647,17 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
ExtraOpaqueData: msg.ExtraOpaqueData, 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 // We will add the edge to the channel router. If the nodes
// present in this channel are not present in the database, a // present in this channel are not present in the database, a
// partial node will be added to represent each node while we // partial node will be added to represent each node while we

@ -146,9 +146,6 @@ func (r *mockGraphSource) AddEdge(info *channeldb.ChannelEdgeInfo) error {
return errors.New("info already exist") 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 r.infos[info.ChannelID] = *info
return nil return nil
} }
@ -3267,18 +3264,21 @@ func TestSendChannelUpdateReliably(t *testing.T) {
} }
func sendLocalMsg(t *testing.T, ctx *testCtx, msg lnwire.Message, func sendLocalMsg(t *testing.T, ctx *testCtx, msg lnwire.Message,
localPub *btcec.PublicKey) { localPub *btcec.PublicKey, optionalMsgFields ...OptionalMsgField) {
t.Helper() t.Helper()
var err error
select { select {
case err := <-ctx.gossiper.ProcessLocalAnnouncement(msg, localPub): case err = <-ctx.gossiper.ProcessLocalAnnouncement(
if err != nil { msg, localPub, optionalMsgFields...,
t.Fatalf("unable to process channel msg: %v", err) ):
}
case <-time.After(2 * time.Second): case <-time.After(2 * time.Second):
t.Fatal("did not process local announcement") 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, 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) { func assertMessage(t *testing.T, expected, got lnwire.Message) {
t.Helper() t.Helper()