mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-13 11:09:23 +01:00
Merge pull request #8390 from carlaKC/7883-experimental-endorsement
Add Experimental Endorsement Signalling
This commit is contained in:
commit
fbeab726e1
20 changed files with 380 additions and 40 deletions
|
@ -61,6 +61,12 @@
|
|||
https://github.com/lightningnetwork/lnd/pull/9253)
|
||||
|
||||
# New Features
|
||||
|
||||
* [Support](https://github.com/lightningnetwork/lnd/pull/8390) for
|
||||
[experimental endorsement](https://github.com/lightning/blips/pull/27)
|
||||
signal relay was added. This signal has *no impact* on routing, and
|
||||
is deployed experimentally to assist ongoing channel jamming research.
|
||||
|
||||
## Functional Enhancements
|
||||
* [Add ability](https://github.com/lightningnetwork/lnd/pull/8998) to paginate
|
||||
wallet transactions.
|
||||
|
@ -205,6 +211,7 @@ The underlying functionality between those two options remain the same.
|
|||
* Abdullahi Yunus
|
||||
* Animesh Bilthare
|
||||
* Boris Nagaev
|
||||
* Carla Kirk-Cohen
|
||||
* CharlieZKSmith
|
||||
* Elle Mouton
|
||||
* George Tsagkarelis
|
||||
|
|
|
@ -96,4 +96,7 @@ var defaultSetDesc = setDesc{
|
|||
SetInit: {}, // I
|
||||
SetNodeAnn: {}, // N
|
||||
},
|
||||
lnwire.ExperimentalEndorsementOptional: {
|
||||
SetNodeAnn: {}, // N
|
||||
},
|
||||
}
|
||||
|
|
|
@ -66,6 +66,10 @@ type Config struct {
|
|||
// NoTaprootOverlay unsets the taproot overlay channel feature bits.
|
||||
NoTaprootOverlay bool
|
||||
|
||||
// NoExperimentalEndorsement unsets any bits that signal support for
|
||||
// forwarding experimental endorsement.
|
||||
NoExperimentalEndorsement bool
|
||||
|
||||
// CustomFeatures is a set of custom features to advertise in each
|
||||
// set.
|
||||
CustomFeatures map[Set][]lnwire.FeatureBit
|
||||
|
@ -199,6 +203,12 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
|
|||
raw.Unset(lnwire.SimpleTaprootOverlayChansOptional)
|
||||
raw.Unset(lnwire.SimpleTaprootOverlayChansRequired)
|
||||
}
|
||||
|
||||
if cfg.NoExperimentalEndorsement {
|
||||
raw.Unset(lnwire.ExperimentalEndorsementOptional)
|
||||
raw.Unset(lnwire.ExperimentalEndorsementRequired)
|
||||
}
|
||||
|
||||
for _, custom := range cfg.CustomFeatures[set] {
|
||||
if custom > set.Maximum() {
|
||||
return nil, fmt.Errorf("feature bit: %v "+
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/queue"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/ticker"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
@ -285,6 +286,10 @@ type ChannelLinkConfig struct {
|
|||
// MaxFeeExposure is the threshold in milli-satoshis after which we'll
|
||||
// restrict the flow of HTLCs and fee updates.
|
||||
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
|
||||
|
@ -3651,6 +3656,13 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||
continue
|
||||
}
|
||||
|
||||
endorseValue := l.experimentalEndorsement(
|
||||
record.CustomSet(add.CustomRecords),
|
||||
)
|
||||
endorseType := uint64(
|
||||
lnwire.ExperimentalEndorsementType,
|
||||
)
|
||||
|
||||
switch fwdPkg.State {
|
||||
case channeldb.FwdStateProcessed:
|
||||
// This add was not forwarded on the previous
|
||||
|
@ -3672,6 +3684,14 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||
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
|
||||
// the _next_ hop using the hop iterator
|
||||
// decoded for the current hop.
|
||||
|
@ -3722,6 +3742,12 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||
BlindingPoint: fwdInfo.NextBlinding,
|
||||
}
|
||||
|
||||
endorseValue.WhenSome(func(e byte) {
|
||||
addMsg.CustomRecords = map[uint64][]byte{
|
||||
endorseType: {e},
|
||||
}
|
||||
})
|
||||
|
||||
// Finally, we'll encode the onion packet for the
|
||||
// _next_ hop using the hop iterator decoded for the
|
||||
// current hop.
|
||||
|
@ -3809,6 +3835,46 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg) {
|
|||
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
|
||||
// returns a boolean indicating whether the commitment tx needs an update.
|
||||
func (l *channelLink) processExitHop(add lnwire.UpdateAddHTLC,
|
||||
|
|
|
@ -2245,6 +2245,7 @@ func newSingleLinkTestHarness(t *testing.T, chanAmt,
|
|||
NotifyInactiveLinkEvent: func(wire.OutPoint) {},
|
||||
HtlcNotifier: aliceSwitch.cfg.HtlcNotifier,
|
||||
GetAliases: getAliases,
|
||||
ShouldFwdExpEndorsement: func() bool { return true },
|
||||
}
|
||||
|
||||
aliceLink := NewChannelLink(aliceCfg, aliceLc.channel)
|
||||
|
@ -4888,6 +4889,8 @@ func (h *persistentLinkHarness) restartLink(
|
|||
// Instantiate with a long interval, so that we can precisely control
|
||||
// the firing via force feeding.
|
||||
bticker := ticker.NewForce(time.Hour)
|
||||
|
||||
//nolint:lll
|
||||
aliceCfg := ChannelLinkConfig{
|
||||
FwrdingPolicy: globalPolicy,
|
||||
Peer: alicePeer,
|
||||
|
@ -4932,6 +4935,7 @@ func (h *persistentLinkHarness) restartLink(
|
|||
HtlcNotifier: h.hSwitch.cfg.HtlcNotifier,
|
||||
SyncStates: syncStates,
|
||||
GetAliases: getAliases,
|
||||
ShouldFwdExpEndorsement: func() bool { return true },
|
||||
}
|
||||
|
||||
aliceLink := NewChannelLink(aliceCfg, aliceChannel)
|
||||
|
|
|
@ -1154,6 +1154,7 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer,
|
|||
return server.htlcSwitch.ForwardPackets(linkQuit, packets...)
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
link := NewChannelLink(
|
||||
ChannelLinkConfig{
|
||||
BestHeight: server.htlcSwitch.BestHeight,
|
||||
|
@ -1193,6 +1194,7 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer,
|
|||
NotifyInactiveLinkEvent: func(wire.OutPoint) {},
|
||||
HtlcNotifier: server.htlcSwitch.cfg.HtlcNotifier,
|
||||
GetAliases: getAliases,
|
||||
ShouldFwdExpEndorsement: func() bool { return true },
|
||||
},
|
||||
channel,
|
||||
)
|
||||
|
|
|
@ -706,4 +706,8 @@ var allTestCases = []*lntest.TestCase{
|
|||
Name: "debuglevel show",
|
||||
TestFunc: testDebuglevelShow,
|
||||
},
|
||||
{
|
||||
Name: "experimental endorsement",
|
||||
TestFunc: testExperimentalEndorsement,
|
||||
},
|
||||
}
|
||||
|
|
124
itest/lnd_experimental_endorsement.go
Normal file
124
itest/lnd_experimental_endorsement.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package itest
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/rpc"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testExperimentalEndorsement tests setting of positive and negative
|
||||
// experimental endorsement signals.
|
||||
func testExperimentalEndorsement(ht *lntest.HarnessTest) {
|
||||
testEndorsement(ht, true)
|
||||
testEndorsement(ht, false)
|
||||
}
|
||||
|
||||
// testEndorsement sets up a 5 hop network and tests propagation of
|
||||
// experimental endorsement signals.
|
||||
func testEndorsement(ht *lntest.HarnessTest, aliceEndorse bool) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
carol := ht.NewNode(
|
||||
"carol", []string{"--protocol.no-experimental-endorsement"},
|
||||
)
|
||||
dave := ht.NewNode("dave", nil)
|
||||
eve := ht.NewNode("eve", nil)
|
||||
|
||||
ht.EnsureConnected(alice, bob)
|
||||
ht.EnsureConnected(bob, carol)
|
||||
ht.EnsureConnected(carol, dave)
|
||||
ht.EnsureConnected(dave, eve)
|
||||
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
|
||||
// Open and wait for channels.
|
||||
const chanAmt = btcutil.Amount(300000)
|
||||
p := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
reqs := []*lntest.OpenChannelRequest{
|
||||
{Local: alice, Remote: bob, Param: p},
|
||||
{Local: bob, Remote: carol, Param: p},
|
||||
{Local: carol, Remote: dave, Param: p},
|
||||
{Local: dave, Remote: eve, Param: p},
|
||||
}
|
||||
resp := ht.OpenMultiChannelsAsync(reqs)
|
||||
cpAB, cpBC, cpCD, cpDE := resp[0], resp[1], resp[2], resp[3]
|
||||
|
||||
// Make sure Alice is aware of Bob=>Carol=>Dave=>Eve channels.
|
||||
ht.AssertChannelInGraph(alice, cpBC)
|
||||
ht.AssertChannelInGraph(alice, cpCD)
|
||||
ht.AssertChannelInGraph(alice, cpDE)
|
||||
|
||||
bobIntercept, cancelBob := bob.RPC.HtlcInterceptor()
|
||||
defer cancelBob()
|
||||
|
||||
carolIntercept, cancelCarol := carol.RPC.HtlcInterceptor()
|
||||
defer cancelCarol()
|
||||
|
||||
daveIntercept, cancelDave := dave.RPC.HtlcInterceptor()
|
||||
defer cancelDave()
|
||||
|
||||
req := &lnrpc.Invoice{ValueMsat: 1000}
|
||||
addResponse := eve.RPC.AddInvoice(req)
|
||||
invoice := eve.RPC.LookupInvoice(addResponse.RHash)
|
||||
|
||||
sendReq := &routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: invoice.PaymentRequest,
|
||||
TimeoutSeconds: int32(wait.PaymentTimeout.Seconds()),
|
||||
FeeLimitMsat: math.MaxInt64,
|
||||
}
|
||||
|
||||
expectedValue := []byte{lnwire.ExperimentalUnendorsed}
|
||||
if aliceEndorse {
|
||||
expectedValue = []byte{lnwire.ExperimentalEndorsed}
|
||||
t := uint64(lnwire.ExperimentalEndorsementType)
|
||||
sendReq.FirstHopCustomRecords = map[uint64][]byte{
|
||||
t: expectedValue,
|
||||
}
|
||||
}
|
||||
|
||||
_ = alice.RPC.SendPayment(sendReq)
|
||||
|
||||
// Validate that our signal (positive or zero) propagates until carol
|
||||
// and then is dropped because she has disabled the feature.
|
||||
validateEndorsedAndResume(ht, bobIntercept, true, expectedValue)
|
||||
validateEndorsedAndResume(ht, carolIntercept, true, expectedValue)
|
||||
validateEndorsedAndResume(ht, daveIntercept, false, nil)
|
||||
|
||||
var preimage lntypes.Preimage
|
||||
copy(preimage[:], invoice.RPreimage)
|
||||
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
||||
|
||||
ht.CloseChannel(alice, cpAB)
|
||||
ht.CloseChannel(bob, cpBC)
|
||||
ht.CloseChannel(carol, cpCD)
|
||||
ht.CloseChannel(dave, cpDE)
|
||||
}
|
||||
|
||||
func validateEndorsedAndResume(ht *lntest.HarnessTest,
|
||||
interceptor rpc.InterceptorClient, hasEndorsement bool,
|
||||
expectedValue []byte) {
|
||||
|
||||
packet := ht.ReceiveHtlcInterceptor(interceptor)
|
||||
|
||||
var expectedRecords map[uint64][]byte
|
||||
if hasEndorsement {
|
||||
u64Type := uint64(lnwire.ExperimentalEndorsementType)
|
||||
expectedRecords = map[uint64][]byte{
|
||||
u64Type: expectedValue,
|
||||
}
|
||||
}
|
||||
require.Equal(ht, expectedRecords, packet.InWireCustomRecords)
|
||||
|
||||
err := interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
||||
IncomingCircuitKey: packet.IncomingCircuitKey,
|
||||
Action: routerrpc.ResolveHoldForwardAction_RESUME,
|
||||
})
|
||||
require.NoError(ht, err)
|
||||
}
|
|
@ -515,12 +515,9 @@ func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) {
|
|||
// all intercepted packets. These packets are held to simulate a
|
||||
// pending payment.
|
||||
packet := ht.ReceiveHtlcInterceptor(bobInterceptor)
|
||||
|
||||
require.Len(ht, packet.InWireCustomRecords, 1)
|
||||
|
||||
val, ok := packet.InWireCustomRecords[65537]
|
||||
require.True(ht, ok, "expected custom record")
|
||||
require.Equal(ht, []byte("test"), val)
|
||||
require.Equal(ht, lntest.CustomRecordsWithUnendorsed(
|
||||
customRecords,
|
||||
), packet.InWireCustomRecords)
|
||||
|
||||
// Just resume the payment on Bob.
|
||||
err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
||||
|
@ -530,9 +527,10 @@ func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) {
|
|||
require.NoError(ht, err, "failed to send request")
|
||||
|
||||
// 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)
|
||||
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
|
||||
// set custom records on the HTLC as well, to make sure the HTLC isn't
|
||||
|
@ -568,7 +566,9 @@ func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) {
|
|||
func(p *lnrpc.Payment) error {
|
||||
recordsEqual := reflect.DeepEqual(
|
||||
p.FirstHopCustomRecords,
|
||||
sendReq.FirstHopCustomRecords,
|
||||
lntest.CustomRecordsWithUnendorsed(
|
||||
customRecords,
|
||||
),
|
||||
)
|
||||
if !recordsEqual {
|
||||
return fmt.Errorf("expected custom records to "+
|
||||
|
@ -642,9 +642,9 @@ func testForwardInterceptorRestart(ht *lntest.HarnessTest) {
|
|||
// all intercepted packets. These packets are held to simulate a
|
||||
// pending payment.
|
||||
packet := ht.ReceiveHtlcInterceptor(bobInterceptor)
|
||||
|
||||
require.Len(ht, packet.InWireCustomRecords, 1)
|
||||
require.Equal(ht, customRecords, packet.InWireCustomRecords)
|
||||
require.Equal(ht, lntest.CustomRecordsWithUnendorsed(
|
||||
customRecords,
|
||||
), packet.InWireCustomRecords)
|
||||
|
||||
// We accept the payment at Bob and resume it, so it gets to Carol.
|
||||
// This means the HTLC should now be fully locked in on Alice's side and
|
||||
|
@ -680,8 +680,9 @@ func testForwardInterceptorRestart(ht *lntest.HarnessTest) {
|
|||
// We should get another notification about the held HTLC.
|
||||
packet = ht.ReceiveHtlcInterceptor(bobInterceptor)
|
||||
|
||||
require.Len(ht, packet.InWireCustomRecords, 1)
|
||||
require.Equal(ht, customRecords, packet.InWireCustomRecords)
|
||||
require.Len(ht, packet.InWireCustomRecords, 2)
|
||||
require.Equal(ht, lntest.CustomRecordsWithUnendorsed(customRecords),
|
||||
packet.InWireCustomRecords)
|
||||
|
||||
err = carolInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
||||
IncomingCircuitKey: packet.IncomingCircuitKey,
|
||||
|
@ -689,9 +690,10 @@ func testForwardInterceptorRestart(ht *lntest.HarnessTest) {
|
|||
})
|
||||
require.NoError(ht, err, "failed to send request")
|
||||
|
||||
// And now we forward the payment at Carol.
|
||||
// And now we forward the payment at Carol, expecting only an
|
||||
// endorsement signal in our incoming custom records.
|
||||
packet = ht.ReceiveHtlcInterceptor(carolInterceptor)
|
||||
require.Len(ht, packet.InWireCustomRecords, 0)
|
||||
require.Len(ht, packet.InWireCustomRecords, 1)
|
||||
err = carolInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
||||
IncomingCircuitKey: packet.IncomingCircuitKey,
|
||||
Action: actionResume,
|
||||
|
@ -703,8 +705,9 @@ func testForwardInterceptorRestart(ht *lntest.HarnessTest) {
|
|||
alice, preimage, lnrpc.Payment_SUCCEEDED,
|
||||
func(p *lnrpc.Payment) error {
|
||||
recordsEqual := reflect.DeepEqual(
|
||||
p.FirstHopCustomRecords,
|
||||
sendReq.FirstHopCustomRecords,
|
||||
lntest.CustomRecordsWithUnendorsed(
|
||||
sendReq.FirstHopCustomRecords,
|
||||
), p.FirstHopCustomRecords,
|
||||
)
|
||||
if !recordsEqual {
|
||||
return fmt.Errorf("expected custom records to "+
|
||||
|
|
|
@ -102,9 +102,12 @@ func testInvoiceHtlcModifierBasic(ht *lntest.HarnessTest) {
|
|||
require.EqualValues(
|
||||
ht, tc.sendAmountMsat, modifierRequest.ExitHtlcAmt,
|
||||
)
|
||||
|
||||
// Expect custom records plus endorsement signal.
|
||||
require.Equal(
|
||||
ht, tc.lastHopCustomRecords,
|
||||
modifierRequest.ExitHtlcWireCustomRecords,
|
||||
ht, lntest.CustomRecordsWithUnendorsed(
|
||||
tc.lastHopCustomRecords,
|
||||
), modifierRequest.ExitHtlcWireCustomRecords,
|
||||
)
|
||||
|
||||
// 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.Equal(
|
||||
ht, tc.lastHopCustomRecords,
|
||||
updatedInvoice.Htlcs[0].CustomRecords,
|
||||
ht, lntest.CustomRecordsWithUnendorsed(
|
||||
tc.lastHopCustomRecords,
|
||||
), updatedInvoice.Htlcs[0].CustomRecords,
|
||||
)
|
||||
|
||||
// Make sure the custom channel data contains the encoded
|
||||
|
|
|
@ -67,6 +67,9 @@ type ProtocolOptions struct {
|
|||
// NoRouteBlindingOption disables forwarding of payments in blinded routes.
|
||||
NoRouteBlindingOption bool `long:"no-route-blinding" description:"do not forward payments that are a part of a blinded route"`
|
||||
|
||||
// NoExperimentalEndorsementOption disables experimental endorsement.
|
||||
NoExperimentalEndorsementOption bool `long:"no-experimental-endorsement" description:"do not forward experimental endorsement signals"`
|
||||
|
||||
// CustomMessage allows the custom message APIs to handle messages with
|
||||
// the provided protocol numbers, which fall outside the custom message
|
||||
// number range.
|
||||
|
@ -132,6 +135,12 @@ func (l *ProtocolOptions) NoRouteBlinding() bool {
|
|||
return l.NoRouteBlindingOption
|
||||
}
|
||||
|
||||
// NoExperimentalEndorsement returns true if experimental endorsement should
|
||||
// be disabled.
|
||||
func (l *ProtocolOptions) NoExperimentalEndorsement() bool {
|
||||
return l.NoExperimentalEndorsementOption
|
||||
}
|
||||
|
||||
// CustomMessageOverrides returns the set of protocol messages that we override
|
||||
// to allow custom handling.
|
||||
func (p ProtocolOptions) CustomMessageOverrides() []uint16 {
|
||||
|
|
|
@ -70,6 +70,9 @@ type ProtocolOptions struct {
|
|||
// NoRouteBlindingOption disables forwarding of payments in blinded routes.
|
||||
NoRouteBlindingOption bool `long:"no-route-blinding" description:"do not forward payments that are a part of a blinded route"`
|
||||
|
||||
// NoExperimentalEndorsementOption disables experimental endorsement.
|
||||
NoExperimentalEndorsementOption bool `long:"no-experimental-endorsement" description:"do not forward experimental endorsement signals"`
|
||||
|
||||
// CustomMessage allows the custom message APIs to handle messages with
|
||||
// the provided protocol numbers, which fall outside the custom message
|
||||
// number range.
|
||||
|
@ -127,6 +130,12 @@ func (l *ProtocolOptions) NoRouteBlinding() bool {
|
|||
return l.NoRouteBlindingOption
|
||||
}
|
||||
|
||||
// NoExperimentalEndorsement returns true if experimental endorsement should
|
||||
// be disabled.
|
||||
func (l *ProtocolOptions) NoExperimentalEndorsement() bool {
|
||||
return l.NoExperimentalEndorsementOption
|
||||
}
|
||||
|
||||
// CustomMessageOverrides returns the set of protocol messages that we override
|
||||
// to allow custom handling.
|
||||
func (l ProtocolOptions) CustomMessageOverrides() []uint16 {
|
||||
|
|
|
@ -110,6 +110,10 @@ type RouterBackend struct {
|
|||
// ParseCustomChannelData is a function that can be used to parse custom
|
||||
// channel data from the first hop of a route.
|
||||
ParseCustomChannelData func(message proto.Message) error
|
||||
|
||||
// ShouldSetExpEndorsement returns a boolean indicating whether the
|
||||
// experimental endorsement bit should be set.
|
||||
ShouldSetExpEndorsement func() bool
|
||||
}
|
||||
|
||||
// MissionControl defines the mission control dependencies of routerrpc.
|
||||
|
@ -891,6 +895,23 @@ func (r *RouterBackend) extractIntentFromSendRequest(
|
|||
}
|
||||
payIntent.FirstHopCustomRecords = firstHopRecords
|
||||
|
||||
// If the experimental endorsement signal is not already set, propagate
|
||||
// a zero value field if configured to set this signal.
|
||||
if r.ShouldSetExpEndorsement() {
|
||||
if payIntent.FirstHopCustomRecords == nil {
|
||||
payIntent.FirstHopCustomRecords = make(
|
||||
map[uint64][]byte,
|
||||
)
|
||||
}
|
||||
|
||||
t := uint64(lnwire.ExperimentalEndorsementType)
|
||||
if _, set := payIntent.FirstHopCustomRecords[t]; !set {
|
||||
payIntent.FirstHopCustomRecords[t] = []byte{
|
||||
lnwire.ExperimentalUnendorsed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
payIntent.PayAttemptTimeout = time.Second *
|
||||
time.Duration(rpcPayReq.TimeoutSeconds)
|
||||
|
||||
|
|
|
@ -282,3 +282,15 @@ func CalcStaticFeeBuffer(c lnrpc.CommitmentType, numHTLCs int) btcutil.Amount {
|
|||
|
||||
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,
|
||||
}},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -263,6 +263,14 @@ const (
|
|||
// being finalized.
|
||||
SimpleTaprootChannelsOptionalStaging = 181
|
||||
|
||||
// ExperimentalEndorsementRequired is a required feature bit that
|
||||
// indicates that the node will relay experimental endorsement signals.
|
||||
ExperimentalEndorsementRequired FeatureBit = 260
|
||||
|
||||
// ExperimentalEndorsementOptional is an optional feature bit that
|
||||
// indicates that the node will relay experimental endorsement signals.
|
||||
ExperimentalEndorsementOptional FeatureBit = 261
|
||||
|
||||
// Bolt11BlindedPathsRequired is a required feature bit that indicates
|
||||
// that the node is able to understand the blinded path tagged field in
|
||||
// a BOLT 11 invoice.
|
||||
|
@ -349,6 +357,8 @@ var Features = map[FeatureBit]string{
|
|||
SimpleTaprootChannelsOptionalStaging: "simple-taproot-chans-x",
|
||||
SimpleTaprootOverlayChansOptional: "taproot-overlay-chans",
|
||||
SimpleTaprootOverlayChansRequired: "taproot-overlay-chans",
|
||||
ExperimentalEndorsementRequired: "endorsement-x",
|
||||
ExperimentalEndorsementOptional: "endorsement-x",
|
||||
Bolt11BlindedPathsOptional: "bolt-11-blinded-paths",
|
||||
Bolt11BlindedPathsRequired: "bolt-11-blinded-paths",
|
||||
}
|
||||
|
|
|
@ -8,11 +8,29 @@ import (
|
|||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// OnionPacketSize is the size of the serialized Sphinx onion packet included
|
||||
// in each UpdateAddHTLC message. The breakdown of the onion packet is as
|
||||
// follows: 1-byte version, 33-byte ephemeral public key (for ECDH), 1300-bytes
|
||||
// of per-hop data, and a 32-byte HMAC over the entire packet.
|
||||
const OnionPacketSize = 1366
|
||||
const (
|
||||
// OnionPacketSize is the size of the serialized Sphinx onion packet
|
||||
// included in each UpdateAddHTLC message. The breakdown of the onion
|
||||
// packet is as follows: 1-byte version, 33-byte ephemeral public key
|
||||
// (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 (
|
||||
// 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.
|
||||
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 chan struct{}
|
||||
}
|
||||
|
@ -1319,6 +1323,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
|
|||
PreviouslySentShutdown: shutdownMsg,
|
||||
DisallowRouteBlinding: p.cfg.DisallowRouteBlinding,
|
||||
MaxFeeExposure: p.cfg.MaxFeeExposure,
|
||||
ShouldFwdExpEndorsement: p.cfg.ShouldFwdExpEndorsement,
|
||||
}
|
||||
|
||||
// Before adding our new link, purge the switch of any pending or live
|
||||
|
|
10
rpcserver.go
10
rpcserver.go
|
@ -44,6 +44,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/channeldb/graphsession"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/contractcourt"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
"github.com/lightningnetwork/lnd/feature"
|
||||
|
@ -758,6 +759,15 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
|
|||
|
||||
return nil
|
||||
},
|
||||
ShouldSetExpEndorsement: func() bool {
|
||||
if s.cfg.ProtocolOptions.NoExperimentalEndorsement() {
|
||||
return false
|
||||
}
|
||||
|
||||
return clock.NewDefaultClock().Now().Before(
|
||||
EndorsementExperimentEnd,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
genInvoiceFeatures := func() *lnwire.FeatureVector {
|
||||
|
|
|
@ -1399,6 +1399,9 @@
|
|||
; Set to disable blinded route forwarding.
|
||||
; protocol.no-route-blinding=false
|
||||
|
||||
; Set to disable experimental endorsement signaling.
|
||||
; protocol.no-experimental-endorsement=false
|
||||
|
||||
; Set to handle messages of a particular type that falls outside of the
|
||||
; custom message number range (i.e. 513 is onion messages). Note that you can
|
||||
; set this option as many times as you want to support more than one custom
|
||||
|
|
42
server.go
42
server.go
|
@ -133,6 +133,12 @@ var (
|
|||
//
|
||||
// TODO(roasbeef): add command line param to modify.
|
||||
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
|
||||
|
@ -567,19 +573,20 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
|
||||
//nolint:lll
|
||||
featureMgr, err := feature.NewManager(feature.Config{
|
||||
NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(),
|
||||
NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(),
|
||||
NoAnchors: cfg.ProtocolOptions.NoAnchorCommitments(),
|
||||
NoWumbo: !cfg.ProtocolOptions.Wumbo(),
|
||||
NoScriptEnforcementLease: cfg.ProtocolOptions.NoScriptEnforcementLease(),
|
||||
NoKeysend: !cfg.AcceptKeySend,
|
||||
NoOptionScidAlias: !cfg.ProtocolOptions.ScidAlias(),
|
||||
NoZeroConf: !cfg.ProtocolOptions.ZeroConf(),
|
||||
NoAnySegwit: cfg.ProtocolOptions.NoAnySegwit(),
|
||||
CustomFeatures: cfg.ProtocolOptions.CustomFeatures(),
|
||||
NoTaprootChans: !cfg.ProtocolOptions.TaprootChans,
|
||||
NoTaprootOverlay: !cfg.ProtocolOptions.TaprootOverlayChans,
|
||||
NoRouteBlinding: cfg.ProtocolOptions.NoRouteBlinding(),
|
||||
NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(),
|
||||
NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(),
|
||||
NoAnchors: cfg.ProtocolOptions.NoAnchorCommitments(),
|
||||
NoWumbo: !cfg.ProtocolOptions.Wumbo(),
|
||||
NoScriptEnforcementLease: cfg.ProtocolOptions.NoScriptEnforcementLease(),
|
||||
NoKeysend: !cfg.AcceptKeySend,
|
||||
NoOptionScidAlias: !cfg.ProtocolOptions.ScidAlias(),
|
||||
NoZeroConf: !cfg.ProtocolOptions.ZeroConf(),
|
||||
NoAnySegwit: cfg.ProtocolOptions.NoAnySegwit(),
|
||||
CustomFeatures: cfg.ProtocolOptions.CustomFeatures(),
|
||||
NoTaprootChans: !cfg.ProtocolOptions.TaprootChans,
|
||||
NoTaprootOverlay: !cfg.ProtocolOptions.TaprootOverlayChans,
|
||||
NoRouteBlinding: cfg.ProtocolOptions.NoRouteBlinding(),
|
||||
NoExperimentalEndorsement: cfg.ProtocolOptions.NoExperimentalEndorsement(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -4214,6 +4221,15 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
|||
MsgRouter: s.implCfg.MsgRouter,
|
||||
AuxChanCloser: s.implCfg.AuxChanCloser,
|
||||
AuxResolver: s.implCfg.AuxContractResolver,
|
||||
ShouldFwdExpEndorsement: func() bool {
|
||||
if s.cfg.ProtocolOptions.NoExperimentalEndorsement() {
|
||||
return false
|
||||
}
|
||||
|
||||
return clock.NewDefaultClock().Now().Before(
|
||||
EndorsementExperimentEnd,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
copy(pCfg.PubKeyBytes[:], peerAddr.IdentityKey.SerializeCompressed())
|
||||
|
|
Loading…
Add table
Reference in a new issue