Skip to content

Commit

Permalink
Merge pull request #8390 from carlaKC/7883-experimental-endorsement
Browse files Browse the repository at this point in the history
Add Experimental Endorsement Signalling
  • Loading branch information
ellemouton authored Nov 26, 2024
2 parents 6694002 + a8c159b commit fbeab72
Show file tree
Hide file tree
Showing 20 changed files with 380 additions and 40 deletions.
7 changes: 7 additions & 0 deletions docs/release-notes/release-notes-0.19.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions feature/default_sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,7 @@ var defaultSetDesc = setDesc{
SetInit: {}, // I
SetNodeAnn: {}, // N
},
lnwire.ExperimentalEndorsementOptional: {
SetNodeAnn: {}, // N
},
}
10 changes: 10 additions & 0 deletions feature/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 "+
Expand Down
66 changes: 66 additions & 0 deletions htlcswitch/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions htlcswitch/link_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions htlcswitch/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)
Expand Down
4 changes: 4 additions & 0 deletions itest/list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -706,4 +706,8 @@ var allTestCases = []*lntest.TestCase{
Name: "debuglevel show",
TestFunc: testDebuglevelShow,
},
{
Name: "experimental endorsement",
TestFunc: testExperimentalEndorsement,
},
}
124 changes: 124 additions & 0 deletions itest/lnd_experimental_endorsement.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit fbeab72

Please sign in to comment.