Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: add BuildOnion, SendOnion, and TrackOnion RPCs #9489

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ run:
- neutrinorpc
- peersrpc
- signrpc
- switchrpc
- walletrpc
- watchtowerrpc
- kvdb_etcd
Expand Down
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module github.com/lightningnetwork/lnd

require (
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
github.com/RoaringBitmap/roaring/v2 v2.4.2
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344
github.com/andybalholm/brotli v1.0.4
github.com/btcsuite/btcd v0.24.3-0.20241210095828-e646d437e95b
Expand Down Expand Up @@ -67,6 +68,11 @@ require (
pgregory.net/rapid v1.1.0
)

require (
github.com/bits-and-blooms/bitset v1.12.0 // indirect
github.com/mschoch/smat v0.2.0 // indirect
)

require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:Gbu
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/RoaringBitmap/roaring/v2 v2.4.2 h1:ew/INI7HLRyYK+dCbF6FcUwoe2Q0q5HCV7WafY9ljBk=
github.com/RoaringBitmap/roaring/v2 v2.4.2/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
Expand All @@ -70,6 +72,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZtmFVPHmA=
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
Expand Down Expand Up @@ -503,6 +507,8 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
Expand Down Expand Up @@ -1039,6 +1045,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
Expand Down
36 changes: 22 additions & 14 deletions htlcswitch/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,20 @@ type scidAliasHandler interface {
zeroConfConfirmed() bool
}

// ChannelUpdateHandler is an interface that provides methods that allow
// sending lnwire.Message to the underlying link as well as querying state.
type ChannelUpdateHandler interface {
// HandleChannelUpdate handles the htlc requests as settle/add/fail
// which sent to us from remote peer we have a channel with.
//
// NOTE: This function MUST be non-blocking (or block as little as
// possible).
HandleChannelUpdate(lnwire.Message)
type ChannelInfoProvider interface {
// NOTE(calvin): These four methods seem to have use in the routerrpc
// implementation of the SendOnion RPC. Maybe split into separate
// interface?

// ChanID returns the channel ID for the channel link. The channel ID
// is a more compact representation of a channel's full outpoint.
ChanID() lnwire.ChannelID

// ShortChanID returns the short channel ID for the channel link. The
// short channel ID encodes the exact location in the main chain that
// the original funding output can be found.
ShortChanID() lnwire.ShortChannelID

// Bandwidth returns the amount of milli-satoshis which current link
// might pass through channel link. The value returned from this method
// represents the up to date available flow through the channel. This
Expand All @@ -142,6 +142,19 @@ type ChannelUpdateHandler interface {
// htlc to the channel, taking the amount of the htlc to add as a
// parameter.
MayAddOutgoingHtlc(lnwire.MilliSatoshi) error
}

// ChannelUpdateHandler is an interface that provides methods that allow
// sending lnwire.Message to the underlying link as well as querying state.
type ChannelUpdateHandler interface {
// HandleChannelUpdate handles the htlc requests as settle/add/fail
// which sent to us from remote peer we have a channel with.
//
// NOTE: This function MUST be non-blocking (or block as little as
// possible).
HandleChannelUpdate(lnwire.Message)

ChannelInfoProvider

// EnableAdds sets the ChannelUpdateHandler state to allow
// UpdateAddHtlc's in the specified direction. It returns true if the
Expand Down Expand Up @@ -250,11 +263,6 @@ type ChannelLink interface {
// ChannelPoint returns the channel outpoint for the channel link.
ChannelPoint() wire.OutPoint

// ShortChanID returns the short channel ID for the channel link. The
// short channel ID encodes the exact location in the main chain that
// the original funding output can be found.
ShortChanID() lnwire.ShortChannelID

// UpdateShortChanID updates the short channel ID for a link. This may
// be required in the event that a link is created before the short
// chan ID for it is known, or a re-org occurs, and the funding
Expand Down
5 changes: 5 additions & 0 deletions htlcswitch/payment_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ type PaymentResult struct {
// irrevocably canceled. If the payment failed during forwarding, this
// error will be a *ForwardingError.
Error error

// EncryptedError will contain the raw bytes of an encrypted error
// in the event of a payment failure if the switch is instructed to
// defer error processing to external sub-systems.
EncryptedError []byte
}

// networkResult is the raw result received from the network after a payment
Expand Down
34 changes: 33 additions & 1 deletion htlcswitch/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,9 @@ func (s *Switch) handleLocalResponse(pkt *htlcPacket) {
}

// extractResult uses the given deobfuscator to extract the payment result from
// the given network message.
// the given network message. If the deobfuscator is not present, then we'll
// return the onion-encrypted blob that details why the HTLC was failed. This
// blob is only fully decryptable by the entity which built the onion packet.
func (s *Switch) extractResult(deobfuscator ErrorDecrypter, n *networkResult,
attemptID uint64, paymentHash lntypes.Hash) (*PaymentResult, error) {

Expand All @@ -1020,6 +1022,12 @@ func (s *Switch) extractResult(deobfuscator ErrorDecrypter, n *networkResult,
// We've received a fail update which means we can finalize the
// user payment and return fail response.
case *lnwire.UpdateFailHTLC:
if deobfuscator == nil {
return &PaymentResult{
EncryptedError: htlc.Reason,
}, nil
}

// TODO(yy): construct deobfuscator here to avoid creating it
// in paymentLifecycle even for settled HTLCs.
paymentErr := s.parseFailedPayment(
Expand Down Expand Up @@ -2460,6 +2468,30 @@ func (s *Switch) GetLinksByInterface(hop [33]byte) ([]ChannelUpdateHandler,
return handlers, nil
}

// GetLinksByPubkey fetches all the links connected to a particular node
// identified by the serialized compressed form of its public key.
func (s *Switch) GetLinksByPubkey(hop [33]byte) ([]ChannelInfoProvider,
error) {

s.indexMtx.RLock()
defer s.indexMtx.RUnlock()

links, err := s.getLinks(hop)
if err != nil {
return nil, err
}

handlers := make([]ChannelInfoProvider, 0, len(links))

// Range over the returned []ChannelLink to convert them into
// []ChannelUpdateHandler.
for _, link := range links {
handlers = append(handlers, link)
}

return handlers, nil
}

// getLinks is function which returns the channel links of the peer by hop
// destination id.
//
Expand Down
82 changes: 82 additions & 0 deletions htlcswitch/switch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5555,3 +5555,85 @@

require.NoError(t, interceptSwitch.Stop())
}

func TestExtractResult(t *testing.T) {
mockEncryptedReason := []byte("mock-encrypted-reason")
mockPaymentPreimage := [32]byte{1, 2, 3, 4, 5}
mockPaymentHash := [32]byte{6, 7, 8, 9, 10}
mockAttemptID := uint64(42)

mockNetworkResult := func(msg lnwire.Message) *networkResult {
return &networkResult{
msg: msg,
unencrypted: false,
isResolution: false,
}
}

// Initialize the switch with a temporary DB.
s, err := initSwitchWithTempDB(t, testStartingHeight)
require.NoError(t, err, "unable to init switch")

err = s.Start()
require.NoError(t, err, "unable to start switch")
defer s.Stop()

Check failure on line 5579 in htlcswitch/switch_test.go

View workflow job for this annotation

GitHub Actions / lint code

Error return value of `s.Stop` is not checked (errcheck)

tests := []struct {
name string
deobfuscator ErrorDecrypter
networkResult *networkResult
expected *PaymentResult
expectsError bool
}{
{
name: "Nil Deobfuscator - UpdateFailHTLC",
deobfuscator: nil,
networkResult: mockNetworkResult(
&lnwire.UpdateFailHTLC{
Reason: mockEncryptedReason},
),
expected: &PaymentResult{
EncryptedError: mockEncryptedReason,
},
expectsError: false,
},
{
name: "UpdateFulfillHTLC",
deobfuscator: nil,
networkResult: mockNetworkResult(
&lnwire.UpdateFulfillHTLC{
PaymentPreimage: mockPaymentPreimage},
),
expected: &PaymentResult{
Preimage: mockPaymentPreimage,
},
expectsError: false,
},
{
name: "Unknown Response Type",
deobfuscator: nil,
networkResult: mockNetworkResult(
&lnwire.UpdateAddHTLC{},
),
expected: nil,
expectsError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := s.extractResult(
tt.deobfuscator, tt.networkResult,
mockAttemptID, mockPaymentHash,
)

if tt.expectsError {
require.Error(t, err)
return
}

require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}
16 changes: 16 additions & 0 deletions itest/list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,22 @@ var allTestCases = []*lntest.TestCase{
Name: "invoice migration",
TestFunc: testInvoiceMigration,
},
{
Name: "send onion",
TestFunc: testSendOnion,
},
{
Name: "send onion twice",
TestFunc: testSendOnionTwice,
},
{
Name: "send then track",
TestFunc: testTrackThenSend,
},
{
Name: "track onion",
TestFunc: testTrackOnion,
},
}

// appendPrefixed is used to add a prefix to each test name in the subtests
Expand Down
Loading
Loading