mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-23 22:46:40 +01:00
Merge pull request #8660 from GeorgeTsagk/interceptor-wire-records
Enhance `update_add_htlc` with remote peer's custom records
This commit is contained in:
commit
966f41f0c7
18 changed files with 1230 additions and 780 deletions
|
@ -2550,6 +2550,12 @@ type HTLC struct {
|
|||
// HTLC. It is stored in the ExtraData field, which is used to store
|
||||
// a TLV stream of additional information associated with the HTLC.
|
||||
BlindingPoint lnwire.BlindingPointRecord
|
||||
|
||||
// CustomRecords is a set of custom TLV records that are associated with
|
||||
// this HTLC. These records are used to store additional information
|
||||
// about the HTLC that is not part of the standard HTLC fields. This
|
||||
// field is encoded within the ExtraData field.
|
||||
CustomRecords lnwire.CustomRecords
|
||||
}
|
||||
|
||||
// serializeExtraData encodes a TLV stream of extra data to be stored with a
|
||||
|
@ -2568,6 +2574,11 @@ func (h *HTLC) serializeExtraData() error {
|
|||
records = append(records, &b)
|
||||
})
|
||||
|
||||
records, err := h.CustomRecords.ExtendRecordProducers(records)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.ExtraData.PackRecords(records...)
|
||||
}
|
||||
|
||||
|
@ -2589,8 +2600,19 @@ func (h *HTLC) deserializeExtraData() error {
|
|||
|
||||
if val, ok := tlvMap[h.BlindingPoint.TlvType()]; ok && val == nil {
|
||||
h.BlindingPoint = tlv.SomeRecordT(blindingPoint)
|
||||
|
||||
// Remove the entry from the TLV map. Anything left in the map
|
||||
// will be included in the custom records field.
|
||||
delete(tlvMap, h.BlindingPoint.TlvType())
|
||||
}
|
||||
|
||||
// Set the custom records field to the remaining TLV records.
|
||||
customRecords, err := lnwire.NewCustomRecordsFromTlvTypeMap(tlvMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.CustomRecords = customRecords
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
|
@ -622,15 +623,16 @@ func (f *interceptedForward) Packet() InterceptedPacket {
|
|||
ChanID: f.packet.incomingChanID,
|
||||
HtlcID: f.packet.incomingHTLCID,
|
||||
},
|
||||
OutgoingChanID: f.packet.outgoingChanID,
|
||||
Hash: f.htlc.PaymentHash,
|
||||
OutgoingExpiry: f.htlc.Expiry,
|
||||
OutgoingAmount: f.htlc.Amount,
|
||||
IncomingAmount: f.packet.incomingAmount,
|
||||
IncomingExpiry: f.packet.incomingTimeout,
|
||||
CustomRecords: f.packet.customRecords,
|
||||
OnionBlob: f.htlc.OnionBlob,
|
||||
AutoFailHeight: f.autoFailHeight,
|
||||
OutgoingChanID: f.packet.outgoingChanID,
|
||||
Hash: f.htlc.PaymentHash,
|
||||
OutgoingExpiry: f.htlc.Expiry,
|
||||
OutgoingAmount: f.htlc.Amount,
|
||||
IncomingAmount: f.packet.incomingAmount,
|
||||
IncomingExpiry: f.packet.incomingTimeout,
|
||||
CustomRecords: f.packet.customRecords,
|
||||
OnionBlob: f.htlc.OnionBlob,
|
||||
AutoFailHeight: f.autoFailHeight,
|
||||
IncomingWireCustomRecords: f.packet.incomingCustomRecords,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -659,50 +661,58 @@ func (f *interceptedForward) ResumeModified(
|
|||
htlc.Amount = amount
|
||||
})
|
||||
|
||||
//nolint:lll
|
||||
err := fn.MapOptionZ(customRecords, func(records record.CustomSet) error {
|
||||
if len(records) == 0 {
|
||||
err := fn.MapOptionZ(
|
||||
customRecords, func(records record.CustomSet) error {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type cast and validate custom records.
|
||||
htlc.CustomRecords = lnwire.CustomRecords(
|
||||
records,
|
||||
)
|
||||
err := htlc.CustomRecords.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate "+
|
||||
"custom records: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type cast and validate custom records.
|
||||
htlc.CustomRecords = lnwire.CustomRecords(records)
|
||||
err := htlc.CustomRecords.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate custom "+
|
||||
"records: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode custom records: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
case *lnwire.UpdateFulfillHTLC:
|
||||
//nolint:lll
|
||||
err := fn.MapOptionZ(customRecords, func(records record.CustomSet) error {
|
||||
if len(records) == 0 {
|
||||
err := fn.MapOptionZ(
|
||||
customRecords, func(records record.CustomSet) error {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type cast and validate custom records.
|
||||
htlc.CustomRecords = lnwire.CustomRecords(
|
||||
records,
|
||||
)
|
||||
err := htlc.CustomRecords.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate "+
|
||||
"custom records: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type cast and validate custom records.
|
||||
htlc.CustomRecords = lnwire.CustomRecords(records)
|
||||
err := htlc.CustomRecords.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate custom "+
|
||||
"records: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode custom records: %w",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Tracef("Forwarding packet %v", spew.Sdump(f.packet))
|
||||
|
||||
// Forward to the switch. A link quit channel isn't needed, because we
|
||||
// are on a different thread now.
|
||||
return f.htlcSwitch.ForwardPackets(nil, f.packet)
|
||||
|
|
|
@ -357,6 +357,10 @@ type InterceptedPacket struct {
|
|||
// OnionBlob is the onion packet for the next hop
|
||||
OnionBlob [lnwire.OnionPacketSize]byte
|
||||
|
||||
// IncomingWireCustomRecords are user-defined records that were defined
|
||||
// by the peer that forwarded this htlc to us.
|
||||
IncomingWireCustomRecords record.CustomSet
|
||||
|
||||
// AutoFailHeight is the block height at which this intercept will be
|
||||
// failed back automatically.
|
||||
AutoFailHeight int32
|
||||
|
|
|
@ -29,7 +29,9 @@ 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"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -3354,6 +3356,27 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||
continue
|
||||
}
|
||||
|
||||
var customRecords record.CustomSet
|
||||
err = fn.MapOptionZ(
|
||||
pd.CustomRecords, func(b tlv.Blob) error {
|
||||
r, err := lnwire.ParseCustomRecords(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
customRecords = record.CustomSet(r)
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
l.fail(LinkFailureError{
|
||||
code: ErrInternalError,
|
||||
}, err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch fwdPkg.State {
|
||||
case channeldb.FwdStateProcessed:
|
||||
// This add was not forwarded on the previous
|
||||
|
@ -3367,7 +3390,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||
}
|
||||
|
||||
// Otherwise, it was already processed, we can
|
||||
// can collect it and continue.
|
||||
// collect it and continue.
|
||||
addMsg := &lnwire.UpdateAddHTLC{
|
||||
Expiry: fwdInfo.OutgoingCTLV,
|
||||
Amount: fwdInfo.AmountToForward,
|
||||
|
@ -3387,19 +3410,21 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||
|
||||
inboundFee := l.cfg.FwrdingPolicy.InboundFee
|
||||
|
||||
//nolint:lll
|
||||
updatePacket := &htlcPacket{
|
||||
incomingChanID: l.ShortChanID(),
|
||||
incomingHTLCID: pd.HtlcIndex,
|
||||
outgoingChanID: fwdInfo.NextHop,
|
||||
sourceRef: pd.SourceRef,
|
||||
incomingAmount: pd.Amount,
|
||||
amount: addMsg.Amount,
|
||||
htlc: addMsg,
|
||||
obfuscator: obfuscator,
|
||||
incomingTimeout: pd.Timeout,
|
||||
outgoingTimeout: fwdInfo.OutgoingCTLV,
|
||||
customRecords: pld.CustomRecords(),
|
||||
inboundFee: inboundFee,
|
||||
incomingChanID: l.ShortChanID(),
|
||||
incomingHTLCID: pd.HtlcIndex,
|
||||
outgoingChanID: fwdInfo.NextHop,
|
||||
sourceRef: pd.SourceRef,
|
||||
incomingAmount: pd.Amount,
|
||||
amount: addMsg.Amount,
|
||||
htlc: addMsg,
|
||||
obfuscator: obfuscator,
|
||||
incomingTimeout: pd.Timeout,
|
||||
outgoingTimeout: fwdInfo.OutgoingCTLV,
|
||||
customRecords: pld.CustomRecords(),
|
||||
inboundFee: inboundFee,
|
||||
incomingCustomRecords: customRecords,
|
||||
}
|
||||
switchPackets = append(
|
||||
switchPackets, updatePacket,
|
||||
|
@ -3455,19 +3480,21 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||
if fwdPkg.State == channeldb.FwdStateLockedIn {
|
||||
inboundFee := l.cfg.FwrdingPolicy.InboundFee
|
||||
|
||||
//nolint:lll
|
||||
updatePacket := &htlcPacket{
|
||||
incomingChanID: l.ShortChanID(),
|
||||
incomingHTLCID: pd.HtlcIndex,
|
||||
outgoingChanID: fwdInfo.NextHop,
|
||||
sourceRef: pd.SourceRef,
|
||||
incomingAmount: pd.Amount,
|
||||
amount: addMsg.Amount,
|
||||
htlc: addMsg,
|
||||
obfuscator: obfuscator,
|
||||
incomingTimeout: pd.Timeout,
|
||||
outgoingTimeout: fwdInfo.OutgoingCTLV,
|
||||
customRecords: pld.CustomRecords(),
|
||||
inboundFee: inboundFee,
|
||||
incomingChanID: l.ShortChanID(),
|
||||
incomingHTLCID: pd.HtlcIndex,
|
||||
outgoingChanID: fwdInfo.NextHop,
|
||||
sourceRef: pd.SourceRef,
|
||||
incomingAmount: pd.Amount,
|
||||
amount: addMsg.Amount,
|
||||
htlc: addMsg,
|
||||
obfuscator: obfuscator,
|
||||
incomingTimeout: pd.Timeout,
|
||||
outgoingTimeout: fwdInfo.OutgoingCTLV,
|
||||
customRecords: pld.CustomRecords(),
|
||||
inboundFee: inboundFee,
|
||||
incomingCustomRecords: customRecords,
|
||||
}
|
||||
|
||||
fwdPkg.FwdFilter.Set(idx)
|
||||
|
|
|
@ -98,6 +98,10 @@ type htlcPacket struct {
|
|||
// were included in the payload.
|
||||
customRecords record.CustomSet
|
||||
|
||||
// incomingCustomRecords are custom type range TLVs that are included
|
||||
// in the incoming update_add_htlc.
|
||||
incomingCustomRecords record.CustomSet
|
||||
|
||||
// originalOutgoingChanID is used when sending back failure messages.
|
||||
// It is only used for forwarded Adds on option_scid_alias channels.
|
||||
// This is to avoid possible confusion if a payer uses the public SCID
|
||||
|
|
|
@ -430,6 +430,10 @@ var allTestCases = []*lntest.TestCase{
|
|||
Name: "forward interceptor modified htlc",
|
||||
TestFunc: testForwardInterceptorModifiedHtlc,
|
||||
},
|
||||
{
|
||||
Name: "forward interceptor wire records",
|
||||
TestFunc: testForwardInterceptorWireRecords,
|
||||
},
|
||||
{
|
||||
Name: "zero conf channel open",
|
||||
TestFunc: testZeroConfChannelOpen,
|
||||
|
|
|
@ -410,10 +410,10 @@ func testForwardInterceptorModifiedHtlc(ht *lntest.HarnessTest) {
|
|||
newOutgoingAmountMsat := packet.OutgoingAmountMsat + 4000
|
||||
|
||||
err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
||||
IncomingCircuitKey: packet.IncomingCircuitKey,
|
||||
OutgoingAmountMsat: newOutgoingAmountMsat,
|
||||
CustomRecords: customRecords,
|
||||
Action: action,
|
||||
IncomingCircuitKey: packet.IncomingCircuitKey,
|
||||
OutgoingAmountMsat: newOutgoingAmountMsat,
|
||||
OutgoingHtlcWireCustomRecords: customRecords,
|
||||
Action: action,
|
||||
})
|
||||
require.NoError(ht, err, "failed to send request")
|
||||
|
||||
|
@ -479,17 +479,109 @@ func testForwardInterceptorModifiedHtlc(ht *lntest.HarnessTest) {
|
|||
ht.CloseChannel(bob, cpBC)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Initialize the test context with 3 connected nodes.
|
||||
ts := newInterceptorTestScenario(ht)
|
||||
|
||||
alice, bob, carol, dave := ts.alice, ts.bob, ts.carol, ts.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},
|
||||
}
|
||||
resp := ht.OpenMultiChannelsAsync(reqs)
|
||||
cpAB, cpBC, cpCD := resp[0], resp[1], resp[2]
|
||||
|
||||
// Make sure Alice is aware of channel Bob=>Carol.
|
||||
ht.AssertTopologyChannelOpen(alice, cpBC)
|
||||
|
||||
// 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()
|
||||
|
||||
req := &lnrpc.Invoice{ValueMsat: 1000}
|
||||
addResponse := dave.RPC.AddInvoice(req)
|
||||
invoice := dave.RPC.LookupInvoice(addResponse.RHash)
|
||||
|
||||
sendReq := &routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: invoice.PaymentRequest,
|
||||
TimeoutSeconds: int32(wait.PaymentTimeout.Seconds()),
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
FirstHopCustomRecords: map[uint64][]byte{
|
||||
65537: []byte("test"),
|
||||
},
|
||||
}
|
||||
|
||||
_ = alice.RPC.SendPayment(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.Len(ht, packet.IncomingHtlcWireCustomRecords, 1)
|
||||
|
||||
val, ok := packet.IncomingHtlcWireCustomRecords[65537]
|
||||
require.True(ht, ok, "expected custom record")
|
||||
require.Equal(ht, []byte("test"), val)
|
||||
|
||||
action := routerrpc.ResolveHoldForwardAction_RESUME_MODIFIED
|
||||
newOutgoingAmountMsat := packet.OutgoingAmountMsat + 800
|
||||
|
||||
err := bobInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
||||
IncomingCircuitKey: packet.IncomingCircuitKey,
|
||||
OutgoingAmountMsat: newOutgoingAmountMsat,
|
||||
Action: action,
|
||||
})
|
||||
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.
|
||||
packet = ht.ReceiveHtlcInterceptor(carolInterceptor)
|
||||
require.Len(ht, packet.IncomingHtlcWireCustomRecords, 0)
|
||||
|
||||
// Just resume the payment on Carol.
|
||||
err = carolInterceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
|
||||
IncomingCircuitKey: packet.IncomingCircuitKey,
|
||||
Action: routerrpc.ResolveHoldForwardAction_RESUME,
|
||||
})
|
||||
require.NoError(ht, err, "carol interceptor response")
|
||||
|
||||
// Assert that the payment was successful.
|
||||
var preimage lntypes.Preimage
|
||||
copy(preimage[:], invoice.RPreimage)
|
||||
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
||||
|
||||
// Finally, close channels.
|
||||
ht.CloseChannel(alice, cpAB)
|
||||
ht.CloseChannel(bob, cpBC)
|
||||
ht.CloseChannel(carol, cpCD)
|
||||
}
|
||||
|
||||
// interceptorTestScenario is a helper struct to hold the test context and
|
||||
// provide the needed functionality.
|
||||
type interceptorTestScenario struct {
|
||||
ht *lntest.HarnessTest
|
||||
alice, bob, carol *node.HarnessNode
|
||||
ht *lntest.HarnessTest
|
||||
alice, bob, carol, dave *node.HarnessNode
|
||||
}
|
||||
|
||||
// newInterceptorTestScenario initializes a new test scenario with three nodes
|
||||
// and connects them to have the following topology,
|
||||
//
|
||||
// Alice --> Bob --> Carol
|
||||
// Alice --> Bob --> Carol --> Dave
|
||||
//
|
||||
// Among them, Alice and Bob are standby nodes and Carol is a new node.
|
||||
func newInterceptorTestScenario(
|
||||
|
@ -497,15 +589,21 @@ func newInterceptorTestScenario(
|
|||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
carol := ht.NewNode("carol", nil)
|
||||
dave := ht.NewNode("dave", nil)
|
||||
|
||||
ht.EnsureConnected(alice, bob)
|
||||
ht.EnsureConnected(bob, carol)
|
||||
ht.EnsureConnected(carol, dave)
|
||||
|
||||
// So that carol can open channels.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
return &interceptorTestScenario{
|
||||
ht: ht,
|
||||
alice: alice,
|
||||
bob: bob,
|
||||
carol: carol,
|
||||
dave: dave,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -84,15 +84,16 @@ func (r *forwardInterceptor) onIntercept(
|
|||
ChanId: inKey.ChanID.ToUint64(),
|
||||
HtlcId: inKey.HtlcID,
|
||||
},
|
||||
OutgoingRequestedChanId: htlc.OutgoingChanID.ToUint64(),
|
||||
PaymentHash: htlc.Hash[:],
|
||||
OutgoingAmountMsat: uint64(htlc.OutgoingAmount),
|
||||
OutgoingExpiry: htlc.OutgoingExpiry,
|
||||
IncomingAmountMsat: uint64(htlc.IncomingAmount),
|
||||
IncomingExpiry: htlc.IncomingExpiry,
|
||||
CustomRecords: htlc.CustomRecords,
|
||||
OnionBlob: htlc.OnionBlob[:],
|
||||
AutoFailHeight: htlc.AutoFailHeight,
|
||||
OutgoingRequestedChanId: htlc.OutgoingChanID.ToUint64(),
|
||||
PaymentHash: htlc.Hash[:],
|
||||
OutgoingAmountMsat: uint64(htlc.OutgoingAmount),
|
||||
OutgoingExpiry: htlc.OutgoingExpiry,
|
||||
IncomingAmountMsat: uint64(htlc.IncomingAmount),
|
||||
IncomingExpiry: htlc.IncomingExpiry,
|
||||
CustomRecords: htlc.CustomRecords,
|
||||
OnionBlob: htlc.OnionBlob[:],
|
||||
AutoFailHeight: htlc.AutoFailHeight,
|
||||
IncomingHtlcWireCustomRecords: htlc.IncomingWireCustomRecords,
|
||||
}
|
||||
|
||||
return r.stream.Send(interceptionRequest)
|
||||
|
@ -138,9 +139,9 @@ func (r *forwardInterceptor) resolveFromClient(
|
|||
}
|
||||
|
||||
customRecords := fn.None[record.CustomSet]()
|
||||
if len(in.CustomRecords) > 0 {
|
||||
if len(in.OutgoingHtlcWireCustomRecords) > 0 {
|
||||
// Validate custom records.
|
||||
cr := record.CustomSet(in.CustomRecords)
|
||||
cr := record.CustomSet(in.OutgoingHtlcWireCustomRecords)
|
||||
if err := cr.Validate(); err != nil {
|
||||
return status.Errorf(
|
||||
codes.InvalidArgument,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -347,6 +347,15 @@ message SendPaymentRequest {
|
|||
only, to 1 to optimize for reliability only or a value inbetween for a mix.
|
||||
*/
|
||||
double time_pref = 23;
|
||||
|
||||
/*
|
||||
An optional field that can be used to pass an arbitrary set of TLV records
|
||||
to the first hop peer of this payment. This can be used to pass application
|
||||
specific data during the payment attempt. Record types are required to be in
|
||||
the custom range >= 65536. When using REST, the values must be encoded as
|
||||
base64.
|
||||
*/
|
||||
map<uint64, bytes> first_hop_custom_records = 24;
|
||||
}
|
||||
|
||||
message TrackPaymentRequest {
|
||||
|
@ -971,6 +980,9 @@ message ForwardHtlcInterceptRequest {
|
|||
// The block height at which this htlc will be auto-failed to prevent the
|
||||
// channel from force-closing.
|
||||
int32 auto_fail_height = 10;
|
||||
|
||||
// The custom records of the peer's incoming wire message.
|
||||
map<uint64, bytes> incoming_htlc_wire_custom_records = 11;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1018,9 +1030,9 @@ message ForwardHtlcInterceptResponse {
|
|||
// for resuming a HTLC.
|
||||
uint64 outgoing_amount_msat = 7;
|
||||
|
||||
// custom_records is used to set the p2p message custom records field for
|
||||
// resuming a HTLC.
|
||||
map<uint64, bytes> custom_records = 8;
|
||||
// Outgoing htlc wire custom records is used to set the p2p message custom
|
||||
// records field for resuming a HTLC.
|
||||
map<uint64, bytes> outgoing_htlc_wire_custom_records = 8;
|
||||
}
|
||||
|
||||
enum ResolveHoldForwardAction {
|
||||
|
|
|
@ -1444,6 +1444,14 @@
|
|||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The block height at which this htlc will be auto-failed to prevent the\nchannel from force-closing."
|
||||
},
|
||||
"incoming_htlc_wire_custom_records": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"description": "The custom records of the peer's incoming wire message."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1482,13 +1490,13 @@
|
|||
"format": "uint64",
|
||||
"description": "outgoing_amount_msat is used to set the p2p message outgoing amount field\nfor resuming a HTLC."
|
||||
},
|
||||
"custom_records": {
|
||||
"outgoing_htlc_wire_custom_records": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"description": "custom_records is used to set the p2p message custom records field for\nresuming a HTLC."
|
||||
"description": "Outgoing htlc wire custom records is used to set the p2p message custom\nrecords field for resuming a HTLC."
|
||||
}
|
||||
},
|
||||
"description": "*\nForwardHtlcInterceptResponse enables the caller to resolve a previously hold\nforward. The caller can choose either to:\n- `Resume`: Execute the default behavior (usually forward).\n- `ResumeModified`: Execute the default behavior (usually forward) with HTLC\nfield modifications.\n- `Reject`: Fail the htlc backwards.\n- `Settle`: Settle this htlc with a given preimage."
|
||||
|
@ -1950,6 +1958,14 @@
|
|||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "The time preference for this payment. Set to -1 to optimize for fees\nonly, to 1 to optimize for reliability only or a value inbetween for a mix."
|
||||
},
|
||||
"first_hop_custom_records": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"description": "An optional field that can be used to pass an arbitrary set of TLV records\nto the first hop peer of this payment. This can be used to pass application\nspecific data during the payment attempt. Record types are required to be in\nthe custom range \u003e= 65536. When using REST, the values must be encoded as\nbase64."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -858,6 +858,12 @@ func (r *RouterBackend) extractIntentFromSendRequest(
|
|||
}
|
||||
payIntent.DestCustomRecords = customRecords
|
||||
|
||||
firstHopRecords := record.CustomSet(rpcPayReq.FirstHopCustomRecords)
|
||||
if err := firstHopRecords.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payIntent.FirstHopCustomRecords = firstHopRecords
|
||||
|
||||
payIntent.PayAttemptTimeout = time.Second *
|
||||
time.Duration(rpcPayReq.TimeoutSeconds)
|
||||
|
||||
|
|
|
@ -335,7 +335,7 @@ type PaymentDescriptor struct {
|
|||
// NOTE: Populated only on add payment descriptor entry types.
|
||||
OnionBlob []byte
|
||||
|
||||
// CustomRecrods also stores the set of optional custom records that
|
||||
// CustomRecords also stores the set of optional custom records that
|
||||
// may have been attached to a sent HTLC.
|
||||
CustomRecords fn.Option[tlv.Blob]
|
||||
|
||||
|
@ -428,6 +428,17 @@ func PayDescsFromRemoteLogUpdates(chanID lnwire.ShortChannelID, height uint64,
|
|||
pd.OnionBlob = make([]byte, len(wireMsg.OnionBlob))
|
||||
copy(pd.OnionBlob[:], wireMsg.OnionBlob[:])
|
||||
|
||||
if len(wireMsg.CustomRecords) > 0 {
|
||||
b, err := wireMsg.CustomRecords.Serialize()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error "+
|
||||
"serializing custom records: "+
|
||||
"%w", err)
|
||||
}
|
||||
|
||||
pd.CustomRecords = fn.Some[tlv.Blob](b)
|
||||
}
|
||||
|
||||
case *lnwire.UpdateFulfillHTLC:
|
||||
pd = PaymentDescriptor{
|
||||
RPreimage: wireMsg.PaymentPreimage,
|
||||
|
@ -719,7 +730,9 @@ func (c *commitment) populateHtlcIndexes(chanType channeldb.ChannelType,
|
|||
|
||||
// toDiskCommit converts the target commitment into a format suitable to be
|
||||
// written to disk after an accepted state transition.
|
||||
func (c *commitment) toDiskCommit(ourCommit bool) *channeldb.ChannelCommitment {
|
||||
func (c *commitment) toDiskCommit(ourCommit bool) (*channeldb.ChannelCommitment,
|
||||
error) {
|
||||
|
||||
numHtlcs := len(c.outgoingHTLCs) + len(c.incomingHTLCs)
|
||||
|
||||
commit := &channeldb.ChannelCommitment{
|
||||
|
@ -756,11 +769,23 @@ func (c *commitment) toDiskCommit(ourCommit bool) *channeldb.ChannelCommitment {
|
|||
}
|
||||
copy(h.OnionBlob[:], htlc.OnionBlob)
|
||||
|
||||
// If the HTLC had custom records, then we'll copy that over so
|
||||
// If the HTLC had custom records, then we'll copy that over, so
|
||||
// we restore with the same information.
|
||||
htlc.CustomRecords.WhenSome(func(b tlv.Blob) {
|
||||
copy(h.ExtraData, b)
|
||||
})
|
||||
err := fn.MapOptionZ(
|
||||
htlc.CustomRecords, func(b tlv.Blob) error {
|
||||
r, err := lnwire.ParseCustomRecords(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.CustomRecords = r
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ourCommit && htlc.sig != nil {
|
||||
h.Signature = htlc.sig.Serialize()
|
||||
|
@ -787,11 +812,23 @@ func (c *commitment) toDiskCommit(ourCommit bool) *channeldb.ChannelCommitment {
|
|||
}
|
||||
copy(h.OnionBlob[:], htlc.OnionBlob)
|
||||
|
||||
// If the HTLC had custom records, then we'll copy that over so
|
||||
// If the HTLC had custom records, then we'll copy that over, so
|
||||
// we restore with the same information.
|
||||
htlc.CustomRecords.WhenSome(func(b tlv.Blob) {
|
||||
copy(h.ExtraData, b)
|
||||
})
|
||||
err := fn.MapOptionZ(
|
||||
htlc.CustomRecords, func(b tlv.Blob) error {
|
||||
r, err := lnwire.ParseCustomRecords(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.CustomRecords = r
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ourCommit && htlc.sig != nil {
|
||||
h.Signature = htlc.sig.Serialize()
|
||||
|
@ -800,7 +837,7 @@ func (c *commitment) toDiskCommit(ourCommit bool) *channeldb.ChannelCommitment {
|
|||
commit.Htlcs = append(commit.Htlcs, h)
|
||||
}
|
||||
|
||||
return commit
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
// diskHtlcToPayDesc converts an HTLC previously written to disk within a
|
||||
|
@ -894,8 +931,12 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
|
|||
|
||||
// Ensure that we'll restore any custom records which were stored as
|
||||
// extra data on disk.
|
||||
if len(htlc.ExtraData) != 0 {
|
||||
pd.CustomRecords = fn.Some[tlv.Blob](htlc.ExtraData)
|
||||
if len(htlc.CustomRecords) != 0 {
|
||||
b, err := htlc.CustomRecords.Serialize()
|
||||
if err != nil {
|
||||
return pd, err
|
||||
}
|
||||
pd.CustomRecords = fn.Some[tlv.Blob](b)
|
||||
}
|
||||
|
||||
return pd, nil
|
||||
|
@ -1641,6 +1682,16 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
|
|||
pd.OnionBlob = make([]byte, len(wireMsg.OnionBlob))
|
||||
copy(pd.OnionBlob[:], wireMsg.OnionBlob[:])
|
||||
|
||||
if len(wireMsg.CustomRecords) > 0 {
|
||||
b, err := wireMsg.CustomRecords.Serialize()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error serializing "+
|
||||
"custom records: %w", err)
|
||||
}
|
||||
|
||||
pd.CustomRecords = fn.Some[tlv.Blob](b)
|
||||
}
|
||||
|
||||
isDustRemote := HtlcIsDust(
|
||||
lc.channelState.ChanType, false, false, feeRate,
|
||||
wireMsg.Amount.ToSatoshis(), remoteDustLimit,
|
||||
|
@ -1846,6 +1897,16 @@ func (lc *LightningChannel) remoteLogUpdateToPayDesc(logUpdate *channeldb.LogUpd
|
|||
pd.OnionBlob = make([]byte, len(wireMsg.OnionBlob))
|
||||
copy(pd.OnionBlob, wireMsg.OnionBlob[:])
|
||||
|
||||
if len(wireMsg.CustomRecords) > 0 {
|
||||
b, err := wireMsg.CustomRecords.Serialize()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error serializing "+
|
||||
"custom records: %w", err)
|
||||
}
|
||||
|
||||
pd.CustomRecords = fn.Some[tlv.Blob](b)
|
||||
}
|
||||
|
||||
// We don't need to generate an htlc script yet. This will be
|
||||
// done once we sign our remote commitment.
|
||||
|
||||
|
@ -3601,9 +3662,14 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
|||
var err error
|
||||
cancelChan := make(chan struct{})
|
||||
|
||||
diskCommit, err := remoteCommitView.toDiskCommit(true)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to convert "+
|
||||
"commitment: %w", err)
|
||||
}
|
||||
|
||||
auxLeaves, err := AuxLeavesFromCommit(
|
||||
chanState, *remoteCommitView.toDiskCommit(false), leafStore,
|
||||
*keyRing,
|
||||
chanState, *diskCommit, leafStore, *keyRing,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to fetch aux leaves: "+
|
||||
|
@ -3838,6 +3904,25 @@ func (lc *LightningChannel) createCommitDiff(newCommit *commitment,
|
|||
copy(htlc.OnionBlob[:], pd.OnionBlob)
|
||||
logUpdate.UpdateMsg = htlc
|
||||
|
||||
// Copy over any custom records as extra data that we
|
||||
// may not explicitly know about.
|
||||
err := fn.MapOptionZ(
|
||||
pd.CustomRecords, func(b tlv.Blob) error {
|
||||
r, err := lnwire.ParseCustomRecords(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
htlc.CustomRecords = r
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error mapping custom "+
|
||||
"records: %w", err)
|
||||
}
|
||||
|
||||
// Gather any references for circuits opened by this Add
|
||||
// HTLC.
|
||||
if pd.OpenCircuitKey != nil {
|
||||
|
@ -3903,7 +3988,11 @@ func (lc *LightningChannel) createCommitDiff(newCommit *commitment,
|
|||
// With the set of log updates mapped into wire messages, we'll now
|
||||
// convert the in-memory commit into a format suitable for writing to
|
||||
// disk.
|
||||
diskCommit := newCommit.toDiskCommit(false)
|
||||
diskCommit, err := newCommit.toDiskCommit(false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error converting commitment to disk "+
|
||||
"commit: %w", err)
|
||||
}
|
||||
|
||||
return &channeldb.CommitDiff{
|
||||
Commitment: *diskCommit,
|
||||
|
@ -3924,7 +4013,9 @@ func (lc *LightningChannel) createCommitDiff(newCommit *commitment,
|
|||
|
||||
// getUnsignedAckedUpdates returns all remote log updates that we haven't
|
||||
// signed for yet ourselves.
|
||||
func (lc *LightningChannel) getUnsignedAckedUpdates() []channeldb.LogUpdate {
|
||||
func (lc *LightningChannel) getUnsignedAckedUpdates() ([]channeldb.LogUpdate,
|
||||
error) {
|
||||
|
||||
// First, we need to convert the funding outpoint into the ID that's
|
||||
// used on the wire to identify this channel.
|
||||
chanID := lnwire.NewChanIDFromOutPoint(lc.channelState.FundingOutpoint)
|
||||
|
@ -3974,6 +4065,26 @@ func (lc *LightningChannel) getUnsignedAckedUpdates() []channeldb.LogUpdate {
|
|||
BlindingPoint: pd.BlindingPoint,
|
||||
}
|
||||
copy(htlc.OnionBlob[:], pd.OnionBlob)
|
||||
|
||||
// Copy over any custom records as extra data that we
|
||||
// may not explicitly know about.
|
||||
err := fn.MapOptionZ(
|
||||
pd.CustomRecords, func(b tlv.Blob) error {
|
||||
r, err := lnwire.ParseCustomRecords(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
htlc.CustomRecords = r
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error mapping custom "+
|
||||
"records: %w", err)
|
||||
}
|
||||
|
||||
logUpdate.UpdateMsg = htlc
|
||||
|
||||
case Settle:
|
||||
|
@ -4010,7 +4121,8 @@ func (lc *LightningChannel) getUnsignedAckedUpdates() []channeldb.LogUpdate {
|
|||
|
||||
logUpdates = append(logUpdates, logUpdate)
|
||||
}
|
||||
return logUpdates
|
||||
|
||||
return logUpdates, nil
|
||||
}
|
||||
|
||||
// CalcFeeBuffer calculates a FeeBuffer in accordance with the recommended
|
||||
|
@ -5073,9 +5185,14 @@ func genHtlcSigValidationJobs(chanState *channeldb.OpenChannel,
|
|||
len(localCommitmentView.outgoingHTLCs))
|
||||
verifyJobs := make([]VerifyJob, 0, numHtlcs)
|
||||
|
||||
diskCommit, err := localCommitmentView.toDiskCommit(true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to convert commitment: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
auxLeaves, err := AuxLeavesFromCommit(
|
||||
chanState, *localCommitmentView.toDiskCommit(true), leafStore,
|
||||
*keyRing,
|
||||
chanState, *diskCommit, leafStore, *keyRing,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch aux leaves: %w",
|
||||
|
@ -5817,12 +5934,18 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck,
|
|||
// Additionally, generate a channel delta for this state transition for
|
||||
// persistent storage.
|
||||
chainTail := lc.localCommitChain.tail()
|
||||
newCommitment := chainTail.toDiskCommit(true)
|
||||
newCommitment, err := chainTail.toDiskCommit(true)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Get the unsigned acked remotes updates that are currently in memory.
|
||||
// We need them after a restart to sync our remote commitment with what
|
||||
// is committed locally.
|
||||
unsignedAckedUpdates := lc.getUnsignedAckedUpdates()
|
||||
unsignedAckedUpdates, err := lc.getUnsignedAckedUpdates()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
finalHtlcs, err := lc.channelState.UpdateCommitment(
|
||||
newCommitment, unsignedAckedUpdates,
|
||||
|
@ -6011,6 +6134,26 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
|
|||
BlindingPoint: pd.BlindingPoint,
|
||||
}
|
||||
copy(htlc.OnionBlob[:], pd.OnionBlob)
|
||||
|
||||
// Copy over any custom records as extra data that we
|
||||
// may not explicitly know about.
|
||||
err := fn.MapOptionZ(
|
||||
pd.CustomRecords, func(b tlv.Blob) error {
|
||||
r, err := lnwire.ParseCustomRecords(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
htlc.CustomRecords = r
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("error "+
|
||||
"mapping custom records: %w", err)
|
||||
}
|
||||
|
||||
logUpdate.UpdateMsg = htlc
|
||||
addUpdates = append(addUpdates, logUpdate)
|
||||
|
||||
|
@ -6212,7 +6355,11 @@ func (lc *LightningChannel) addHTLC(htlc *lnwire.UpdateAddHTLC,
|
|||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
|
||||
pd := lc.htlcAddDescriptor(htlc, openKey)
|
||||
pd, err := lc.htlcAddDescriptor(htlc, openKey)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := lc.validateAddHtlc(pd, buffer); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -6316,12 +6463,16 @@ func (lc *LightningChannel) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error {
|
|||
// to the commitment so that we validate commitment slots rather than
|
||||
// available balance, since our actual htlc amount is unknown at this
|
||||
// stage.
|
||||
pd := lc.htlcAddDescriptor(
|
||||
pd, err := lc.htlcAddDescriptor(
|
||||
&lnwire.UpdateAddHTLC{
|
||||
Amount: mockHtlcAmt,
|
||||
},
|
||||
&models.CircuitKey{},
|
||||
)
|
||||
if err != nil {
|
||||
lc.log.Errorf("Error adding htlc descriptor: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Enforce the FeeBuffer because we are evaluating whether we can add
|
||||
// another htlc to the channel state.
|
||||
|
@ -6336,7 +6487,7 @@ func (lc *LightningChannel) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error {
|
|||
// htlcAddDescriptor returns a payment descriptor for the htlc and open key
|
||||
// provided to add to our local update log.
|
||||
func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC,
|
||||
openKey *models.CircuitKey) *PaymentDescriptor {
|
||||
openKey *models.CircuitKey) (*PaymentDescriptor, error) {
|
||||
|
||||
pd := &PaymentDescriptor{
|
||||
EntryType: Add,
|
||||
|
@ -6352,11 +6503,17 @@ func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC,
|
|||
|
||||
// Copy over any extra data included to ensure we can forward and
|
||||
// process this HTLC properly.
|
||||
if len(htlc.ExtraData) != 0 {
|
||||
pd.CustomRecords = fn.Some[tlv.Blob](htlc.ExtraData[:])
|
||||
if len(htlc.CustomRecords) > 0 {
|
||||
b, err := htlc.CustomRecords.Serialize()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error serializing custom "+
|
||||
"records: %w", err)
|
||||
}
|
||||
|
||||
pd.CustomRecords = fn.Some[tlv.Blob](b)
|
||||
}
|
||||
|
||||
return pd
|
||||
return pd, nil
|
||||
}
|
||||
|
||||
// validateAddHtlc validates the addition of an outgoing htlc to our local and
|
||||
|
@ -6416,10 +6573,25 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, err
|
|||
BlindingPoint: htlc.BlindingPoint,
|
||||
}
|
||||
|
||||
if len(htlc.CustomRecords) > 0 {
|
||||
b, err := htlc.CustomRecords.Serialize()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error serializing custom "+
|
||||
"records: %w", err)
|
||||
}
|
||||
|
||||
pd.CustomRecords = fn.Some[tlv.Blob](b)
|
||||
}
|
||||
|
||||
// Copy over any extra data included to ensure we can forward and
|
||||
// process this HTLC properly.
|
||||
if htlc.ExtraData != nil {
|
||||
pd.CustomRecords = fn.Some(tlv.Blob(htlc.ExtraData[:]))
|
||||
var cr []byte
|
||||
pd.CustomRecords.WhenSome(func(b tlv.Blob) {
|
||||
cr = b
|
||||
})
|
||||
|
||||
pd.CustomRecords = fn.Some(append(cr, htlc.ExtraData...))
|
||||
}
|
||||
|
||||
localACKedIndex := lc.remoteCommitChain.tail().ourMessageIndex
|
||||
|
|
|
@ -23,6 +23,10 @@ type CustomRecords map[uint64][]byte
|
|||
func NewCustomRecordsFromTlvTypeMap(tlvMap tlv.TypeMap) (CustomRecords,
|
||||
error) {
|
||||
|
||||
if len(tlvMap) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
customRecords := make(CustomRecords, len(tlvMap))
|
||||
for k, v := range tlvMap {
|
||||
customRecords[uint64(k)] = v
|
||||
|
@ -38,9 +42,9 @@ func NewCustomRecordsFromTlvTypeMap(tlvMap tlv.TypeMap) (CustomRecords,
|
|||
return customRecords, nil
|
||||
}
|
||||
|
||||
// NewCustomRecordsFromTlvBlob creates a new CustomRecords instance from a
|
||||
// ParseCustomRecords creates a new CustomRecords instance from a
|
||||
// tlv.Blob.
|
||||
func NewCustomRecordsFromTlvBlob(b tlv.Blob) (CustomRecords, error) {
|
||||
func ParseCustomRecords(b tlv.Blob) (CustomRecords, error) {
|
||||
stream, err := tlv.NewStream()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating stream: %w", err)
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"github.com/lightningnetwork/lnd/routing/shards"
|
||||
)
|
||||
|
@ -31,6 +32,7 @@ type paymentLifecycle struct {
|
|||
shardTracker shards.ShardTracker
|
||||
timeoutChan <-chan time.Time
|
||||
currentHeight int32
|
||||
firstHopTLVs record.CustomSet
|
||||
|
||||
// quit is closed to signal the sub goroutines of the payment lifecycle
|
||||
// to stop.
|
||||
|
@ -53,7 +55,7 @@ type paymentLifecycle struct {
|
|||
func newPaymentLifecycle(r *ChannelRouter, feeLimit lnwire.MilliSatoshi,
|
||||
identifier lntypes.Hash, paySession PaymentSession,
|
||||
shardTracker shards.ShardTracker, timeout time.Duration,
|
||||
currentHeight int32) *paymentLifecycle {
|
||||
currentHeight int32, firstHopTLVs record.CustomSet) *paymentLifecycle {
|
||||
|
||||
p := &paymentLifecycle{
|
||||
router: r,
|
||||
|
@ -64,6 +66,7 @@ func newPaymentLifecycle(r *ChannelRouter, feeLimit lnwire.MilliSatoshi,
|
|||
currentHeight: currentHeight,
|
||||
quit: make(chan struct{}),
|
||||
resultCollected: make(chan error, 1),
|
||||
firstHopTLVs: firstHopTLVs,
|
||||
}
|
||||
|
||||
// Mount the result collector.
|
||||
|
@ -668,9 +671,10 @@ func (p *paymentLifecycle) sendAttempt(
|
|||
// this packet will be used to route the payment through the network,
|
||||
// starting with the first-hop.
|
||||
htlcAdd := &lnwire.UpdateAddHTLC{
|
||||
Amount: rt.TotalAmount,
|
||||
Expiry: rt.TotalTimeLock,
|
||||
PaymentHash: *attempt.Hash,
|
||||
Amount: rt.TotalAmount,
|
||||
Expiry: rt.TotalTimeLock,
|
||||
PaymentHash: *attempt.Hash,
|
||||
CustomRecords: lnwire.CustomRecords(p.firstHopTLVs),
|
||||
}
|
||||
|
||||
// Generate the raw encoded sphinx packet to be included along
|
||||
|
|
|
@ -88,7 +88,7 @@ func newTestPaymentLifecycle(t *testing.T) (*paymentLifecycle, *mockers) {
|
|||
// Create a test payment lifecycle with no fee limit and no timeout.
|
||||
p := newPaymentLifecycle(
|
||||
rt, noFeeLimit, paymentHash, mockPaymentSession,
|
||||
mockShardTracker, 0, 0,
|
||||
mockShardTracker, 0, 0, nil,
|
||||
)
|
||||
|
||||
// Create a mock payment which is returned from mockControlTower.
|
||||
|
|
|
@ -713,7 +713,7 @@ func (r *ChannelRouter) Start() error {
|
|||
// be tried.
|
||||
_, _, err := r.sendPayment(
|
||||
0, payment.Info.PaymentIdentifier, 0,
|
||||
paySession, shardTracker,
|
||||
paySession, shardTracker, nil,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Resuming payment %v failed: %v.",
|
||||
|
@ -2308,6 +2308,11 @@ type LightningPayment struct {
|
|||
// fail.
|
||||
DestCustomRecords record.CustomSet
|
||||
|
||||
// FirstHopCustomRecords are the TLV records that are to be sent to the
|
||||
// first hop of this payment. These records will be transmitted via the
|
||||
// wire message and therefore do not affect the onion payload size.
|
||||
FirstHopCustomRecords record.CustomSet
|
||||
|
||||
// MaxParts is the maximum number of partial payments that may be used
|
||||
// to complete the full amount.
|
||||
MaxParts uint32
|
||||
|
@ -2393,6 +2398,7 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte,
|
|||
return r.sendPayment(
|
||||
payment.FeeLimit, payment.Identifier(),
|
||||
payment.PayAttemptTimeout, paySession, shardTracker,
|
||||
payment.FirstHopCustomRecords,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2413,6 +2419,7 @@ func (r *ChannelRouter) SendPaymentAsync(payment *LightningPayment,
|
|||
_, _, err := r.sendPayment(
|
||||
payment.FeeLimit, payment.Identifier(),
|
||||
payment.PayAttemptTimeout, ps, st,
|
||||
payment.FirstHopCustomRecords,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("Payment %x failed: %v",
|
||||
|
@ -2587,7 +2594,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
|
|||
// - no payment timeout.
|
||||
// - no current block height.
|
||||
p := newPaymentLifecycle(
|
||||
r, 0, paymentIdentifier, nil, shardTracker, 0, 0,
|
||||
r, 0, paymentIdentifier, nil, shardTracker, 0, 0, nil,
|
||||
)
|
||||
|
||||
// We found a route to try, create a new HTLC attempt to try.
|
||||
|
@ -2683,8 +2690,8 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
|
|||
// the ControlTower.
|
||||
func (r *ChannelRouter) sendPayment(feeLimit lnwire.MilliSatoshi,
|
||||
identifier lntypes.Hash, timeout time.Duration,
|
||||
paySession PaymentSession,
|
||||
shardTracker shards.ShardTracker) ([32]byte, *route.Route, error) {
|
||||
paySession PaymentSession, shardTracker shards.ShardTracker,
|
||||
firstHopTLVs record.CustomSet) ([32]byte, *route.Route, error) {
|
||||
|
||||
// We'll also fetch the current block height so we can properly
|
||||
// calculate the required HTLC time locks within the route.
|
||||
|
@ -2697,7 +2704,7 @@ func (r *ChannelRouter) sendPayment(feeLimit lnwire.MilliSatoshi,
|
|||
// can resume the payment from the current state.
|
||||
p := newPaymentLifecycle(
|
||||
r, feeLimit, identifier, paySession,
|
||||
shardTracker, timeout, currentHeight,
|
||||
shardTracker, timeout, currentHeight, firstHopTLVs,
|
||||
)
|
||||
|
||||
return p.resumePayment()
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
)
|
||||
|
||||
// preimageSubscriber reprints an active subscription to be notified once the
|
||||
|
@ -101,10 +102,11 @@ func (p *preimageBeacon) SubscribeUpdates(
|
|||
ChanID: chanID,
|
||||
HtlcID: htlc.HtlcIndex,
|
||||
},
|
||||
OutgoingChanID: payload.FwdInfo.NextHop,
|
||||
OutgoingExpiry: payload.FwdInfo.OutgoingCTLV,
|
||||
OutgoingAmount: payload.FwdInfo.AmountToForward,
|
||||
CustomRecords: payload.CustomRecords(),
|
||||
OutgoingChanID: payload.FwdInfo.NextHop,
|
||||
OutgoingExpiry: payload.FwdInfo.OutgoingCTLV,
|
||||
OutgoingAmount: payload.FwdInfo.AmountToForward,
|
||||
CustomRecords: payload.CustomRecords(),
|
||||
IncomingWireCustomRecords: record.CustomSet(htlc.CustomRecords),
|
||||
}
|
||||
copy(packet.OnionBlob[:], nextHopOnionBlob)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue