Skip to content

Commit 32592db

Browse files
authored
Merge pull request #9897 from ellemouton/inboundFeeTLV
multi: explicitly define InboundFees in ChannelUpdate and ChannelEdgePolicy
2 parents c6d6d4c + 6141bcd commit 32592db

22 files changed

+248
-101
lines changed

discovery/gossiper.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3257,6 +3257,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context,
32573257
MaxHTLC: upd.HtlcMaximumMsat,
32583258
FeeBaseMSat: lnwire.MilliSatoshi(upd.BaseFee),
32593259
FeeProportionalMillionths: lnwire.MilliSatoshi(upd.FeeRate),
3260+
InboundFee: upd.InboundFee.ValOpt(),
32603261
ExtraOpaqueData: upd.ExtraOpaqueData,
32613262
}
32623263

docs/release-notes/release-notes-0.20.0.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ circuit. The indices are only available for forwarding events saved after v0.20.
8888
# Technical and Architectural Updates
8989
## BOLT Spec Updates
9090

91+
* Explicitly define the [inbound fee TLV
92+
record](https://github.com/lightningnetwork/lnd/pull/9897) on the
93+
`channel_update` message and handle it explicitly throughout the code base
94+
instead of extracting it from the TLV stream at various call-sites.
95+
9196
## Testing
9297

9398
## Database
@@ -99,4 +104,5 @@ circuit. The indices are only available for forwarding events saved after v0.20.
99104
# Contributors (Alphabetical Order)
100105

101106
* Abdulkbk
102-
Pins
107+
* Elle Mouton
108+
* Pins

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ require (
203203
// store have been included in a tagged sqldb version.
204204
replace github.com/lightningnetwork/lnd/sqldb => ./sqldb
205205

206+
// TODO(elle): replace once the updated tlv package has been tagged.
207+
replace github.com/lightningnetwork/lnd/tlv => ./tlv
208+
206209
// This replace is for https://github.com/advisories/GHSA-25xm-hr59-7c27
207210
replace github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.11
208211

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,6 @@ github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQ
377377
github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4=
378378
github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM=
379379
github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA=
380-
github.com/lightningnetwork/lnd/tlv v1.3.1 h1:o7CZg06y+rJZfUMAo0WzBLr0pgBWCzrt0f9gpujYUzk=
381-
github.com/lightningnetwork/lnd/tlv v1.3.1/go.mod h1:pJuiBj1ecr1WWLOtcZ+2+hu9Ey25aJWFIsjmAoPPnmc=
382380
github.com/lightningnetwork/lnd/tor v1.1.6 h1:WHUumk7WgU6BUFsqHuqszI9P6nfhMeIG+rjJBlVE6OE=
383381
github.com/lightningnetwork/lnd/tor v1.1.6/go.mod h1:qSRB8llhAK+a6kaTPWOLLXSZc6Hg8ZC0mq1sUQ/8JfI=
384382
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=

graph/builder.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -943,7 +943,7 @@ func (b *Builder) ApplyChannelUpdate(msg *lnwire.ChannelUpdate1) bool {
943943
return false
944944
}
945945

946-
err = b.UpdateEdge(&models.ChannelEdgePolicy{
946+
update := &models.ChannelEdgePolicy{
947947
SigBytes: msg.Signature.ToSignatureBytes(),
948948
ChannelID: msg.ShortChannelID.ToUint64(),
949949
LastUpdate: time.Unix(int64(msg.Timestamp), 0),
@@ -954,8 +954,11 @@ func (b *Builder) ApplyChannelUpdate(msg *lnwire.ChannelUpdate1) bool {
954954
MaxHTLC: msg.HtlcMaximumMsat,
955955
FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee),
956956
FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate),
957+
InboundFee: msg.InboundFee.ValOpt(),
957958
ExtraOpaqueData: msg.ExtraOpaqueData,
958-
})
959+
}
960+
961+
err = b.UpdateEdge(update)
959962
if err != nil && !IsError(err, ErrIgnored, ErrOutdated) {
960963
log.Errorf("Unable to apply channel update: %v", err)
961964
return false

graph/db/errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ var (
1212
ErrEdgePolicyOptionalFieldNotFound = fmt.Errorf("optional field not " +
1313
"present")
1414

15+
// ErrParsingExtraTLVBytes is returned when we attempt to parse
16+
// extra opaque bytes as a TLV stream, but the parsing fails.
17+
ErrParsingExtraTLVBytes = fmt.Errorf("error parsing extra TLV bytes")
18+
1519
// ErrGraphNotFound is returned when at least one of the components of
1620
// graph doesn't exist.
1721
ErrGraphNotFound = fmt.Errorf("graph bucket not initialized")

graph/db/graph_cache.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -180,17 +180,6 @@ func (c *GraphCache) updateOrAddEdge(node route.Vertex, edge *DirectedChannel) {
180180
func (c *GraphCache) UpdatePolicy(policy *models.ChannelEdgePolicy, fromNode,
181181
toNode route.Vertex, edge1 bool) {
182182

183-
// Extract inbound fee if possible and available. If there is a decoding
184-
// error, ignore this policy.
185-
var inboundFee lnwire.Fee
186-
_, err := policy.ExtraOpaqueData.ExtractRecords(&inboundFee)
187-
if err != nil {
188-
log.Errorf("Failed to extract records from edge policy %v: %v",
189-
policy.ChannelID, err)
190-
191-
return
192-
}
193-
194183
c.mtx.Lock()
195184
defer c.mtx.Unlock()
196185

@@ -216,13 +205,17 @@ func (c *GraphCache) UpdatePolicy(policy *models.ChannelEdgePolicy, fromNode,
216205
// policy for node 1.
217206
case channel.IsNode1 && edge1:
218207
channel.OutPolicySet = true
219-
channel.InboundFee = inboundFee
208+
policy.InboundFee.WhenSome(func(fee lnwire.Fee) {
209+
channel.InboundFee = fee
210+
})
220211

221212
// This is node 2, and it is edge 2, so this is the outgoing
222213
// policy for node 2.
223214
case !channel.IsNode1 && !edge1:
224215
channel.OutPolicySet = true
225-
channel.InboundFee = inboundFee
216+
policy.InboundFee.WhenSome(func(fee lnwire.Fee) {
217+
channel.InboundFee = fee
218+
})
226219

227220
// The other two cases left mean it's the inbound policy for the
228221
// node.

graph/db/graph_cache_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/hex"
55
"testing"
66

7+
"github.com/lightningnetwork/lnd/fn/v2"
78
"github.com/lightningnetwork/lnd/graph/db/models"
89
"github.com/lightningnetwork/lnd/lnwire"
910
"github.com/lightningnetwork/lnd/routing/route"
@@ -37,11 +38,17 @@ func TestGraphCacheAddNode(t *testing.T) {
3738
channelFlagA, channelFlagB = 1, 0
3839
}
3940

41+
inboundFee := lnwire.Fee{
42+
BaseFee: 10,
43+
FeeRate: 20,
44+
}
45+
4046
outPolicy1 := &models.ChannelEdgePolicy{
4147
ChannelID: 1000,
4248
ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagA),
4349
ToNode: nodeB,
4450
// Define an inbound fee.
51+
InboundFee: fn.Some(inboundFee),
4552
ExtraOpaqueData: []byte{
4653
253, 217, 3, 8, 0, 0, 0, 10, 0, 0, 0, 20,
4754
},

graph/db/graph_test.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4195,19 +4195,25 @@ func TestGraphCacheForEachNodeChannel(t *testing.T) {
41954195

41964196
directedChan := getSingleChannel()
41974197
require.NotNil(t, directedChan)
4198-
require.Equal(t, directedChan.InboundFee, lnwire.Fee{
4198+
expectedInbound := lnwire.Fee{
41994199
BaseFee: 10,
42004200
FeeRate: 20,
4201-
})
4201+
}
4202+
require.Equal(t, expectedInbound, directedChan.InboundFee)
42024203

4203-
// Set an invalid inbound fee and check that the edge is no longer
4204-
// returned.
4204+
// Set an invalid inbound fee and check that persistence fails.
42054205
edge1.ExtraOpaqueData = []byte{
42064206
253, 217, 3, 8, 0,
42074207
}
4208-
require.NoError(t, graph.UpdateEdgePolicy(edge1))
4208+
require.ErrorIs(
4209+
t, graph.UpdateEdgePolicy(edge1), ErrParsingExtraTLVBytes,
4210+
)
42094211

4210-
require.Nil(t, getSingleChannel())
4212+
// Since persistence of the last update failed, we should still bet
4213+
// the previous result when we query the channel again.
4214+
directedChan = getSingleChannel()
4215+
require.NotNil(t, directedChan)
4216+
require.Equal(t, expectedInbound, directedChan.InboundFee)
42114217
}
42124218

42134219
// TestGraphLoading asserts that the cache is properly reconstructed after a

graph/db/kv_store.go

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/btcsuite/btcwallet/walletdb"
2323
"github.com/lightningnetwork/lnd/aliasmgr"
2424
"github.com/lightningnetwork/lnd/batch"
25+
"github.com/lightningnetwork/lnd/fn/v2"
2526
"github.com/lightningnetwork/lnd/graph/db/models"
2627
"github.com/lightningnetwork/lnd/input"
2728
"github.com/lightningnetwork/lnd/kvdb"
@@ -284,6 +285,13 @@ func (c *KVStore) getChannelMap(edges kvdb.RBucket) (
284285
case errors.Is(err, ErrEdgePolicyOptionalFieldNotFound):
285286
return nil
286287

288+
// We don't want a single policy with bad TLV data to stop us
289+
// from loading the rest of the data, so we just skip this
290+
// policy. This is for backwards compatibility since we did not
291+
// use to validate TLV data in the past before persisting it.
292+
case errors.Is(err, ErrParsingExtraTLVBytes):
293+
return nil
294+
287295
case err != nil:
288296
return err
289297
}
@@ -474,24 +482,19 @@ func (c *KVStore) forEachNodeDirectedChannel(tx kvdb.RTx,
474482
cachedInPolicy.ToNodeFeatures = toNodeFeatures
475483
}
476484

477-
var inboundFee lnwire.Fee
478-
if p1 != nil {
479-
// Extract inbound fee. If there is a decoding error,
480-
// skip this edge.
481-
_, err := p1.ExtraOpaqueData.ExtractRecords(&inboundFee)
482-
if err != nil {
483-
return nil
484-
}
485-
}
486-
487485
directedChannel := &DirectedChannel{
488486
ChannelID: e.ChannelID,
489487
IsNode1: node == e.NodeKey1Bytes,
490488
OtherNode: e.NodeKey2Bytes,
491489
Capacity: e.Capacity,
492490
OutPolicySet: p1 != nil,
493491
InPolicy: cachedInPolicy,
494-
InboundFee: inboundFee,
492+
}
493+
494+
if p1 != nil {
495+
p1.InboundFee.WhenSome(func(fee lnwire.Fee) {
496+
directedChannel.InboundFee = fee
497+
})
495498
}
496499

497500
if node == e.NodeKey2Bytes {
@@ -2342,7 +2345,7 @@ func (c *KVStore) FilterChannelRange(startHeight,
23422345
edge, err := deserializeChanEdgePolicyRaw(r)
23432346
if err != nil && !errors.Is(
23442347
err, ErrEdgePolicyOptionalFieldNotFound,
2345-
) {
2348+
) && !errors.Is(err, ErrParsingExtraTLVBytes) {
23462349

23472350
return err
23482351
}
@@ -2357,7 +2360,7 @@ func (c *KVStore) FilterChannelRange(startHeight,
23572360
edge, err := deserializeChanEdgePolicyRaw(r)
23582361
if err != nil && !errors.Is(
23592362
err, ErrEdgePolicyOptionalFieldNotFound,
2360-
) {
2363+
) && !errors.Is(err, ErrParsingExtraTLVBytes) {
23612364

23622365
return err
23632366
}
@@ -4334,15 +4337,20 @@ func putChanEdgePolicy(edges kvdb.RwBucket, edge *models.ChannelEdgePolicy,
43344337
// need to deserialize the existing policy within the database
43354338
// (now outdated by the new one), and delete its corresponding
43364339
// entry within the update index. We'll ignore any
4337-
// ErrEdgePolicyOptionalFieldNotFound error, as we only need
4338-
// the channel ID and update time to delete the entry.
4340+
// ErrEdgePolicyOptionalFieldNotFound or ErrParsingExtraTLVBytes
4341+
// errors, as we only need the channel ID and update time to
4342+
// delete the entry.
4343+
//
43394344
// TODO(halseth): get rid of these invalid policies in a
43404345
// migration.
4346+
// TODO(elle): complete the above TODO in migration from kvdb
4347+
// to SQL.
43414348
oldEdgePolicy, err := deserializeChanEdgePolicy(
43424349
bytes.NewReader(edgeBytes),
43434350
)
43444351
if err != nil &&
4345-
!errors.Is(err, ErrEdgePolicyOptionalFieldNotFound) {
4352+
!errors.Is(err, ErrEdgePolicyOptionalFieldNotFound) &&
4353+
!errors.Is(err, ErrParsingExtraTLVBytes) {
43464354

43474355
return err
43484356
}
@@ -4449,6 +4457,11 @@ func fetchChanEdgePolicy(edges kvdb.RBucket, chanID []byte,
44494457
case errors.Is(err, ErrEdgePolicyOptionalFieldNotFound):
44504458
return nil, nil
44514459

4460+
// If the policy contains invalid TLV bytes, we return nil as if
4461+
// the policy was unknown.
4462+
case errors.Is(err, ErrParsingExtraTLVBytes):
4463+
return nil, nil
4464+
44524465
case err != nil:
44534466
return nil, err
44544467
}
@@ -4546,6 +4559,12 @@ func serializeChanEdgePolicy(w io.Writer, edge *models.ChannelEdgePolicy,
45464559
}
45474560
}
45484561

4562+
// Validate that the ExtraOpaqueData is in fact a valid TLV stream.
4563+
err = edge.ExtraOpaqueData.ValidateTLV()
4564+
if err != nil {
4565+
return fmt.Errorf("%w: %w", ErrParsingExtraTLVBytes, err)
4566+
}
4567+
45494568
if len(edge.ExtraOpaqueData) > MaxAllowedExtraOpaqueBytes {
45504569
return ErrTooManyExtraOpaqueBytes(len(edge.ExtraOpaqueData))
45514570
}
@@ -4562,15 +4581,18 @@ func serializeChanEdgePolicy(w io.Writer, edge *models.ChannelEdgePolicy,
45624581

45634582
func deserializeChanEdgePolicy(r io.Reader) (*models.ChannelEdgePolicy, error) {
45644583
// Deserialize the policy. Note that in case an optional field is not
4565-
// found, both an error and a populated policy object are returned.
4566-
edge, deserializeErr := deserializeChanEdgePolicyRaw(r)
4567-
if deserializeErr != nil &&
4568-
!errors.Is(deserializeErr, ErrEdgePolicyOptionalFieldNotFound) {
4584+
// found or if the edge has invalid TLV data, then both an error and a
4585+
// populated policy object are returned so that the caller can decide
4586+
// if it still wants to use the edge or not.
4587+
edge, err := deserializeChanEdgePolicyRaw(r)
4588+
if err != nil &&
4589+
!errors.Is(err, ErrEdgePolicyOptionalFieldNotFound) &&
4590+
!errors.Is(err, ErrParsingExtraTLVBytes) {
45694591

4570-
return nil, deserializeErr
4592+
return nil, err
45714593
}
45724594

4573-
return edge, deserializeErr
4595+
return edge, err
45744596
}
45754597

45764598
func deserializeChanEdgePolicyRaw(r io.Reader) (*models.ChannelEdgePolicy,
@@ -4657,6 +4679,22 @@ func deserializeChanEdgePolicyRaw(r io.Reader) (*models.ChannelEdgePolicy,
46574679
edge.ExtraOpaqueData = opq[8:]
46584680
}
46594681

4682+
// Attempt to extract the inbound fee from the opaque data. If we fail
4683+
// to parse the TLV here, we return an error we also return the edge
4684+
// so that the caller can still use it. This is for backwards
4685+
// compatibility in case we have already persisted some policies that
4686+
// have invalid TLV data.
4687+
var inboundFee lnwire.Fee
4688+
typeMap, err := edge.ExtraOpaqueData.ExtractRecords(&inboundFee)
4689+
if err != nil {
4690+
return edge, fmt.Errorf("%w: %w", ErrParsingExtraTLVBytes, err)
4691+
}
4692+
4693+
val, ok := typeMap[lnwire.FeeRecordType]
4694+
if ok && val == nil {
4695+
edge.InboundFee = fn.Some(inboundFee)
4696+
}
4697+
46604698
return edge, nil
46614699
}
46624700

0 commit comments

Comments
 (0)