Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions htlcswitch/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,11 @@ func (s *Switch) CleanStore(keepPids map[uint64]struct{}) error {
return s.attemptStore.CleanStore(keepPids)
}

// AttemptStore provides access to the Switch's underlying attempt store.
func (s *Switch) AttemptStore() AttemptStore {
return s.attemptStore
}

// SendHTLC is used by other subsystems which aren't belong to htlc switch
// package in order to send the htlc update. The attemptID used MUST be unique
// for this HTLC, and MUST be used only once, otherwise the switch might reject
Expand Down
22 changes: 16 additions & 6 deletions itest/lnd_sendonion_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package itest

import (
"context"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
sphinx "github.com/lightningnetwork/lightning-onion"
Expand All @@ -9,9 +11,12 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/switchrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/rpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

// testSendOnion tests the basic success case for the SendOnion RPC. It
Expand Down Expand Up @@ -178,13 +183,18 @@ func testSendOnionTwice(ht *lntest.HarnessTest) {
// While the first onion is still in-flight, we'll send the same onion
// again with the same attempt ID. This should error as our Switch will
// detect duplicate ADDs for *in-flight* HTLCs.
resp = alice.RPC.SendOnion(sendReq)
ht.Logf("SendOnion resp: %+v, code: %v", resp, resp.ErrorCode)
require.False(ht, resp.Success, "expected failure on onion send")
require.Equal(ht, resp.ErrorCode,
switchrpc.ErrorCode_DUPLICATE_HTLC,
ctxt, cancel := context.WithTimeout(context.Background(),
rpc.DefaultTimeout)
defer cancel()

_, err := alice.RPC.Switch.SendOnion(ctxt, sendReq)
require.Error(ht, err, "expected failure on onion send")

// Check that we get the expected gRPC error.
s, ok := status.FromError(err)
require.True(ht, ok, "expected gRPC status error")
require.Equal(ht, codes.AlreadyExists, s.Code(),
"unexpected error code")
require.Equal(ht, resp.ErrorMessage, htlcswitch.ErrDuplicateAdd.Error())

// Dave settles the invoice.
dave.RPC.SettleInvoice(preimage[:])
Expand Down
4 changes: 4 additions & 0 deletions lnrpc/switchrpc/config_active.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type Config struct {
// be dispatched.
HtlcDispatcher routing.PaymentAttemptDispatcher

// AttemptStore provides the means by which the RPC server can manage
// the state of an HTLC attempt, including initializing and failing it.
AttemptStore htlcswitch.AttemptStore

// ChannelInfoAccessor defines an interface for accessing channel
// information necessary for dispatching payment attempts, specifically
// methods for fetching links by public key.
Expand Down
4 changes: 4 additions & 0 deletions lnrpc/switchrpc/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
return nil, nil, fmt.Errorf("route processor must be set to " +
"create SwitchRPC")
}
if config.AttemptStore == nil {
return nil, nil, fmt.Errorf("attempt store must be set to " +
"create SwitchRPC")
}

return New(config)
}
Expand Down
19 changes: 19 additions & 0 deletions lnrpc/switchrpc/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,22 @@ func (m *mockRouteProcessor) UnmarshallRoute(route *lnrpc.Route) (

return m.unmarshallRoute, m.unmarshallErr
}

// mockAttemptStore is a mock implementation of the AttemptStore interface.
type mockAttemptStore struct {
htlcswitch.AttemptStore
initErr error
failErr error
}

// InitAttempt returns the mocked initErr.
func (m *mockAttemptStore) InitAttempt(attemptID uint64) error {
return m.initErr
}

// FailPendingAttempt returns the mocked failErr.
func (m *mockAttemptStore) FailPendingAttempt(attemptID uint64,
reason *htlcswitch.LinkError) error {

return m.failErr
}
31 changes: 28 additions & 3 deletions lnrpc/switchrpc/switch.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,34 @@ option go_package = "github.com/lightningnetwork/lnd/lnrpc/switchrpc";
// subsystem of the daemon.
service Switch {
/*
SendOnion attempts to make a payment via the specified onion. This
method differs from SendPayment in that the instance need not be aware of
the full details of the payment route.
SendOnion provides an idempotent API for dispatching a pre-formed onion
packet, which is the primary entry point for a remote router.

To safely handle network failures, a client can and should retry this RPC
after a timeout or disconnection. Retries MUST use the exact same
attempt_id to allow the server to correctly detect duplicate requests.

A client interacting with this RPC must handle four distinct categories of
outcomes, communicated via gRPC status codes:

1. SUCCESS (gRPC code OK): A definitive confirmation that the HTLC has
been successfully dispatched. The client can proceed to track the
payment's final result via the `TrackOnion` RPC.

2. DUPLICATE ACKNOWLEDGMENT (gRPC code AlreadyExists): A definitive
acknowledgment that a request with the same attempt_id has already
been successfully processed. A retrying client should interpret this
as a success and proceed to tracking the payment's result.

3. AMBIGUOUS FAILURE (gRPC code Unavailable or DeadlineExceeded): An
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we should add that context cancelled is also such an error and that this should be the fallback if an error can't be classified in terms of the other cases (we could make this the last point), since it's always safe to retry this RPC.

ambiguous error occurred (e.g., the server is shutting down or the
client timed out). The state of the HTLC dispatch is unknown. The
client MUST retry the exact same request to resolve the ambiguity.

4. DEFINITIVE FAILURE (gRPC code FailedPrecondition, InvalidArgument, etc.):
A definitive failure is a guarantee that the HTLC was not and will not be
dispatched. The client should fail the attempt and may retry with a new
route and/or new attempt_id.
*/
rpc SendOnion (SendOnionRequest) returns (SendOnionResponse);

Expand Down
3 changes: 2 additions & 1 deletion lnrpc/switchrpc/switch.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
},
"/v2/switch/onion": {
"post": {
"summary": "SendOnion attempts to make a payment via the specified onion. This\nmethod differs from SendPayment in that the instance need not be aware of\nthe full details of the payment route.",
"summary": "SendOnion provides an idempotent API for dispatching a pre-formed onion\npacket, which is the primary entry point for a remote router.",
"description": "To safely handle network failures, a client can and should retry this RPC\nafter a timeout or disconnection. Retries MUST use the exact same\nattempt_id to allow the server to correctly detect duplicate requests.\n\nA client interacting with this RPC must handle four distinct categories of\noutcomes, communicated via gRPC status codes:\n\n1. SUCCESS (gRPC code OK): A definitive confirmation that the HTLC has\nbeen successfully dispatched. The client can proceed to track the\npayment's final result via the `TrackOnion` RPC.\n\n2. DUPLICATE ACKNOWLEDGMENT (gRPC code AlreadyExists): A definitive\nacknowledgment that a request with the same attempt_id has already\nbeen successfully processed. A retrying client should interpret this\nas a success and proceed to tracking the payment's result.\n\n3. AMBIGUOUS FAILURE (gRPC code Unavailable or DeadlineExceeded): An\nambiguous error occurred (e.g., the server is shutting down or the\nclient timed out). The state of the HTLC dispatch is unknown. The\nclient MUST retry the exact same request to resolve the ambiguity.\n\n4. DEFINITIVE FAILURE (gRPC code FailedPrecondition, InvalidArgument, etc.):\nA definitive failure is a guarantee that the HTLC was not and will not be\ndispatched. The client should fail the attempt and may retry with a new\nroute and/or new attempt_id.",
"operationId": "Switch_SendOnion",
"responses": {
"200": {
Expand Down
62 changes: 56 additions & 6 deletions lnrpc/switchrpc/switch_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading