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

LND lsp CLN client compatibility #134

Merged
merged 2 commits into from
Oct 18, 2023
Merged
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 .github/actions/setup-clightning/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ runs:
- name: Install dependencies
if: steps.cache-core-lightning.outputs.cache-hit != 'true'
run: |
sudo apt-get update -y
sudo apt-get install -y autoconf automake build-essential git libtool libgmp-dev libsqlite3-dev python3 python3-pip net-tools zlib1g-dev libsodium-dev gettext valgrind libpq-dev shellcheck cppcheck libsecp256k1-dev jq
sudo apt-get remove -y protobuf-compiler
curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.12.0/protoc-3.12.0-linux-x86_64.zip
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/integration_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
- setup-lnd-lsp
- setup-cln
- build-lspd
name: test ${{ matrix.implementation }} ${{ matrix.test }}
name: test ${{ matrix.lsp }}-lsp ${{ matrix.client }}-client ${{ matrix.test }}
strategy:
max-parallel: 6
matrix:
Expand All @@ -103,19 +103,27 @@ jobs:
testOfflineNotificationRegularForward,
testOfflineNotificationZeroConfChannel,
]
implementation: [
lsp: [
LND,
CLN
]
client: [
LND,
CLN
]
exclude:
- lsp: CLN
client: LND

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Run and Process Test State
uses: ./.github/actions/test-lspd
with:
TESTRE: "TestLspd/${{ matrix.implementation }}-lspd:_${{ matrix.test }}"
artifact-name: TestLspd-${{ matrix.implementation }}-lspd_${{ matrix.test }}
TESTRE: "TestLspd/${{ matrix.lsp }}-lsp-${{ matrix.client}}-client:_${{ matrix.test }}"
artifact-name: TestLspd-${{ matrix.lsp }}-lsp-${{ matrix.client}}-client_${{ matrix.test }}
bitcoin-version: ${{ env.BITCOIN_VERSION }}
LSP_REF: ${{ env.LSP_REF }}
CLIENT_REF: ${{ env.CLIENT_REF }}
Expand Down
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ type NodeConfig struct {
// configured node, so it's obvious which node an rpc call is meant for.
Tokens []string `json:"tokens"`

// If the used token is in the LegacyOnionTokens array, the forwarded htlc
// will have the legacy onion format. As per the time of writing breezmobile
// requires the legacy onion format.
LegacyOnionTokens []string `json:"legacyOnionTokens"`

// The network location of the lightning node, e.g. `12.34.56.78:9012` or
// `localhost:10011`
Host string `json:"host"`
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.19

require (
github.com/aws/aws-sdk-go v1.34.0
github.com/breez/lntest v0.0.26
github.com/breez/lntest v0.0.28
github.com/btcsuite/btcd v0.23.5-0.20230228185050-38331963bddd
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2
Expand Down
34 changes: 19 additions & 15 deletions interceptor/intercept.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/breez/lspd/lightning"
"github.com/breez/lspd/notifications"
"github.com/btcsuite/btcd/wire"
"golang.org/x/exp/slices"
"golang.org/x/sync/singleflight"
)

Expand All @@ -35,14 +36,15 @@ var (
)

type InterceptResult struct {
Action InterceptAction
FailureCode InterceptFailureCode
Destination []byte
AmountMsat uint64
TotalAmountMsat uint64
ChannelPoint *wire.OutPoint
ChannelId uint64
PaymentSecret []byte
Action InterceptAction
FailureCode InterceptFailureCode
Destination []byte
AmountMsat uint64
TotalAmountMsat uint64
ChannelPoint *wire.OutPoint
ChannelId uint64
PaymentSecret []byte
UseLegacyOnionBlob bool
}

type Interceptor struct {
Expand Down Expand Up @@ -257,14 +259,16 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [
channelID = uint64(chanResult.InitialChannelID)
}

useLegacyOnionBlob := slices.Contains(i.config.LegacyOnionTokens, token)
return InterceptResult{
Action: INTERCEPT_RESUME_WITH_ONION,
Destination: destination,
ChannelPoint: channelPoint,
ChannelId: channelID,
PaymentSecret: paymentSecret,
AmountMsat: uint64(amt),
TotalAmountMsat: uint64(outgoingAmountMsat),
Action: INTERCEPT_RESUME_WITH_ONION,
Destination: destination,
ChannelPoint: channelPoint,
ChannelId: channelID,
PaymentSecret: paymentSecret,
AmountMsat: uint64(amt),
TotalAmountMsat: uint64(outgoingAmountMsat),
UseLegacyOnionBlob: useLegacyOnionBlob,
}, nil
}

Expand Down
3 changes: 3 additions & 0 deletions itest/cln_breez_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ def on_openchannel(openchannel, plugin, **kwargs):
plugin.log(repr(openchannel))
mindepth = int(0)

if openchannel['funding_msat'] == 200000000:
return {'result': 'continue'}

plugin.log(f"This peer is in the zeroconf allowlist, setting mindepth={mindepth}")
return {'result': 'continue', 'mindepth': mindepth}

Expand Down
8 changes: 7 additions & 1 deletion itest/lspd_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
ecies "github.com/ecies/go/v2"
"github.com/golang/protobuf/proto"
"github.com/jackc/pgx/v4/pgxpool"
"google.golang.org/grpc/metadata"
)

var (
Expand Down Expand Up @@ -121,6 +122,10 @@ func newLspd(h *lntest.TestHarness, mem *mempoolApi, name string, nodeConfig *co
if nodeConfig.MinConfs != nil {
conf.MinConfs = nodeConfig.MinConfs
}

if nodeConfig.LegacyOnionTokens != nil {
conf.LegacyOnionTokens = nodeConfig.LegacyOnionTokens
}
}

log.Printf("%s: node config: %+v", name, conf)
Expand Down Expand Up @@ -271,9 +276,10 @@ func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation, continueOn
encrypted, err := ecies.Encrypt(l.EciesPublicKey(), serialized)
lntest.CheckError(l.Harness().T, err)

ctx := metadata.AppendToOutgoingContext(l.Harness().Ctx, "authorization", "Bearer hello")
log.Printf("Registering payment")
_, err = l.Rpc().RegisterPayment(
l.Harness().Ctx,
ctx,
&lspd.RegisterPaymentRequest{
Blob: encrypted,
},
Expand Down
15 changes: 13 additions & 2 deletions itest/lspd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,19 @@ var defaultTimeout time.Duration = time.Second * 120

func TestLspd(t *testing.T) {
testCases := allTestCases
runTests(t, testCases, "LND-lspd", lndLspFunc, lndClientFunc)
runTests(t, testCases, "CLN-lspd", clnLspFunc, clnClientFunc)
runTests(t, testCases, "LND-lsp-CLN-client", lndLspFunc, clnClientFunc)
runTests(t, testCases, "LND-lsp-LND-client", legacyOnionLndLspFunc, lndClientFunc)
runTests(t, testCases, "CLN-lsp-CLN-client", clnLspFunc, clnClientFunc)
}

func legacyOnionLndLspFunc(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, c *config.NodeConfig) LspNode {
cfg := c
if cfg == nil {
cfg = &config.NodeConfig{}
}

cfg.LegacyOnionTokens = []string{"hello"}
return lndLspFunc(h, m, mem, cfg)
}

func lndLspFunc(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, c *config.NodeConfig) LspNode {
Expand Down
19 changes: 18 additions & 1 deletion itest/notification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,23 @@ func testOfflineNotificationRegularForward(p *testParams) {
})
log.Printf(bobInvoice.Bolt11)

invoiceWithHint := bobInvoice.Bolt11
if !ContainsHopHint(p.t, bobInvoice.Bolt11) {
chans := p.BreezClient().Node().GetChannels()
assert.Len(p.t, chans, 1)

var id lntest.ShortChannelID
if chans[0].RemoteAlias != nil {
id = *chans[0].RemoteAlias
} else if chans[0].LocalAlias != nil {
id = *chans[0].LocalAlias
} else {
id = chans[0].ShortChannelID
}
invoiceWithHint = AddHopHint(p.BreezClient(), bobInvoice.Bolt11, p.Lsp(), id, nil)
}
log.Printf("invoice with hint: %v", invoiceWithHint)

log.Printf("Bob going offline")
p.BreezClient().Stop()

Expand All @@ -176,7 +193,7 @@ func testOfflineNotificationRegularForward(p *testParams) {
<-time.After(htlcInterceptorDelay)

log.Printf("Alice paying")
payResp := alice.Pay(bobInvoice.Bolt11)
payResp := alice.Pay(invoiceWithHint)
invoiceResult := p.BreezClient().Node().GetInvoice(bobInvoice.PaymentHash)

assert.Equal(p.t, payResp.PaymentPreimage, invoiceResult.PaymentPreimage)
Expand Down
19 changes: 18 additions & 1 deletion itest/regular_forward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,25 @@ func testRegularForward(p *testParams) {
})
log.Printf(bobInvoice.Bolt11)

invoiceWithHint := bobInvoice.Bolt11
if !ContainsHopHint(p.t, bobInvoice.Bolt11) {
chans := p.BreezClient().Node().GetChannels()
assert.Len(p.t, chans, 1)

var id lntest.ShortChannelID
if chans[0].RemoteAlias != nil {
id = *chans[0].RemoteAlias
} else if chans[0].LocalAlias != nil {
id = *chans[0].LocalAlias
} else {
id = chans[0].ShortChannelID
}
invoiceWithHint = AddHopHint(p.BreezClient(), bobInvoice.Bolt11, p.Lsp(), id, nil)
}
log.Printf("invoice with hint: %v", invoiceWithHint)

log.Printf("Alice paying")
payResp := alice.Pay(bobInvoice.Bolt11)
payResp := alice.Pay(invoiceWithHint)
invoiceResult := p.BreezClient().Node().GetInvoice(bobInvoice.PaymentHash)

assert.Equal(p.t, payResp.PaymentPreimage, invoiceResult.PaymentPreimage)
Expand Down
6 changes: 6 additions & 0 deletions itest/zero_reserve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,10 @@ func testZeroReserve(p *testParams) {
assert.Equal(p.t, c.RemoteReserveMsat, c.CapacityMsat/100)
log.Printf("local reserve: %d, remote reserve: %d", c.LocalReserveMsat, c.RemoteReserveMsat)
assert.Zero(p.t, c.LocalReserveMsat)

lspInvoice := p.lsp.LightningNode().CreateBolt11Invoice(&lntest.CreateInvoiceOptions{
AmountMsat: innerAmountMsat - 1,
})
<-time.After(time.Second * 1)
p.BreezClient().Node().Pay(lspInvoice.Bolt11)
}
42 changes: 25 additions & 17 deletions lnd/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,23 +139,7 @@ func (i *LndHtlcInterceptor) intercept() error {
interceptResult := i.interceptor.Intercept(&scid, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry)
switch interceptResult.Action {
case interceptor.INTERCEPT_RESUME_WITH_ONION:
onion, err := i.constructOnion(interceptResult, request.OutgoingExpiry, request.PaymentHash)
if err == nil {
interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: request.IncomingCircuitKey,
Action: routerrpc.ResolveHoldForwardAction_RESUME,
OutgoingAmountMsat: interceptResult.AmountMsat,
OutgoingRequestedChanId: uint64(interceptResult.ChannelId),
OnionBlob: onion,
})
} else {
interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: request.IncomingCircuitKey,
Action: routerrpc.ResolveHoldForwardAction_FAIL,
FailureCode: lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE,
})
}

interceptorClient.Send(i.createOnionResponse(interceptResult, request))
case interceptor.INTERCEPT_FAIL_HTLC_WITH_CODE:
interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: request.IncomingCircuitKey,
Expand Down Expand Up @@ -257,3 +241,27 @@ func (i *LndHtlcInterceptor) constructOnion(

return onionBlob.Bytes(), nil
}

func (i *LndHtlcInterceptor) createOnionResponse(interceptResult interceptor.InterceptResult, request *routerrpc.ForwardHtlcInterceptRequest) *routerrpc.ForwardHtlcInterceptResponse {
onionBlob := request.OnionBlob

if interceptResult.UseLegacyOnionBlob {
var err error
onionBlob, err = i.constructOnion(interceptResult, request.OutgoingExpiry, request.PaymentHash)
if err != nil {
return &routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: request.IncomingCircuitKey,
Action: routerrpc.ResolveHoldForwardAction_FAIL,
FailureCode: lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE,
}
}
}

return &routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: request.IncomingCircuitKey,
Action: routerrpc.ResolveHoldForwardAction_RESUME,
OutgoingAmountMsat: interceptResult.AmountMsat,
OutgoingRequestedChanId: uint64(interceptResult.ChannelId),
OnionBlob: onionBlob,
}
}
Loading