mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-11 01:11:02 +02:00
htlcswitch: relay experimental endorsement signal with update_add_htlc
This commit is contained in:
parent
94f7ed46de
commit
774bfa740a
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/queue"
|
"github.com/lightningnetwork/lnd/queue"
|
||||||
|
"github.com/lightningnetwork/lnd/record"
|
||||||
"github.com/lightningnetwork/lnd/ticker"
|
"github.com/lightningnetwork/lnd/ticker"
|
||||||
"github.com/lightningnetwork/lnd/tlv"
|
"github.com/lightningnetwork/lnd/tlv"
|
||||||
)
|
)
|
||||||
@ -285,6 +286,10 @@ type ChannelLinkConfig struct {
|
|||||||
// MaxFeeExposure is the threshold in milli-satoshis after which we'll
|
// MaxFeeExposure is the threshold in milli-satoshis after which we'll
|
||||||
// restrict the flow of HTLCs and fee updates.
|
// restrict the flow of HTLCs and fee updates.
|
||||||
MaxFeeExposure lnwire.MilliSatoshi
|
MaxFeeExposure lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// ShouldFwdExpEndorsement is a closure that indicates whether the link
|
||||||
|
// should forward experimental endorsement signals.
|
||||||
|
ShouldFwdExpEndorsement func() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// channelLink is the service which drives a channel's commitment update
|
// channelLink is the service which drives a channel's commitment update
|
||||||
@ -3651,6 +3656,13 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
endorseValue := l.experimentalEndorsement(
|
||||||
|
record.CustomSet(add.CustomRecords),
|
||||||
|
)
|
||||||
|
endorseType := uint64(
|
||||||
|
lnwire.ExperimentalEndorsementType,
|
||||||
|
)
|
||||||
|
|
||||||
switch fwdPkg.State {
|
switch fwdPkg.State {
|
||||||
case channeldb.FwdStateProcessed:
|
case channeldb.FwdStateProcessed:
|
||||||
// This add was not forwarded on the previous
|
// This add was not forwarded on the previous
|
||||||
@ -3672,6 +3684,14 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||||||
BlindingPoint: fwdInfo.NextBlinding,
|
BlindingPoint: fwdInfo.NextBlinding,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
endorseValue.WhenSome(func(e byte) {
|
||||||
|
custRecords := map[uint64][]byte{
|
||||||
|
endorseType: {e},
|
||||||
|
}
|
||||||
|
|
||||||
|
outgoingAdd.CustomRecords = custRecords
|
||||||
|
})
|
||||||
|
|
||||||
// Finally, we'll encode the onion packet for
|
// Finally, we'll encode the onion packet for
|
||||||
// the _next_ hop using the hop iterator
|
// the _next_ hop using the hop iterator
|
||||||
// decoded for the current hop.
|
// decoded for the current hop.
|
||||||
@ -3722,6 +3742,12 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||||||
BlindingPoint: fwdInfo.NextBlinding,
|
BlindingPoint: fwdInfo.NextBlinding,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
endorseValue.WhenSome(func(e byte) {
|
||||||
|
addMsg.CustomRecords = map[uint64][]byte{
|
||||||
|
endorseType: {e},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Finally, we'll encode the onion packet for the
|
// Finally, we'll encode the onion packet for the
|
||||||
// _next_ hop using the hop iterator decoded for the
|
// _next_ hop using the hop iterator decoded for the
|
||||||
// current hop.
|
// current hop.
|
||||||
@ -3809,6 +3835,46 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||||||
l.forwardBatch(replay, switchPackets...)
|
l.forwardBatch(replay, switchPackets...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// experimentalEndorsement returns the value to set for our outgoing
|
||||||
|
// experimental endorsement field, and a boolean indicating whether it should
|
||||||
|
// be populated on the outgoing htlc.
|
||||||
|
func (l *channelLink) experimentalEndorsement(
|
||||||
|
customUpdateAdd record.CustomSet) fn.Option[byte] {
|
||||||
|
|
||||||
|
// Only relay experimental signal if we are within the experiment
|
||||||
|
// period.
|
||||||
|
if !l.cfg.ShouldFwdExpEndorsement() {
|
||||||
|
return fn.None[byte]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have any custom records or the experimental field is
|
||||||
|
// not set, just forward a zero value.
|
||||||
|
if len(customUpdateAdd) == 0 {
|
||||||
|
return fn.Some[byte](lnwire.ExperimentalUnendorsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := uint64(lnwire.ExperimentalEndorsementType)
|
||||||
|
value, set := customUpdateAdd[t]
|
||||||
|
if !set {
|
||||||
|
return fn.Some[byte](lnwire.ExperimentalUnendorsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect at least one byte for this field, consider it invalid if
|
||||||
|
// it has no data and just forward a zero value.
|
||||||
|
if len(value) == 0 {
|
||||||
|
return fn.Some[byte](lnwire.ExperimentalUnendorsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only forward endorsed if the incoming link is endorsed.
|
||||||
|
if value[0] == lnwire.ExperimentalEndorsed {
|
||||||
|
return fn.Some[byte](lnwire.ExperimentalEndorsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward as unendorsed otherwise, including cases where we've
|
||||||
|
// received an invalid value that uses more than 3 bits of information.
|
||||||
|
return fn.Some[byte](lnwire.ExperimentalUnendorsed)
|
||||||
|
}
|
||||||
|
|
||||||
// processExitHop handles an htlc for which this link is the exit hop. It
|
// processExitHop handles an htlc for which this link is the exit hop. It
|
||||||
// returns a boolean indicating whether the commitment tx needs an update.
|
// returns a boolean indicating whether the commitment tx needs an update.
|
||||||
func (l *channelLink) processExitHop(add lnwire.UpdateAddHTLC,
|
func (l *channelLink) processExitHop(add lnwire.UpdateAddHTLC,
|
||||||
|
@ -2245,6 +2245,7 @@ func newSingleLinkTestHarness(t *testing.T, chanAmt,
|
|||||||
NotifyInactiveLinkEvent: func(wire.OutPoint) {},
|
NotifyInactiveLinkEvent: func(wire.OutPoint) {},
|
||||||
HtlcNotifier: aliceSwitch.cfg.HtlcNotifier,
|
HtlcNotifier: aliceSwitch.cfg.HtlcNotifier,
|
||||||
GetAliases: getAliases,
|
GetAliases: getAliases,
|
||||||
|
ShouldFwdExpEndorsement: func() bool { return true },
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceLink := NewChannelLink(aliceCfg, aliceLc.channel)
|
aliceLink := NewChannelLink(aliceCfg, aliceLc.channel)
|
||||||
@ -4888,6 +4889,8 @@ func (h *persistentLinkHarness) restartLink(
|
|||||||
// Instantiate with a long interval, so that we can precisely control
|
// Instantiate with a long interval, so that we can precisely control
|
||||||
// the firing via force feeding.
|
// the firing via force feeding.
|
||||||
bticker := ticker.NewForce(time.Hour)
|
bticker := ticker.NewForce(time.Hour)
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
aliceCfg := ChannelLinkConfig{
|
aliceCfg := ChannelLinkConfig{
|
||||||
FwrdingPolicy: globalPolicy,
|
FwrdingPolicy: globalPolicy,
|
||||||
Peer: alicePeer,
|
Peer: alicePeer,
|
||||||
@ -4932,6 +4935,7 @@ func (h *persistentLinkHarness) restartLink(
|
|||||||
HtlcNotifier: h.hSwitch.cfg.HtlcNotifier,
|
HtlcNotifier: h.hSwitch.cfg.HtlcNotifier,
|
||||||
SyncStates: syncStates,
|
SyncStates: syncStates,
|
||||||
GetAliases: getAliases,
|
GetAliases: getAliases,
|
||||||
|
ShouldFwdExpEndorsement: func() bool { return true },
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceLink := NewChannelLink(aliceCfg, aliceChannel)
|
aliceLink := NewChannelLink(aliceCfg, aliceChannel)
|
||||||
|
@ -1154,6 +1154,7 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer,
|
|||||||
return server.htlcSwitch.ForwardPackets(linkQuit, packets...)
|
return server.htlcSwitch.ForwardPackets(linkQuit, packets...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:lll
|
||||||
link := NewChannelLink(
|
link := NewChannelLink(
|
||||||
ChannelLinkConfig{
|
ChannelLinkConfig{
|
||||||
BestHeight: server.htlcSwitch.BestHeight,
|
BestHeight: server.htlcSwitch.BestHeight,
|
||||||
@ -1193,6 +1194,7 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer,
|
|||||||
NotifyInactiveLinkEvent: func(wire.OutPoint) {},
|
NotifyInactiveLinkEvent: func(wire.OutPoint) {},
|
||||||
HtlcNotifier: server.htlcSwitch.cfg.HtlcNotifier,
|
HtlcNotifier: server.htlcSwitch.cfg.HtlcNotifier,
|
||||||
GetAliases: getAliases,
|
GetAliases: getAliases,
|
||||||
|
ShouldFwdExpEndorsement: func() bool { return true },
|
||||||
},
|
},
|
||||||
channel,
|
channel,
|
||||||
)
|
)
|
||||||
|
@ -530,9 +530,10 @@ func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) {
|
|||||||
require.NoError(ht, err, "failed to send request")
|
require.NoError(ht, err, "failed to send request")
|
||||||
|
|
||||||
// Assert that the Alice -> Bob custom records in update_add_htlc are
|
// Assert that the Alice -> Bob custom records in update_add_htlc are
|
||||||
// not propagated on the Bob -> Carol link.
|
// not propagated on the Bob -> Carol link, just an endorsement signal.
|
||||||
packet = ht.ReceiveHtlcInterceptor(carolInterceptor)
|
packet = ht.ReceiveHtlcInterceptor(carolInterceptor)
|
||||||
require.Len(ht, packet.InWireCustomRecords, 0)
|
require.Equal(ht, lntest.CustomRecordsWithUnendorsed(nil),
|
||||||
|
packet.InWireCustomRecords)
|
||||||
|
|
||||||
// We're going to tell Carol to forward 5k sats less to Dave. We need to
|
// We're going to tell Carol to forward 5k sats less to Dave. We need to
|
||||||
// set custom records on the HTLC as well, to make sure the HTLC isn't
|
// set custom records on the HTLC as well, to make sure the HTLC isn't
|
||||||
|
@ -102,9 +102,12 @@ func testInvoiceHtlcModifierBasic(ht *lntest.HarnessTest) {
|
|||||||
require.EqualValues(
|
require.EqualValues(
|
||||||
ht, tc.sendAmountMsat, modifierRequest.ExitHtlcAmt,
|
ht, tc.sendAmountMsat, modifierRequest.ExitHtlcAmt,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Expect custom records plus endorsement signal.
|
||||||
require.Equal(
|
require.Equal(
|
||||||
ht, tc.lastHopCustomRecords,
|
ht, lntest.CustomRecordsWithUnendorsed(
|
||||||
modifierRequest.ExitHtlcWireCustomRecords,
|
tc.lastHopCustomRecords,
|
||||||
|
), modifierRequest.ExitHtlcWireCustomRecords,
|
||||||
)
|
)
|
||||||
|
|
||||||
// For all other packets we resolve according to the test case.
|
// For all other packets we resolve according to the test case.
|
||||||
@ -140,8 +143,9 @@ func testInvoiceHtlcModifierBasic(ht *lntest.HarnessTest) {
|
|||||||
|
|
||||||
require.Len(ht, updatedInvoice.Htlcs, 1)
|
require.Len(ht, updatedInvoice.Htlcs, 1)
|
||||||
require.Equal(
|
require.Equal(
|
||||||
ht, tc.lastHopCustomRecords,
|
ht, lntest.CustomRecordsWithUnendorsed(
|
||||||
updatedInvoice.Htlcs[0].CustomRecords,
|
tc.lastHopCustomRecords,
|
||||||
|
), updatedInvoice.Htlcs[0].CustomRecords,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Make sure the custom channel data contains the encoded
|
// Make sure the custom channel data contains the encoded
|
||||||
|
@ -282,3 +282,15 @@ func CalcStaticFeeBuffer(c lnrpc.CommitmentType, numHTLCs int) btcutil.Amount {
|
|||||||
|
|
||||||
return feeBuffer.ToSatoshis()
|
return feeBuffer.ToSatoshis()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CustomRecordsWithUnendorsed copies the map of custom records and adds an
|
||||||
|
// endorsed signal (replacing in the case of conflict) for assertion in tests.
|
||||||
|
func CustomRecordsWithUnendorsed(
|
||||||
|
originalRecords lnwire.CustomRecords) map[uint64][]byte {
|
||||||
|
|
||||||
|
return originalRecords.MergedCopy(map[uint64][]byte{
|
||||||
|
uint64(lnwire.ExperimentalEndorsementType): {
|
||||||
|
lnwire.ExperimentalUnendorsed,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -8,11 +8,29 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/tlv"
|
"github.com/lightningnetwork/lnd/tlv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OnionPacketSize is the size of the serialized Sphinx onion packet included
|
const (
|
||||||
// in each UpdateAddHTLC message. The breakdown of the onion packet is as
|
// OnionPacketSize is the size of the serialized Sphinx onion packet
|
||||||
// follows: 1-byte version, 33-byte ephemeral public key (for ECDH), 1300-bytes
|
// included in each UpdateAddHTLC message. The breakdown of the onion
|
||||||
// of per-hop data, and a 32-byte HMAC over the entire packet.
|
// packet is as follows: 1-byte version, 33-byte ephemeral public key
|
||||||
const OnionPacketSize = 1366
|
// (for ECDH), 1300-bytes of per-hop data, and a 32-byte HMAC over the
|
||||||
|
// entire packet.
|
||||||
|
OnionPacketSize = 1366
|
||||||
|
|
||||||
|
// ExperimentalEndorsementType is the TLV type used for a custom
|
||||||
|
// record that sets an experimental endorsement value.
|
||||||
|
ExperimentalEndorsementType tlv.Type = 106823
|
||||||
|
|
||||||
|
// ExperimentalUnendorsed is the value that the experimental endorsement
|
||||||
|
// field contains when a htlc is not endorsed.
|
||||||
|
ExperimentalUnendorsed = 0
|
||||||
|
|
||||||
|
// ExperimentalEndorsed is the value that the experimental endorsement
|
||||||
|
// field contains when a htlc is endorsed. We're using a single byte
|
||||||
|
// to represent our endorsement value, but limit the value to using
|
||||||
|
// the first three bits (max value = 00000111). Interpreted as a uint8
|
||||||
|
// (an alias for byte in go), we can just define this constant as 7.
|
||||||
|
ExperimentalEndorsed = 7
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// BlindingPointTlvType is the type for ephemeral pubkeys used in
|
// BlindingPointTlvType is the type for ephemeral pubkeys used in
|
||||||
|
@ -424,6 +424,10 @@ type Config struct {
|
|||||||
// used to modify the way the co-op close transaction is constructed.
|
// used to modify the way the co-op close transaction is constructed.
|
||||||
AuxChanCloser fn.Option[chancloser.AuxChanCloser]
|
AuxChanCloser fn.Option[chancloser.AuxChanCloser]
|
||||||
|
|
||||||
|
// ShouldFwdExpEndorsement is a closure that indicates whether
|
||||||
|
// experimental endorsement signals should be set.
|
||||||
|
ShouldFwdExpEndorsement func() bool
|
||||||
|
|
||||||
// Quit is the server's quit channel. If this is closed, we halt operation.
|
// Quit is the server's quit channel. If this is closed, we halt operation.
|
||||||
Quit chan struct{}
|
Quit chan struct{}
|
||||||
}
|
}
|
||||||
@ -1319,6 +1323,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
|
|||||||
PreviouslySentShutdown: shutdownMsg,
|
PreviouslySentShutdown: shutdownMsg,
|
||||||
DisallowRouteBlinding: p.cfg.DisallowRouteBlinding,
|
DisallowRouteBlinding: p.cfg.DisallowRouteBlinding,
|
||||||
MaxFeeExposure: p.cfg.MaxFeeExposure,
|
MaxFeeExposure: p.cfg.MaxFeeExposure,
|
||||||
|
ShouldFwdExpEndorsement: p.cfg.ShouldFwdExpEndorsement,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before adding our new link, purge the switch of any pending or live
|
// Before adding our new link, purge the switch of any pending or live
|
||||||
|
11
server.go
11
server.go
@ -133,6 +133,12 @@ var (
|
|||||||
//
|
//
|
||||||
// TODO(roasbeef): add command line param to modify.
|
// TODO(roasbeef): add command line param to modify.
|
||||||
MaxFundingAmount = funding.MaxBtcFundingAmount
|
MaxFundingAmount = funding.MaxBtcFundingAmount
|
||||||
|
|
||||||
|
// EndorsementExperimentEnd is the time after which nodes should stop
|
||||||
|
// propagating experimental endorsement signals.
|
||||||
|
//
|
||||||
|
// Per blip04: January 1, 2026 12:00:00 AM UTC in unix seconds.
|
||||||
|
EndorsementExperimentEnd = time.Unix(1767225600, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
// errPeerAlreadyConnected is an error returned by the server when we're
|
// errPeerAlreadyConnected is an error returned by the server when we're
|
||||||
@ -4214,6 +4220,11 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
|||||||
MsgRouter: s.implCfg.MsgRouter,
|
MsgRouter: s.implCfg.MsgRouter,
|
||||||
AuxChanCloser: s.implCfg.AuxChanCloser,
|
AuxChanCloser: s.implCfg.AuxChanCloser,
|
||||||
AuxResolver: s.implCfg.AuxContractResolver,
|
AuxResolver: s.implCfg.AuxContractResolver,
|
||||||
|
ShouldFwdExpEndorsement: func() bool {
|
||||||
|
return clock.NewDefaultClock().Now().Before(
|
||||||
|
EndorsementExperimentEnd,
|
||||||
|
)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(pCfg.PubKeyBytes[:], peerAddr.IdentityKey.SerializeCompressed())
|
copy(pCfg.PubKeyBytes[:], peerAddr.IdentityKey.SerializeCompressed())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user