Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

channeldb+discovery: ensure we store, validate and propagate announcements with opaque data #1825

Merged
merged 10 commits into from
Sep 6, 2018
Merged
17 changes: 10 additions & 7 deletions chan_series.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,14 @@ func makeNodeAnn(n *channeldb.LightningNode) (*lnwire.NodeAnnouncement, error) {
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,
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
}

Expand Down Expand Up @@ -277,6 +278,7 @@ func (c *chanSeries) FetchChanUpdates(chain chainhash.Hash,
HtlcMinimumMsat: e1.MinHTLC,
BaseFee: uint32(e1.FeeBaseMSat),
FeeRate: uint32(e1.FeeProportionalMillionths),
ExtraOpaqueData: e1.ExtraOpaqueData,
}
chanUpdate.Signature, err = lnwire.NewSigFromRawSignature(e1.SigBytes)
if err != nil {
Expand All @@ -295,6 +297,7 @@ func (c *chanSeries) FetchChanUpdates(chain chainhash.Hash,
HtlcMinimumMsat: e2.MinHTLC,
BaseFee: uint32(e2.FeeBaseMSat),
FeeRate: uint32(e2.FeeProportionalMillionths),
ExtraOpaqueData: e1.ExtraOpaqueData,
}
chanUpdate.Signature, err = lnwire.NewSigFromRawSignature(e2.SigBytes)
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions channeldb/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,13 @@ var (
// to the log not having any recorded events.
ErrNoForwardingEvents = fmt.Errorf("no recorded forwarding events")
)

// ErrTooManyExtraOpaqueBytes creates an error which should be returned if the
// caller attempts to write an announcement message which bares too many extra
// opaque bytes. We limit this value in order to ensure that we don't waste
// disk space due to nodes unnecessarily padding out their announcements with
// garbage data.
func ErrTooManyExtraOpaqueBytes(numBytes int) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

return fmt.Errorf("max allowed number of opaque bytes is %v, received "+
"%v bytes", MaxAllowedExtraOpaqueBytes, numBytes)
}
91 changes: 91 additions & 0 deletions channeldb/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ var (
nodeBloomKey = []byte("node-bloom")
)

const (
// MaxAllowedExtraOpaqueBytes is the largest amount of opaque bytes that
// we'll permit to be written to disk. We limit this as otherwise, it
// would be possible for a node to create a ton of updates and slowly
// fill our disk, and also waste bandwidth due to relaying.
MaxAllowedExtraOpaqueBytes = 10000
)

// ChannelGraph is a persistent, on-disk graph representation of the Lightning
// Network. This struct can be used to implement path finding algorithms on top
// of, and also to update a node's view based on information received from the
Expand Down Expand Up @@ -1694,6 +1702,14 @@ type LightningNode struct {
// Features is the list of protocol features supported by this node.
Features *lnwire.FeatureVector

// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte

db *DB

// TODO(roasbeef): discovery will need storage to keep it's last IP
Expand Down Expand Up @@ -1978,6 +1994,14 @@ type ChannelEdgeInfo struct {
// the value output in the outpoint that created this channel.
Capacity btcutil.Amount

// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte

db *DB
}

Expand Down Expand Up @@ -2322,6 +2346,14 @@ type ChannelEdgePolicy struct {
// this pointer the channel graph can further be traversed.
Node *LightningNode

// ExtraOpaqueData is the set of data that was appended to this
// message, some of which we may not actually know how to iterate or
// parse. By holding onto this data, we ensure that we're able to
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte

db *DB
}

Expand Down Expand Up @@ -2691,6 +2723,14 @@ func putLightningNode(nodeBucket *bolt.Bucket, aliasBucket *bolt.Bucket,
return err
}

if len(node.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes {
return ErrTooManyExtraOpaqueBytes(len(node.ExtraOpaqueData))
}
err = wire.WriteVarBytes(&b, 0, node.ExtraOpaqueData)
if err != nil {
return err
}

if err := aliasBucket.Put(nodePub, []byte(node.Alias)); err != nil {
return err
}
Expand Down Expand Up @@ -2815,6 +2855,18 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) {
return LightningNode{}, err
}

// We'll try and see if there are any opaque bytes left, if not, then
// we'll ignore the EOF error and return the node as is.
node.ExtraOpaqueData, err = wire.ReadVarBytes(
r, 0, MaxAllowedExtraOpaqueBytes, "blob",
)
switch {
case err == io.ErrUnexpectedEOF:
case err == io.EOF:
case err != nil:
return LightningNode{}, err
}

return node, nil
}

Expand Down Expand Up @@ -2873,6 +2925,14 @@ func putChanEdgeInfo(edgeIndex *bolt.Bucket, edgeInfo *ChannelEdgeInfo, chanID [
return err
}

if len(edgeInfo.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes {
return ErrTooManyExtraOpaqueBytes(len(edgeInfo.ExtraOpaqueData))
}
err := wire.WriteVarBytes(&b, 0, edgeInfo.ExtraOpaqueData)
if err != nil {
return err
}

return edgeIndex.Put(chanID[:], b.Bytes())
}

Expand Down Expand Up @@ -2950,6 +3010,18 @@ func deserializeChanEdgeInfo(r io.Reader) (ChannelEdgeInfo, error) {
return ChannelEdgeInfo{}, err
}

// We'll try and see if there are any opaque bytes left, if not, then
// we'll ignore the EOF error and return the edge as is.
edgeInfo.ExtraOpaqueData, err = wire.ReadVarBytes(
r, 0, MaxAllowedExtraOpaqueBytes, "blob",
)
switch {
case err == io.ErrUnexpectedEOF:
case err == io.EOF:
case err != nil:
return ChannelEdgeInfo{}, err
}

return edgeInfo, nil
}

Expand Down Expand Up @@ -2998,6 +3070,13 @@ func putChanEdgePolicy(edges, nodes *bolt.Bucket, edge *ChannelEdgePolicy,
return err
}

if len(edge.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes {
return ErrTooManyExtraOpaqueBytes(len(edge.ExtraOpaqueData))
}
if err := wire.WriteVarBytes(&b, 0, edge.ExtraOpaqueData); err != nil {
return err
}

// Before we write out the new edge, we'll create a new entry in the
// update index in order to keep it fresh.
var indexKey [8 + 8]byte
Expand Down Expand Up @@ -3182,6 +3261,18 @@ func deserializeChanEdgePolicy(r io.Reader,
pub[:], err)
}

// We'll try and see if there are any opaque bytes left, if not, then
// we'll ignore the EOF error and return the edge as is.
edge.ExtraOpaqueData, err = wire.ReadVarBytes(
r, 0, MaxAllowedExtraOpaqueBytes, "blob",
)
switch {
case err == io.ErrUnexpectedEOF:
case err == io.EOF:
case err != nil:
return nil, err
}

edge.Node = &node
return edge, nil
}
29 changes: 23 additions & 6 deletions channeldb/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
Alias: "kek",
Features: testFeatures,
Addresses: testAddrs,
ExtraOpaqueData: []byte("extra new data"),
db: db,
}
copy(node.PubKeyBytes[:], testPub.SerializeCompressed())
Expand Down Expand Up @@ -614,6 +615,11 @@ func assertEdgeInfoEqual(t *testing.T, e1 *ChannelEdgeInfo,
t.Fatalf("capacity doesn't match: %v vs %v", e1.Capacity,
e2.Capacity)
}

if !bytes.Equal(e1.ExtraOpaqueData, e2.ExtraOpaqueData) {
t.Fatalf("extra data doesn't match: %v vs %v",
e2.ExtraOpaqueData, e2.ExtraOpaqueData)
}
}

