multi: introduce new traffic shaper method.

We introduce a new specific fail resolution error when the
external HTLC interceptor denies the incoming HTLC. Moreover
we introduce a new traffic shaper method which moves the
implementation of asset HTLC to the external layers.
Moreover itests are adopted to reflect this new change.
This commit is contained in:
ziggie committed 2025-01-29 09:59:02 +01:00
1 parent 9ee12ee029
commit 17e37bd7c2
8 files changed
+37 -248

No files matched your search

-8
View File
@@ -406,14 +406,6 @@ var allTestCases = []*lntest.TestCase{
Name: "forward interceptor",
TestFunc: testForwardInterceptorBasic,
},
{
Name: "forward interceptor modified htlc",
TestFunc: testForwardInterceptorModifiedHtlc,
},
{
Name: "forward interceptor wire records",
TestFunc: testForwardInterceptorWireRecords,
},
{
Name: "forward interceptor restart",
TestFunc: testForwardInterceptorRestart,
-218
View File
@@ -10,7 +10,6 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
@@ -346,223 +345,6 @@ func testForwardInterceptorBasic(ht *lntest.HarnessTest) {
}
}
// testForwardInterceptorModifiedHtlc tests that the interceptor can modify the
// amount and custom records of an intercepted HTLC and resume it.
func testForwardInterceptorModifiedHtlc(ht *lntest.HarnessTest) {
const chanAmt = btcutil.Amount(300000)
p := lntest.OpenChannelParams{Amt: chanAmt}
// Initialize the test context with 3 connected nodes.
cfgs := [][]string{nil, nil, nil}
// Open and wait for channels.
_, nodes := ht.CreateSimpleNetwork(cfgs, p)
alice, bob, carol := nodes[0], nodes[1], nodes[2]
// Init the scenario.
ts := &interceptorTestScenario{
ht: ht,
alice: alice,
bob: bob,
carol: carol,
}
// Connect an interceptor to Bob's node.
bobInterceptor, cancelBobInterceptor := bob.RPC.HtlcInterceptor()
// We're going to modify the payment amount and want Carol to accept the
// payment, so we set up an invoice acceptor on Dave.
carolAcceptor, carolCancel := carol.RPC.InvoiceHtlcModifier()
defer carolCancel()
// Prepare the test cases.
invoiceValueAmtMsat := int64(20_000_000)
req := &lnrpc.Invoice{ValueMsat: invoiceValueAmtMsat}
addResponse := carol.RPC.AddInvoice(req)
invoice := carol.RPC.LookupInvoice(addResponse.RHash)
tc := &interceptorTestCase{
amountMsat: invoiceValueAmtMsat,
invoice: invoice,
payAddr: invoice.PaymentAddr,
}
// We initiate a payment from Alice.
done := make(chan struct{})
go func() {
// Signal that all the payments have been sent.
defer close(done)
ts.sendPaymentAndAssertAction(tc)
}()
// We start the htlc interceptor with a simple implementation that saves
// all intercepted packets. These packets are held to simulate a
// pending payment.
packet := ht.ReceiveHtlcInterceptor(bobInterceptor)
// Resume the intercepted HTLC with a modified amount and custom
// records.
customRecords := make(map[uint64][]byte)
// Add custom records entry.
crKey := uint64(65537)
crValue := []byte("custom-records-test-value")
customRecords[crKey] = crValue
// Modify the amount of the HTLC, so we send out less than the original
// amount.
const modifyAmount = 5_000_000
newOutAmountMsat := packet.OutgoingAmountMsat - modifyAmount
err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: packet.IncomingCircuitKey,
OutAmountMsat: newOutAmountMsat,
OutWireCustomRecords: customRecords,
Action: actionResumeModify,
})
require.NoError(ht, err, "failed to send request")
invoicePacket := ht.ReceiveInvoiceHtlcModification(carolAcceptor)
require.EqualValues(
ht, newOutAmountMsat, invoicePacket.ExitHtlcAmt,
)
amtPaid := newOutAmountMsat + modifyAmount
err = carolAcceptor.Send(&invoicesrpc.HtlcModifyResponse{
CircuitKey: invoicePacket.ExitHtlcCircuitKey,
AmtPaid: &amtPaid,
})
require.NoError(ht, err, "carol acceptor response")
// Cancel the context, which will disconnect Bob's interceptor.
cancelBobInterceptor()
// Make sure all goroutines are finished.
select {
case <-done:
case <-time.After(defaultTimeout):
require.Fail(ht, "timeout waiting for sending payment")
}
// Assert that the payment was successful.
var preimage lntypes.Preimage
copy(preimage[:], invoice.RPreimage)
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
}
// testForwardInterceptorWireRecords tests that the interceptor can read any
// wire custom records provided by the sender of a payment as part of the
// update_add_htlc message.
func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) {
const chanAmt = btcutil.Amount(300000)
p := lntest.OpenChannelParams{Amt: chanAmt}
// Initialize the test context with 4 connected nodes.
cfgs := [][]string{nil, nil, nil, nil}
// Open and wait for channels.
_, nodes := ht.CreateSimpleNetwork(cfgs, p)
alice, bob, carol, dave := nodes[0], nodes[1], nodes[2], nodes[3]
// Connect an interceptor to Bob's node.
bobInterceptor, cancelBobInterceptor := bob.RPC.HtlcInterceptor()
defer cancelBobInterceptor()
// Also connect an interceptor on Carol's node to check whether we're
// relaying the TLVs send in update_add_htlc over Alice -> Bob on the
// Bob -> Carol link.
carolInterceptor, cancelCarolInterceptor := carol.RPC.HtlcInterceptor()
defer cancelCarolInterceptor()
// We're going to modify the payment amount and want Dave to accept the
// payment, so we set up an invoice acceptor on Dave.
daveAcceptor, daveCancel := dave.RPC.InvoiceHtlcModifier()
defer daveCancel()
req := &lnrpc.Invoice{ValueMsat: 20_000_000}
addResponse := dave.RPC.AddInvoice(req)
invoice := dave.RPC.LookupInvoice(addResponse.RHash)
customRecords := map[uint64][]byte{
65537: []byte("test"),
}
sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: invoice.PaymentRequest,
TimeoutSeconds: int32(wait.PaymentTimeout.Seconds()),
FeeLimitMsat: noFeeLimitMsat,
FirstHopCustomRecords: customRecords,
}
ht.SendPaymentAssertInflight(alice, sendReq)
// We start the htlc interceptor with a simple implementation that saves
// all intercepted packets. These packets are held to simulate a
// pending payment.
packet := ht.ReceiveHtlcInterceptor(bobInterceptor)
require.Equal(ht, lntest.CustomRecordsWithUnendorsed(
customRecords,
), packet.InWireCustomRecords)
// Just resume the payment on Bob.
err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: packet.IncomingCircuitKey,
Action: actionResume,
})
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, just an endorsement signal.
packet = ht.ReceiveHtlcInterceptor(carolInterceptor)
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
// rejected outright and actually gets to the invoice acceptor.
const modifyAmount = 5_000_000
newOutAmountMsat := packet.OutgoingAmountMsat - modifyAmount
err = carolInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: packet.IncomingCircuitKey,
OutAmountMsat: newOutAmountMsat,
OutWireCustomRecords: customRecords,
Action: actionResumeModify,
})
require.NoError(ht, err, "carol interceptor response")
// The payment should get to Dave, and we should be able to intercept
// and modify it, telling Dave to accept it.
invoicePacket := ht.ReceiveInvoiceHtlcModification(daveAcceptor)
require.EqualValues(
ht, newOutAmountMsat, invoicePacket.ExitHtlcAmt,
)
amtPaid := newOutAmountMsat + modifyAmount
err = daveAcceptor.Send(&invoicesrpc.HtlcModifyResponse{
CircuitKey: invoicePacket.ExitHtlcCircuitKey,
AmtPaid: &amtPaid,
})
require.NoError(ht, err, "dave acceptor response")
// Assert that the payment was successful.
var preimage lntypes.Preimage
copy(preimage[:], invoice.RPreimage)
ht.AssertPaymentStatus(
alice, preimage, lnrpc.Payment_SUCCEEDED,
func(p *lnrpc.Payment) error {
recordsEqual := reflect.DeepEqual(
p.FirstHopCustomRecords,
lntest.CustomRecordsWithUnendorsed(
customRecords,
),
)
if !recordsEqual {
return fmt.Errorf("expected custom records to "+
"be equal, got %v expected %v",
p.FirstHopCustomRecords,
sendReq.FirstHopCustomRecords)
}
return nil
},
)
}
// testForwardInterceptorRestart tests that the interceptor can read any wire
// custom records provided by the sender of a payment as part of the
// update_add_htlc message and that those records are persisted correctly and