lnrpc: add wire records fields to payment+interceptor RPCs

This commit is contained in:
George Tsagkarelis 2024-04-16 11:03:59 +02:00 committed by Oliver Gugger
parent 878f964a33
commit 42e358e3d3
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
9 changed files with 902 additions and 681 deletions

View File

@ -92,6 +92,7 @@ func (r *forwardInterceptor) onIntercept(
CustomRecords: htlc.InOnionCustomRecords,
OnionBlob: htlc.OnionBlob[:],
AutoFailHeight: htlc.AutoFailHeight,
InWireCustomRecords: htlc.InWireCustomRecords,
}
return r.stream.Send(interceptionRequest)

File diff suppressed because it is too large Load Diff

View File

@ -358,6 +358,15 @@ message SendPaymentRequest {
being sent.
*/
bool cancelable = 24;
/*
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 = 25;
}
message TrackPaymentRequest {
@ -451,6 +460,15 @@ message SendToRouteRequest {
routes, incorrect payment details, or insufficient funds.
*/
bool skip_temp_err = 3;
/*
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 = 4;
}
message SendToRouteResponse {
@ -726,6 +744,15 @@ message BuildRouteRequest {
This is also called payment secret in specifications (e.g. BOLT 11).
*/
bytes payment_addr = 5;
/*
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 = 6;
}
message BuildRouteResponse {
@ -982,6 +1009,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 p2p wire message.
map<uint64, bytes> in_wire_custom_records = 11;
}
/**

View File

@ -1283,6 +1283,14 @@
"type": "string",
"format": "byte",
"description": "An optional payment addr to be included within the last hop of the route.\nThis is also called payment secret in specifications (e.g. BOLT 11)."
},
"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."
}
}
},
@ -1447,6 +1455,14 @@
"type": "integer",
"format": "int32",
"description": "The block height at which this htlc will be auto-failed to prevent the\nchannel from force-closing."
},
"in_wire_custom_records": {
"type": "object",
"additionalProperties": {
"type": "string",
"format": "byte"
},
"description": "The custom records of the peer's incoming p2p wire message."
}
}
},
@ -1957,6 +1973,14 @@
"cancelable": {
"type": "boolean",
"description": "If set, the payment loop can be interrupted by manually canceling the\npayment context, even before the payment timeout is reached. Note that the\npayment may still succeed after cancellation, as in-flight attempts can\nstill settle afterwards. Canceling will only prevent further attempts from\nbeing sent."
},
"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."
}
}
},
@ -1975,6 +1999,14 @@
"skip_temp_err": {
"type": "boolean",
"description": "Whether the payment should be marked as failed when a temporary error is\nreturned from the given route. Set it to true so the payment won't be\nfailed unless a terminal error is occurred, such as payment timeout, no\nroutes, incorrect payment details, or insufficient funds."
},
"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."
}
}
},

View File

@ -858,6 +858,12 @@ func (r *RouterBackend) extractIntentFromSendRequest(
}
payIntent.DestCustomRecords = customRecords
firstHopRecords := lnwire.CustomRecords(rpcPayReq.FirstHopCustomRecords)
if err := firstHopRecords.Validate(); err != nil {
return nil, err
}
payIntent.FirstHopCustomRecords = firstHopRecords
payIntent.PayAttemptTimeout = time.Second *
time.Duration(rpcPayReq.TimeoutSeconds)

View File

@ -864,6 +864,11 @@ func (s *Server) SendToRouteV2(ctx context.Context,
return nil, err
}
firstHopRecords := lnwire.CustomRecords(req.FirstHopCustomRecords)
if err := firstHopRecords.Validate(); err != nil {
return nil, err
}
var attempt *channeldb.HTLCAttempt
// Pass route to the router. This call returns the full htlc attempt
@ -873,9 +878,13 @@ func (s *Server) SendToRouteV2(ctx context.Context,
// case, we give precedence to the attempt information as stored in the
// db.
if req.SkipTempErr {
attempt, err = s.cfg.Router.SendToRouteSkipTempErr(hash, route)
attempt, err = s.cfg.Router.SendToRouteSkipTempErr(
hash, route, firstHopRecords,
)
} else {
attempt, err = s.cfg.Router.SendToRoute(hash, route)
attempt, err = s.cfg.Router.SendToRoute(
hash, route, firstHopRecords,
)
}
if attempt != nil {
rpcAttempt, err := s.cfg.RouterBackend.MarshalHTLCAttempt(
@ -1458,9 +1467,26 @@ func (s *Server) BuildRoute(_ context.Context,
)
}
var firstHopBlob fn.Option[[]byte]
if len(req.FirstHopCustomRecords) > 0 {
firstHopRecords := lnwire.CustomRecords(
req.FirstHopCustomRecords,
)
if err := firstHopRecords.Validate(); err != nil {
return nil, err
}
firstHopData, err := firstHopRecords.Serialize()
if err != nil {
return nil, err
}
firstHopBlob = fn.Some(firstHopData)
}
// Build the route and return it to the caller.
route, err := s.cfg.Router.BuildRoute(
amt, hops, outgoingChan, req.FinalCltvDelta, payAddr,
firstHopBlob,
)
if err != nil {
return nil, err

View File

@ -1057,18 +1057,21 @@ func (r *ChannelRouter) PreparePayment(payment *LightningPayment) (
// SendToRoute sends a payment using the provided route and fails the payment
// when an error is returned from the attempt.
func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash,
rt *route.Route) (*channeldb.HTLCAttempt, error) {
func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, rt *route.Route,
firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt,
error) {
return r.sendToRoute(htlcHash, rt, false)
return r.sendToRoute(htlcHash, rt, false, firstHopCustomRecords)
}
// SendToRouteSkipTempErr sends a payment using the provided route and fails
// the payment ONLY when a terminal error is returned from the attempt.
func (r *ChannelRouter) SendToRouteSkipTempErr(htlcHash lntypes.Hash,
rt *route.Route) (*channeldb.HTLCAttempt, error) {
rt *route.Route,
firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt,
error) {
return r.sendToRoute(htlcHash, rt, true)
return r.sendToRoute(htlcHash, rt, true, firstHopCustomRecords)
}
// sendToRoute attempts to send a payment with the given hash through the
@ -1078,7 +1081,9 @@ func (r *ChannelRouter) SendToRouteSkipTempErr(htlcHash lntypes.Hash,
// was initiated, both return values will be non-nil. If skipTempErr is true,
// the payment won't be failed unless a terminal error has occurred.
func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
skipTempErr bool) (*channeldb.HTLCAttempt, error) {
skipTempErr bool,
firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt,
error) {
log.Debugf("SendToRoute for payment %v with skipTempErr=%v",
htlcHash, skipTempErr)
@ -1149,7 +1154,8 @@ 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, nil,
r, 0, paymentIdentifier, nil, shardTracker, 0,
firstHopCustomRecords,
)
// We found a route to try, create a new HTLC attempt to try.
@ -1318,8 +1324,9 @@ func (e ErrNoChannel) Error() string {
// amount is nil, the minimum routable amount is used. To force a specific
// outgoing channel, use the outgoingChan parameter.
func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi],
hops []route.Vertex, outgoingChan *uint64,
finalCltvDelta int32, payAddr *[32]byte) (*route.Route, error) {
hops []route.Vertex, outgoingChan *uint64, finalCltvDelta int32,
payAddr *[32]byte, _ fn.Option[[]byte]) (*route.Route,
error) {
log.Tracef("BuildRoute called: hopsCount=%v, amt=%v", len(hops), amt)

View File

@ -519,7 +519,7 @@ func TestChannelUpdateValidation(t *testing.T) {
// Send off the payment request to the router. The specified route
// should be attempted and the channel update should be received by
// graph and ignored because it is missing a valid signature.
_, err = ctx.router.SendToRoute(payment, rt)
_, err = ctx.router.SendToRoute(payment, rt, nil)
require.Error(t, err, "expected route to fail with channel update")
_, e1, e2, err = ctx.graph.FetchChannelEdgesByID(
@ -539,7 +539,7 @@ func TestChannelUpdateValidation(t *testing.T) {
ctx.graphBuilder.setNextReject(false)
// Retry the payment using the same route as before.
_, err = ctx.router.SendToRoute(payment, rt)
_, err = ctx.router.SendToRoute(payment, rt, nil)
require.Error(t, err, "expected route to fail with channel update")
// This time a valid signature was supplied and the policy change should
@ -1428,7 +1428,7 @@ func TestSendToRouteStructuredError(t *testing.T) {
// update should be received by router and ignored
// because it is missing a valid
// signature.
_, err = ctx.router.SendToRoute(payment, rt)
_, err = ctx.router.SendToRoute(payment, rt, nil)
fErr, ok := err.(*htlcswitch.ForwardingError)
require.True(
@ -1507,7 +1507,7 @@ func TestSendToRouteMaxHops(t *testing.T) {
// Send off the payment request to the router. We expect an error back
// indicating that the route is too long.
var payHash lntypes.Hash
_, err = ctx.router.SendToRoute(payHash, rt)
_, err = ctx.router.SendToRoute(payHash, rt, nil)
if err != route.ErrMaxRouteHopsExceeded {
t.Fatalf("expected ErrMaxRouteHopsExceeded, but got %v", err)
}
@ -1649,12 +1649,16 @@ func TestBuildRoute(t *testing.T) {
// Test that we can't build a route when no hops are given.
hops = []route.Vertex{}
_, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, nil)
_, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, nil, fn.None[[]byte](),
)
require.Error(t, err)
// Create hop list for an unknown destination.
hops := []route.Vertex{ctx.aliases["b"], ctx.aliases["y"]}
_, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr)
_, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
)
noChanErr := ErrNoChannel{}
require.ErrorAs(t, err, &noChanErr)
require.Equal(t, 1, noChanErr.position)
@ -1664,7 +1668,9 @@ func TestBuildRoute(t *testing.T) {
amt := lnwire.NewMSatFromSatoshis(100)
// Build the route for the given amount.
rt, err := ctx.router.BuildRoute(fn.Some(amt), hops, nil, 40, &payAddr)
rt, err := ctx.router.BuildRoute(
fn.Some(amt), hops, nil, 40, &payAddr, fn.None[[]byte](),
)
require.NoError(t, err)
// Check that we get the expected route back. The total amount should be
@ -1674,7 +1680,9 @@ func TestBuildRoute(t *testing.T) {
require.Equal(t, lnwire.MilliSatoshi(106000), rt.TotalAmount)
// Build the route for the minimum amount.
rt, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr)
rt, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
)
require.NoError(t, err)
// Check that we get the expected route back. The minimum that we can
@ -1690,7 +1698,9 @@ func TestBuildRoute(t *testing.T) {
// Test a route that contains incompatible channel htlc constraints.
// There is no amount that can pass through both channel 5 and 4.
hops = []route.Vertex{ctx.aliases["e"], ctx.aliases["c"]}
_, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, nil)
_, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, nil, fn.None[[]byte](),
)
require.Error(t, err)
noChanErr = ErrNoChannel{}
require.ErrorAs(t, err, &noChanErr)
@ -1708,7 +1718,9 @@ func TestBuildRoute(t *testing.T) {
// amount that could be delivered to the receiver of 21819 msat, using
// policy of channel 3.
hops = []route.Vertex{ctx.aliases["b"], ctx.aliases["z"]}
rt, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr)
rt, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
)
require.NoError(t, err)
checkHops(rt, []uint64{1, 8}, payAddr)
require.Equal(t, lnwire.MilliSatoshi(21200), rt.TotalAmount)
@ -1720,7 +1732,9 @@ func TestBuildRoute(t *testing.T) {
// We get 106000 - 1000 (base in) - 0.001 * 106000 (rate in) = 104894.
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
amt = lnwire.NewMSatFromSatoshis(100)
rt, err = ctx.router.BuildRoute(fn.Some(amt), hops, nil, 40, &payAddr)
rt, err = ctx.router.BuildRoute(
fn.Some(amt), hops, nil, 40, &payAddr, fn.None[[]byte](),
)
require.NoError(t, err)
checkHops(rt, []uint64{9, 10}, payAddr)
require.EqualValues(t, 104894, rt.TotalAmount)
@ -1734,7 +1748,9 @@ func TestBuildRoute(t *testing.T) {
// of 20179 msat, which results in underpayment of 1 msat in fee. There
// is a third pass through newRoute in which this gets corrected to end
hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
rt, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr)
rt, err = ctx.router.BuildRoute(
noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
)
require.NoError(t, err)
checkHops(rt, []uint64{9, 10}, payAddr)
require.EqualValues(t, 20180, rt.TotalAmount, "%v", rt.TotalAmount)
@ -2208,7 +2224,7 @@ func TestSendToRouteSkipTempErrSuccess(t *testing.T) {
payment.On("TerminalInfo").Return(nil, nil)
// Expect a successful send to route.
attempt, err := router.SendToRouteSkipTempErr(payHash, rt)
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
require.NoError(t, err)
require.Equal(t, testAttempt, attempt)
@ -2261,7 +2277,7 @@ func TestSendToRouteSkipTempErrNonMPP(t *testing.T) {
}}
// Expect an error to be returned.
attempt, err := router.SendToRouteSkipTempErr(payHash, rt)
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
require.ErrorIs(t, ErrSkipTempErr, err)
require.Nil(t, attempt)
@ -2345,7 +2361,7 @@ func TestSendToRouteSkipTempErrTempFailure(t *testing.T) {
payment.On("TerminalInfo").Return(nil, nil)
// Expect a failed send to route.
attempt, err := router.SendToRouteSkipTempErr(payHash, rt)
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
require.Equal(t, tempErr, err)
require.Equal(t, testAttempt, attempt)
@ -2432,7 +2448,7 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) {
payment.On("TerminalInfo").Return(nil, &failureReason)
// Expect a failed send to route.
attempt, err := router.SendToRouteSkipTempErr(payHash, rt)
attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
require.Equal(t, permErr, err)
require.Equal(t, testAttempt, attempt)
@ -2517,7 +2533,7 @@ func TestSendToRouteTempFailure(t *testing.T) {
).Return(nil, nil)
// Expect a failed send to route.
attempt, err := router.SendToRoute(payHash, rt)
attempt, err := router.SendToRoute(payHash, rt, nil)
require.Equal(t, tempErr, err)
require.Equal(t, testAttempt, attempt)

View File

@ -5445,7 +5445,7 @@ func (r *rpcServer) dispatchPaymentIntent(
} else {
var attempt *channeldb.HTLCAttempt
attempt, routerErr = r.server.chanRouter.SendToRoute(
payIntent.rHash, payIntent.route,
payIntent.rHash, payIntent.route, nil,
)
if routerErr == nil {