Skip to content

Commit 39a764c

Browse files
committed
tapcfg+tapchannel: validate incoming channel in invoice mgr
We need to make sure an asset HTLC actually comes through the correct channel that commits to that asset in the first place.
1 parent f1d133e commit 39a764c

File tree

3 files changed

+76
-10
lines changed

3 files changed

+76
-10
lines changed

tapcfg/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
487487
ChainParams: &tapChainParams,
488488
InvoiceHtlcModifier: lndInvoicesClient,
489489
RfqManager: rfqManager,
490+
LightningClient: lndServices.Client,
490491
},
491492
)
492493
auxChanCloser := tapchannel.NewAuxChanCloser(

tapchannel/aux_invoice_manager.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tapchannel
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"sync"
78

@@ -13,6 +14,7 @@ import (
1314
"github.com/lightninglabs/taproot-assets/rfqmath"
1415
"github.com/lightninglabs/taproot-assets/rfqmsg"
1516
"github.com/lightninglabs/taproot-assets/taprpc"
17+
"github.com/lightningnetwork/lnd/invoices"
1618
"github.com/lightningnetwork/lnd/lnrpc"
1719
"github.com/lightningnetwork/lnd/lnwire"
1820
"github.com/lightningnetwork/lnd/routing/route"
@@ -77,6 +79,10 @@ type InvoiceManagerConfig struct {
7779
// accepted quotes for determining the incoming value of invoice related
7880
// HTLCs.
7981
RfqManager RfqManager
82+
83+
// LndClient is the lnd client that will be used to interact with the
84+
// lnd node.
85+
LightningClient lndclient.LightningClient
8086
}
8187

8288
// AuxInvoiceManager is a Taproot Asset auxiliary invoice manager that can be
@@ -206,7 +212,7 @@ func (s *AuxInvoiceManager) handleInvoiceAccept(ctx context.Context,
206212
}
207213

208214
// We now run some validation checks on the asset HTLC.
209-
err = s.validateAssetHTLC(ctx, htlc)
215+
err = s.validateAssetHTLC(ctx, htlc, resp.CircuitKey)
210216
if err != nil {
211217
log.Errorf("Failed to validate asset HTLC: %v", err)
212218

@@ -390,7 +396,7 @@ func isAssetInvoice(invoice *lnrpc.Invoice, rfqLookup RfqLookup) bool {
390396

391397
// validateAssetHTLC runs a couple of checks on the provided asset HTLC.
392398
func (s *AuxInvoiceManager) validateAssetHTLC(ctx context.Context,
393-
htlc *rfqmsg.Htlc) error {
399+
htlc *rfqmsg.Htlc, circuitKey invoices.CircuitKey) error {
394400

395401
rfqID := htlc.RfqID.ValOpt().UnsafeFromSome()
396402

@@ -403,6 +409,7 @@ func (s *AuxInvoiceManager) validateAssetHTLC(ctx context.Context,
403409

404410
// Check for each of the asset balances of the HTLC that the identifier
405411
// matches that of the RFQ quote.
412+
assetIDs := fn.NewSet[asset.ID]()
406413
for _, v := range htlc.Balances() {
407414
match, err := s.cfg.RfqManager.AssetMatchesSpecifier(
408415
ctx, identifier, v.AssetID.Val,
@@ -415,6 +422,47 @@ func (s *AuxInvoiceManager) validateAssetHTLC(ctx context.Context,
415422
return fmt.Errorf("asset ID %s does not match %s",
416423
v.AssetID.Val.String(), identifier.String())
417424
}
425+
426+
assetIDs.Add(v.AssetID.Val)
427+
}
428+
429+
// We also need to validate that the HTLC is actually the correct asset
430+
// and arrived through the correct asset channel.
431+
channels, err := s.cfg.LightningClient.ListChannels(ctx, true, false)
432+
if err != nil {
433+
return fmt.Errorf("unable to list channels: %w", err)
434+
}
435+
436+
var inboundChannel *lndclient.ChannelInfo
437+
for _, channel := range channels {
438+
if channel.ChannelID == circuitKey.ChanID.ToUint64() {
439+
inboundChannel = &channel
440+
break
441+
}
442+
}
443+
444+
if inboundChannel == nil {
445+
return fmt.Errorf("unable to find channel with short channel "+
446+
"ID %d", circuitKey.ChanID.ToUint64())
447+
}
448+
449+
if len(inboundChannel.CustomChannelData) == 0 {
450+
return fmt.Errorf("channel %d does not have custom channel "+
451+
"data, can't accept asset HTLC over non-asset channel",
452+
inboundChannel.ChannelID)
453+
}
454+
455+
var assetData rfqmsg.JsonAssetChannel
456+
err = json.Unmarshal(inboundChannel.CustomChannelData, &assetData)
457+
if err != nil {
458+
return fmt.Errorf("unable to unmarshal channel asset data: %w",
459+
err)
460+
}
461+
462+
if !assetData.HasAllAssetIDs(assetIDs) {
463+
return fmt.Errorf("channel %d does not have all asset IDs "+
464+
"required for HTLC settlement",
465+
inboundChannel.ChannelID)
418466
}
419467

420468
return nil

tapchannel/aux_invoice_manager_test.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,9 @@ func (m *mockHtlcModifierProperty) HtlcModifier(ctx context.Context,
298298
}
299299
} else {
300300
if assetValueMsat != res.AmtPaid {
301-
m.t.Errorf("unexpected final asset value")
301+
m.t.Errorf("unexpected final asset value, "+
302+
"wanted %d, got %d", assetValueMsat,
303+
res.AmtPaid)
302304
}
303305
}
304306
}
@@ -309,6 +311,19 @@ func (m *mockHtlcModifierProperty) HtlcModifier(ctx context.Context,
309311
return nil
310312
}
311313

314+
type mockLndClient struct {
315+
lndclient.LightningClient
316+
317+
channels []lndclient.ChannelInfo
318+
}
319+
320+
// ListChannels retrieves all channels of the backing lnd node.
321+
func (m *mockLndClient) ListChannels(_ context.Context, _,
322+
_ bool) ([]lndclient.ChannelInfo, error) {
323+
324+
return m.channels, nil
325+
}
326+
312327
// TestAuxInvoiceManager tests that the htlc modifications of the aux invoice
313328
// manager align with our expectations.
314329
func TestAuxInvoiceManager(t *testing.T) {
@@ -609,6 +624,7 @@ func TestAuxInvoiceManager(t *testing.T) {
609624
peerBuyQuotes: testCase.buyQuotes,
610625
localSellQuotes: testCase.sellQuotes,
611626
}
627+
mockLnd := &mockLndClient{}
612628

613629
done := make(chan bool)
614630

@@ -626,6 +642,7 @@ func TestAuxInvoiceManager(t *testing.T) {
626642
ChainParams: testChainParams,
627643
InvoiceHtlcModifier: mockModifier,
628644
RfqManager: mockRfq,
645+
LightningClient: mockLnd,
629646
},
630647
)
631648

@@ -903,6 +920,7 @@ func testInvoiceManager(t *rapid.T) {
903920
mockRfq := &mockRfqManager{
904921
peerBuyQuotes: rfqMap,
905922
}
923+
mockLnd := &mockLndClient{}
906924

907925
done := make(chan bool)
908926

@@ -913,13 +931,12 @@ func testInvoiceManager(t *rapid.T) {
913931
t: t,
914932
}
915933

916-
manager := NewAuxInvoiceManager(
917-
&InvoiceManagerConfig{
918-
ChainParams: testChainParams,
919-
InvoiceHtlcModifier: mockModifier,
920-
RfqManager: mockRfq,
921-
},
922-
)
934+
manager := NewAuxInvoiceManager(&InvoiceManagerConfig{
935+
ChainParams: testChainParams,
936+
InvoiceHtlcModifier: mockModifier,
937+
RfqManager: mockRfq,
938+
LightningClient: mockLnd,
939+
})
923940

924941
err := manager.Start()
925942
require.NoError(t, err)

0 commit comments

Comments
 (0)