Merge pull request #1825 from Roasbeef/extra-gossip-message-data
channeldb+discovery: ensure we store, validate and propagate announcements with opaque data
This commit is contained in:
14 files changed
+486
-131
No files matched your search
@@ -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 {
|
||||
return fmt.Errorf("max allowed number of opaque bytes is %v, received "+
|
||||
"%v bytes", MaxAllowedExtraOpaqueBytes, numBytes)
|
||||
}
|
||||
@@ -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
|
||||
@@ -1707,6 +1715,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
|
||||
@@ -1991,6 +2007,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
|
||||
}
|
||||
|
||||
@@ -2335,6 +2359,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
|
||||
}
|
||||
|
||||
@@ -2704,6 +2736,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
|
||||
}
|
||||
@@ -2828,6 +2868,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
|
||||
}
|
||||
|
||||
@@ -2886,6 +2938,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())
|
||||
}
|
||||
|
||||
@@ -2963,6 +3023,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
|
||||
}
|
||||
|
||||
@@ -3011,6 +3083,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
|
||||
@@ -3195,6 +3274,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
|
||||
}
|
||||
+23
-6
@@ -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())
|
||||
@@ -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) {
|
||||
@@ -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[:])
|
||||
@@ -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(),
|
||||
@@ -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
|
||||
@@ -2461,6 +2470,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
|
||||
}
|
||||
@@ -2497,6 +2510,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
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user