func TestEdgeInfoUpdates(t *testing.T) {
Expand Down Expand Up @@ -675,8 +681,9 @@ func TestEdgeInfoUpdates(t *testing.T) {
BitcoinSig1Bytes: testSig.Serialize(),
BitcoinSig2Bytes: testSig.Serialize(),
},
ChannelPoint: outpoint,
Capacity: 1000,
ChannelPoint: outpoint,
Capacity: 1000,
ExtraOpaqueData: []byte("new unknown feature"),
}
copy(edgeInfo.NodeKey1Bytes[:], firstNode.PubKeyBytes[:])
copy(edgeInfo.NodeKey2Bytes[:], secondNode.PubKeyBytes[:])
Expand All @@ -697,8 +704,9 @@ func TestEdgeInfoUpdates(t *testing.T) {
MinHTLC: 2342135,
FeeBaseMSat: 4352345,
FeeProportionalMillionths: 3452352,
Node: secondNode,
db: db,
Node: secondNode,
ExtraOpaqueData: []byte("new unknown feature2"),
db: db,
}
edge2 := &ChannelEdgePolicy{
SigBytes: testSig.Serialize(),
Expand All @@ -709,8 +717,9 @@ func TestEdgeInfoUpdates(t *testing.T) {
MinHTLC: 2342135,
FeeBaseMSat: 4352345,
FeeProportionalMillionths: 90392423,
Node: firstNode,
db: db,
Node: firstNode,
ExtraOpaqueData: []byte("new unknown feature1"),
db: db,
}

// Next, insert both nodes into the database, they should both be
Expand Down Expand Up @@ -2460,6 +2469,10 @@ func compareNodes(a, b *LightningNode) error {
return fmt.Errorf("HaveNodeAnnouncement doesn't match: expected %#v, \n "+
"got %#v", a.HaveNodeAnnouncement, b.HaveNodeAnnouncement)
}
if !bytes.Equal(a.ExtraOpaqueData, b.ExtraOpaqueData) {
return fmt.Errorf("extra data doesn't match: %v vs %v",
a.ExtraOpaqueData, b.ExtraOpaqueData)
}

return nil
}
Expand Down Expand Up @@ -2496,6 +2509,10 @@ func compareEdgePolicies(a, b *ChannelEdgePolicy) error {
"expected %v, got %v", a.FeeProportionalMillionths,
b.FeeProportionalMillionths)
}
if !bytes.Equal(a.ExtraOpaqueData, b.ExtraOpaqueData) {
return fmt.Errorf("extra data doesn't match: %v vs %v",
a.ExtraOpaqueData, b.ExtraOpaqueData)
}
if err := compareNodes(a.Node, b.Node); err != nil {
return err
}
Expand Down
34 changes: 20 additions & 14 deletions discovery/gossiper.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,14 @@ func (d *AuthenticatedGossiper) SynchronizeNode(syncPeer lnpeer.Peer) error {
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,
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
}

Expand Down Expand Up @@ -1645,6 +1646,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
AuthSigBytes: msg.Signature.ToSignatureBytes(),
Features: features,
Color: msg.RGBColor,
ExtraOpaqueData: msg.ExtraOpaqueData,
}

if err := d.cfg.Router.AddNode(node); err != nil {
Expand Down Expand Up @@ -1767,6 +1769,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
BitcoinKey2Bytes: msg.BitcoinKey2,
AuthProof: proof,
Features: featureBuf.Bytes(),
ExtraOpaqueData: msg.ExtraOpaqueData,
}

// We will add the edge to the channel router. If the nodes
Expand Down Expand Up @@ -2036,6 +2039,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
MinHTLC: msg.HtlcMinimumMsat,
FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee),
FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate),
ExtraOpaqueData: msg.ExtraOpaqueData,
}

if err := d.cfg.Router.UpdateEdge(update); err != nil {
Expand Down Expand Up @@ -2504,6 +2508,7 @@ func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo,
HtlcMinimumMsat: edge.MinHTLC,
BaseFee: uint32(edge.FeeBaseMSat),
FeeRate: uint32(edge.FeeProportionalMillionths),
ExtraOpaqueData: edge.ExtraOpaqueData,
}
chanUpdate.Signature, err = lnwire.NewSigFromRawSignature(edge.SigBytes)
if err != nil {
Expand Down Expand Up @@ -2545,13 +2550,14 @@ func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo,
if info.AuthProof != nil {
chanID := lnwire.NewShortChanIDFromInt(info.ChannelID)
chanAnn = &lnwire.ChannelAnnouncement{
ShortChannelID: chanID,
NodeID1: info.NodeKey1Bytes,
NodeID2: info.NodeKey2Bytes,
ChainHash: info.ChainHash,
BitcoinKey1: info.BitcoinKey1Bytes,
Features: lnwire.NewRawFeatureVector(),
BitcoinKey2: info.BitcoinKey2Bytes,
ShortChannelID: chanID,
NodeID1: info.NodeKey1Bytes,
NodeID2: info.NodeKey2Bytes,
ChainHash: info.ChainHash,
BitcoinKey1: info.BitcoinKey1Bytes,
Features: lnwire.NewRawFeatureVector(),
BitcoinKey2: info.BitcoinKey2Bytes,
ExtraOpaqueData: edge.ExtraOpaqueData,
}
chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature(
info.AuthProof.NodeSig1Bytes,
Expand Down
Loading