diff --git a/cln/cln_interceptor.go b/cln/cln_interceptor.go index 71c4cc0..0efa6a8 100644 --- a/cln/cln_interceptor.go +++ b/cln/cln_interceptor.go @@ -211,7 +211,7 @@ func (i *ClnHtlcInterceptor) resumeWithOnion(request *proto.HtlcAccepted, interc log.Printf("resumeWithOnion: hex.DecodeString(%v) error: %v", request.Onion.Payload, err) return i.failWithCode(request, interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE) } - newPayload, err := encodePayloadWithNextHop(payload, interceptResult.ChannelId) + newPayload, err := encodePayloadWithNextHop(payload, interceptResult.ChannelId, interceptResult.AmountMsat) if err != nil { log.Printf("encodePayloadWithNextHop error: %v", err) return i.failWithCode(request, interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE) @@ -254,7 +254,7 @@ func (i *ClnHtlcInterceptor) failWithCode(request *proto.HtlcAccepted, code inte } } -func encodePayloadWithNextHop(payload []byte, channelId uint64) ([]byte, error) { +func encodePayloadWithNextHop(payload []byte, channelId uint64, amountToForward uint64) ([]byte, error) { bufReader := bytes.NewBuffer(payload) var b [8]byte varInt, err := sphinx.ReadVarInt(bufReader, &b) @@ -274,15 +274,26 @@ func encodePayloadWithNextHop(payload []byte, channelId uint64) ([]byte, error) } tt := record.NewNextHopIDRecord(&channelId) - buf := bytes.NewBuffer([]byte{}) - if err := tt.Encode(buf); err != nil { + ttbuf := bytes.NewBuffer([]byte{}) + if err := tt.Encode(ttbuf); err != nil { return nil, fmt.Errorf("failed to encode nexthop %x: %v", innerPayload[:], err) } + amt := record.NewAmtToFwdRecord(&amountToForward) + amtbuf := bytes.NewBuffer([]byte{}) + if err := amt.Encode(amtbuf); err != nil { + return nil, fmt.Errorf("failed to encode AmtToFwd %x: %v", innerPayload[:], err) + } + uTlvMap := make(map[uint64][]byte) for t, b := range tlvMap { if t == record.NextHopOnionType { - uTlvMap[uint64(t)] = buf.Bytes() + uTlvMap[uint64(t)] = ttbuf.Bytes() + continue + } + + if t == record.AmtOnionType { + uTlvMap[uint64(t)] = amtbuf.Bytes() continue } uTlvMap[uint64(t)] = b diff --git a/itest/bob_offline_test.go b/itest/bob_offline_test.go index bd7d505..45551cb 100644 --- a/itest/bob_offline_test.go +++ b/itest/bob_offline_test.go @@ -23,7 +23,7 @@ func testFailureBobOffline(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -33,6 +33,7 @@ func testFailureBobOffline(p *testParams) { lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -42,7 +43,7 @@ func testFailureBobOffline(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // Kill the mobile client diff --git a/itest/breez_client.go b/itest/breez_client.go index a510959..7b96ca2 100644 --- a/itest/breez_client.go +++ b/itest/breez_client.go @@ -18,6 +18,7 @@ type BreezClient interface { Node() lntest.LightningNode Start() Stop() error + SetHtlcAcceptor(totalMsat uint64) } type generateInvoicesRequest struct { diff --git a/itest/cln_breez_client.go b/itest/cln_breez_client.go index ebd3afe..7cf1911 100644 --- a/itest/cln_breez_client.go +++ b/itest/cln_breez_client.go @@ -2,12 +2,26 @@ package itest import ( "bufio" + "bytes" + "context" + "encoding/hex" "fmt" + "io" + "log" "os" "path/filepath" "sync" + "time" "github.com/breez/lntest" + "github.com/breez/lspd/cln_plugin/proto" + sphinx "github.com/lightningnetwork/lightning-onion" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/tlv" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/keepalive" ) var pluginContent string = `#!/usr/bin/env python3 @@ -37,23 +51,34 @@ python %s ` type clnBreezClient struct { - name string - scriptDir string - pluginFilePath string - harness *lntest.TestHarness - isInitialized bool - node *lntest.ClnNode - mtx sync.Mutex + name string + scriptDir string + pluginFilePath string + htlcAcceptorAddress string + htlcAcceptor func(*proto.HtlcAccepted) *proto.HtlcResolution + htlcAcceptorCancel context.CancelFunc + harness *lntest.TestHarness + isInitialized bool + node *lntest.ClnNode + mtx sync.Mutex } func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) BreezClient { scriptDir := h.GetDirectory(name) pluginFilePath := filepath.Join(scriptDir, "start_zero_conf_plugin.sh") + htlcAcceptorPort, err := lntest.GetPort() + if err != nil { + h.T.Fatalf("Could not get port for htlc acceptor plugin: %v", err) + } + + htlcAcceptorAddress := fmt.Sprintf("127.0.0.1:%v", htlcAcceptorPort) node := lntest.NewClnNode( h, m, name, fmt.Sprintf("--plugin=%s", pluginFilePath), + fmt.Sprintf("--plugin=%s", *clnPluginExec), + fmt.Sprintf("--lsp.listen=%s", htlcAcceptorAddress), // NOTE: max-concurrent-htlcs is 30 on mainnet by default. In cln V22.11 // there is a check for 'all dust' commitment transactions. The max // concurrent HTLCs of both sides of the channel * dust limit must be @@ -64,11 +89,12 @@ func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) Bree ) return &clnBreezClient{ - name: name, - harness: h, - node: node, - scriptDir: scriptDir, - pluginFilePath: pluginFilePath, + name: name, + harness: h, + node: node, + scriptDir: scriptDir, + pluginFilePath: pluginFilePath, + htlcAcceptorAddress: htlcAcceptorAddress, } } @@ -94,6 +120,151 @@ func (c *clnBreezClient) Start() { } c.node.Start() + c.startHtlcAcceptor() +} + +func (c *clnBreezClient) SetHtlcAcceptor(totalMsat uint64) { + c.htlcAcceptor = func(htlc *proto.HtlcAccepted) *proto.HtlcResolution { + origPayload, err := hex.DecodeString(htlc.Onion.Payload) + if err != nil { + c.harness.T.Fatalf("failed to hex decode onion payload %s: %v", htlc.Onion.Payload, err) + } + bufReader := bytes.NewBuffer(origPayload) + var b [8]byte + varInt, err := sphinx.ReadVarInt(bufReader, &b) + if err != nil { + c.harness.T.Fatalf("Failed to read payload: %v", err) + } + + innerPayload := make([]byte, varInt) + if _, err := io.ReadFull(bufReader, innerPayload[:]); err != nil { + c.harness.T.Fatalf("failed to decode payload %x: %v", innerPayload[:], err) + } + + s, _ := tlv.NewStream() + tlvMap, err := s.DecodeWithParsedTypes(bytes.NewReader(innerPayload)) + if err != nil { + c.harness.T.Fatalf("DecodeWithParsedTypes failed for %x: %v", innerPayload[:], err) + } + + amt := record.NewAmtToFwdRecord(&htlc.Htlc.AmountMsat) + amtbuf := bytes.NewBuffer([]byte{}) + if err := amt.Encode(amtbuf); err != nil { + c.harness.T.Fatalf("failed to encode AmtToFwd %x: %v", innerPayload[:], err) + } + + uTlvMap := make(map[uint64][]byte) + for t, b := range tlvMap { + if t == record.AmtOnionType { + uTlvMap[uint64(t)] = amtbuf.Bytes() + continue + } + + if t == record.MPPOnionType { + addr := [32]byte{} + copy(addr[:], b[:32]) + mppbuf := bytes.NewBuffer([]byte{}) + mpp := record.NewMPP( + lnwire.MilliSatoshi(totalMsat), + addr, + ) + record := mpp.Record() + record.Encode(mppbuf) + uTlvMap[uint64(t)] = mppbuf.Bytes() + continue + } + + uTlvMap[uint64(t)] = b + } + tlvRecords := tlv.MapToRecords(uTlvMap) + s, err = tlv.NewStream(tlvRecords...) + if err != nil { + c.harness.T.Fatalf("tlv.NewStream(%+v) error: %v", tlvRecords, err) + } + var newPayloadBuf bytes.Buffer + err = s.Encode(&newPayloadBuf) + if err != nil { + c.harness.T.Fatalf("encode error: %v", err) + } + payload := hex.EncodeToString(newPayloadBuf.Bytes()) + + return &proto.HtlcResolution{ + Correlationid: htlc.Correlationid, + Outcome: &proto.HtlcResolution_Continue{ + Continue: &proto.HtlcContinue{ + Payload: &payload, + }, + }, + } + } +} + +func (c *clnBreezClient) startHtlcAcceptor() { + ctx, cancel := context.WithCancel(c.harness.Ctx) + c.htlcAcceptorCancel = cancel + + go func() { + for { + if ctx.Err() != nil { + return + } + + select { + case <-ctx.Done(): + return + case <-time.After(time.Second): + } + + conn, err := grpc.DialContext( + ctx, + c.htlcAcceptorAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: time.Duration(10) * time.Second, + Timeout: time.Duration(10) * time.Second, + }), + ) + if err != nil { + log.Printf("%s: Dial htlc acceptor error: %v", c.name, err) + continue + } + + client := proto.NewClnPluginClient(conn) + for { + acceptor, err := client.HtlcStream(ctx) + if err != nil { + log.Printf("%s: client.HtlcStream() error: %v", c.name, err) + break + } + + htlc, err := acceptor.Recv() + if err != nil { + log.Printf("%s: acceptor.Recv() error: %v", c.name, err) + break + } + + f := c.htlcAcceptor + var resp *proto.HtlcResolution + if f != nil { + resp = f(htlc) + } + if resp == nil { + resp = &proto.HtlcResolution{ + Correlationid: htlc.Correlationid, + Outcome: &proto.HtlcResolution_Continue{ + Continue: &proto.HtlcContinue{}, + }, + } + } + + err = acceptor.Send(resp) + if err != nil { + log.Printf("%s: acceptor.Send() error: %v", c.name, err) + break + } + } + } + }() } func (c *clnBreezClient) initialize() error { @@ -146,10 +317,17 @@ func (c *clnBreezClient) initialize() error { lntest.PerformCleanup(cleanups) return fmt.Errorf("failed to flush plugin file '%s': %v", c.pluginFilePath, err) } + lntest.PerformCleanup(cleanups) return nil } func (c *clnBreezClient) Stop() error { + c.mtx.Lock() + defer c.mtx.Unlock() + if c.htlcAcceptorCancel != nil { + c.htlcAcceptorCancel() + c.htlcAcceptorCancel = nil + } return c.node.Stop() } diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index be3d67a..1697e26 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -214,9 +214,6 @@ func (l *ClnLspNode) LightningNode() lntest.LightningNode { return l.lightningNode } -func (l *ClnLspNode) SupportsChargingFees() bool { - return false -} func (l *ClnLspNode) PostgresBackend() *PostgresContainer { return l.lspBase.postgresBackend } diff --git a/itest/cltv_test.go b/itest/cltv_test.go index 7bc9eaf..75a9f47 100644 --- a/itest/cltv_test.go +++ b/itest/cltv_test.go @@ -23,7 +23,7 @@ func testInvalidCltv(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -42,7 +42,7 @@ func testInvalidCltv(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index 0aec482..483840f 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -25,7 +25,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -34,6 +34,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { description: description, lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -44,7 +45,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. @@ -78,7 +79,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -88,6 +89,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -97,7 +99,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/lnd_breez_client.go b/itest/lnd_breez_client.go index 6da835a..66fe7fb 100644 --- a/itest/lnd_breez_client.go +++ b/itest/lnd_breez_client.go @@ -78,6 +78,10 @@ func (c *lndBreezClient) Stop() error { return c.node.Stop() } +func (c *lndBreezClient) SetHtlcAcceptor(totalMsat uint64) { + // No need for a htlc acceptor in the LND breez client +} + func (c *lndBreezClient) startChannelAcceptor(ctx context.Context) error { client, err := c.node.LightningClient().ChannelAcceptor(ctx) if err != nil { diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index e8375df..9f7e0ce 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -230,10 +230,6 @@ func (c *LndLspNode) Rpc() lspd.ChannelOpenerClient { return c.runtime.rpc } -func (l *LndLspNode) SupportsChargingFees() bool { - return true -} - func (l *LndLspNode) NodeId() []byte { return l.lightningNode.NodeId() } diff --git a/itest/lspd_node.go b/itest/lspd_node.go index b8c6186..bfeaf10 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -45,7 +45,6 @@ type LspNode interface { Rpc() lspd.ChannelOpenerClient NodeId() []byte LightningNode() lntest.LightningNode - SupportsChargingFees() bool PostgresBackend() *PostgresContainer } diff --git a/itest/no_balance_test.go b/itest/no_balance_test.go index 251f753..1c93c9d 100644 --- a/itest/no_balance_test.go +++ b/itest/no_balance_test.go @@ -22,7 +22,7 @@ func testNoBalance(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -31,6 +31,7 @@ func testNoBalance(p *testParams) { description: description, lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -41,7 +42,7 @@ func testNoBalance(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/probing_test.go b/itest/probing_test.go index 2fddbcd..15a44b7 100644 --- a/itest/probing_test.go +++ b/itest/probing_test.go @@ -24,7 +24,7 @@ func testProbing(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -33,6 +33,7 @@ func testProbing(p *testParams) { description: description, lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -43,7 +44,7 @@ func testProbing(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/test_common.go b/itest/test_common.go index 5f388df..9d8f666 100644 --- a/itest/test_common.go +++ b/itest/test_common.go @@ -26,7 +26,7 @@ func AssertChannelCapacity( assert.Equal(t, ((outerAmountMsat/1000)+100000)*1000, capacityMsat) } -func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) (uint64, uint64) { +func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) uint64 { fee := outerAmountMsat * 40 / 10_000 / 1_000 * 1_000 if fee < 2000000 { fee = 2000000 @@ -34,14 +34,7 @@ func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) (uint64, uint inner := outerAmountMsat - fee - // NOTE: If the LSP does not support charging fees (the CLN version doesn't) - // We have to pretend in the registerpayment call that the LSP WILL charge - // fees. If we update the CLN lsp to charge fees, this check can be removed. - if lsp.SupportsChargingFees() { - return inner, inner - } else { - return outerAmountMsat, inner - } + return inner } var publicChanAmount uint64 = 1000183 diff --git a/itest/zero_conf_utxo_test.go b/itest/zero_conf_utxo_test.go index d366a65..ea991fc 100644 --- a/itest/zero_conf_utxo_test.go +++ b/itest/zero_conf_utxo_test.go @@ -36,7 +36,7 @@ func testOpenZeroConfUtxo(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -45,6 +45,7 @@ func testOpenZeroConfUtxo(p *testParams) { description: description, lsp: lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(lsp.LightningNode()) @@ -55,7 +56,7 @@ func testOpenZeroConfUtxo(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/zero_reserve_test.go b/itest/zero_reserve_test.go index 81ffdee..6b1eab3 100644 --- a/itest/zero_reserve_test.go +++ b/itest/zero_reserve_test.go @@ -23,7 +23,7 @@ func testZeroReserve(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -32,6 +32,7 @@ func testZeroReserve(p *testParams) { description: description, lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -42,7 +43,7 @@ func testZeroReserve(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor.