lnwire+channeldb: parse inbound fees

In this commit, the tlv extension of a channel update message is parsed.
If an inbound fee schedule is encountered, it is reported in the
graph rpc calls.
This commit is contained in:
Joost Jager 2022-09-23 14:42:28 +02:00
parent 1d61de28c0
commit 3e6adbf1c0
9 changed files with 1561 additions and 1396 deletions

View File

@ -70,7 +70,7 @@ type ChannelEdgePolicy struct {
// 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
ExtraOpaqueData lnwire.ExtraOpaqueData
}
// Signature is a channel announcement signature, which is needed for proper

View File

@ -217,6 +217,14 @@
"format": "byte"
},
"description": "Custom channel update tlv records."
},
"inbound_fee_base_msat": {
"type": "integer",
"format": "int32"
},
"inbound_fee_rate_milli_msat": {
"type": "integer",
"format": "int32"
}
}
},

File diff suppressed because it is too large Load Diff

View File

@ -3330,6 +3330,9 @@ message RoutingPolicy {
// Custom channel update tlv records.
map<uint64, bytes> custom_records = 8;
int32 inbound_fee_base_msat = 9;
int32 inbound_fee_rate_milli_msat = 10;
}
/*

View File

@ -6840,6 +6840,14 @@
"format": "byte"
},
"description": "Custom channel update tlv records."
},
"inbound_fee_base_msat": {
"type": "integer",
"format": "int32"
},
"inbound_fee_rate_milli_msat": {
"type": "integer",
"format": "int32"
}
}
},

60
lnwire/typed_fee.go Normal file
View File

@ -0,0 +1,60 @@
package lnwire
import (
"io"
"github.com/lightningnetwork/lnd/tlv"
)
const (
FeeRecordType tlv.Type = 55555
)
// Fee represents a fee schedule.
type Fee struct {
BaseFee int32
FeeRate int32
}
// Record returns a TLV record that can be used to encode/decode the fee
// type from a given TLV stream.
func (l *Fee) Record() tlv.Record {
return tlv.MakeStaticRecord(
FeeRecordType, l, 8, feeEncoder, feeDecoder, //nolint:gomnd
)
}
// feeEncoder is a custom TLV encoder for the fee record.
func feeEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
v, ok := val.(*Fee)
if !ok {
return tlv.NewTypeForEncodingErr(val, "lnwire.Fee")
}
if err := tlv.EUint32T(w, uint32(v.BaseFee), buf); err != nil {
return err
}
return tlv.EUint32T(w, uint32(v.FeeRate), buf)
}
// feeDecoder is a custom TLV decoder for the fee record.
func feeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
v, ok := val.(*Fee)
if !ok {
return tlv.NewTypeForDecodingErr(val, "lnwire.Fee", l, 8)
}
var baseFee, feeRate uint32
if err := tlv.DUint32(r, &baseFee, buf, 4); err != nil { //nolint: gomnd,lll
return err
}
if err := tlv.DUint32(r, &feeRate, buf, 4); err != nil { //nolint: gomnd,lll
return err
}
v.FeeRate = int32(feeRate)
v.BaseFee = int32(baseFee)
return nil
}

40
lnwire/typed_fee_test.go Normal file
View File

@ -0,0 +1,40 @@
package lnwire
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestTypedFee(t *testing.T) {
t.Parallel()
t.Run("positive", func(t *testing.T) {
t.Parallel()
testTypedFee(t, Fee{
BaseFee: 10,
FeeRate: 20,
})
})
t.Run("negative", func(t *testing.T) {
t.Parallel()
testTypedFee(t, Fee{
BaseFee: -10,
FeeRate: -20,
})
})
}
func testTypedFee(t *testing.T, fee Fee) { //nolint: thelper
var eob ExtraOpaqueData
require.NoError(t, eob.PackRecords(&fee))
var extractedFee Fee
_, err := eob.ExtractRecords(&extractedFee)
require.NoError(t, err)
require.Equal(t, fee, extractedFee)
}

View File

@ -2763,6 +2763,7 @@ func (r *ChannelRouter) applyChannelUpdate(msg *lnwire.ChannelUpdate) bool {
MaxHTLC: msg.HtlcMaximumMsat,
FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee),
FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate),
ExtraOpaqueData: msg.ExtraOpaqueData,
})
if err != nil && !IsError(err, ErrIgnored, ErrOutdated) {
log.Errorf("Unable to apply channel update: %v", err)

View File

@ -6059,6 +6059,23 @@ func marshalExtraOpaqueData(data []byte) map[uint64][]byte {
return records
}
// extractInboundFeeSafe tries to extract the inbound fee from the given extra
// opaque data tlv block. If parsing fails, a zero inbound fee is returned. This
// function is typically used on unvalidated data coming stored in the database.
// There is not much we can do other than ignoring errors here.
func extractInboundFeeSafe(data lnwire.ExtraOpaqueData) lnwire.Fee {
var inboundFee lnwire.Fee
_, err := data.ExtractRecords(&inboundFee)
if err != nil {
// Return zero fee. Do not return the inboundFee variable
// because it may be undefined.
return lnwire.Fee{}
}
return inboundFee
}
func marshalDBEdge(edgeInfo *models.ChannelEdgeInfo,
c1, c2 *models.ChannelEdgePolicy) *lnrpc.ChannelEdge {
@ -6108,6 +6125,7 @@ func marshalDBRoutingPolicy(
disabled := policy.ChannelFlags&lnwire.ChanUpdateDisabled != 0
customRecords := marshalExtraOpaqueData(policy.ExtraOpaqueData)
inboundFee := extractInboundFeeSafe(policy.ExtraOpaqueData)
return &lnrpc.RoutingPolicy{
TimeLockDelta: uint32(policy.TimeLockDelta),
@ -6118,6 +6136,9 @@ func marshalDBRoutingPolicy(
Disabled: disabled,
LastUpdate: uint32(policy.LastUpdate.Unix()),
CustomRecords: customRecords,
InboundFeeBaseMsat: inboundFee.BaseFee,
InboundFeeRateMilliMsat: inboundFee.FeeRate,
}
}