Skip to content

Commit eeca76c

Browse files
authored
Merge pull request #1011 from lightninglabs/tap-channel-testing-bugfixes
Custom channel testing bugfixes
2 parents fca9d11 + 01be6ee commit eeca76c

File tree

13 files changed

+278
-201
lines changed

13 files changed

+278
-201
lines changed

config.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,6 @@ type Config struct {
197197

198198
UniverseStats universe.Telemetry
199199

200-
AuxLeafCreator *tapchannel.AuxLeafCreator
201-
202200
AuxLeafSigner *tapchannel.AuxLeafSigner
203201

204202
AuxFundingController *tapchannel.FundingController

fn/errors.go

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

33
import (
44
"context"
5+
"errors"
56
"strings"
67

78
"google.golang.org/grpc/codes"
@@ -44,3 +45,42 @@ func IsRpcErr(err error, candidate error) bool {
4445

4546
return strings.Contains(err.Error(), candidate.Error())
4647
}
48+
49+
// CriticalError is an error type that should be used for errors that are
50+
// critical and should cause the application to exit.
51+
type CriticalError struct {
52+
Err error
53+
}
54+
55+
// NewCriticalError creates a new CriticalError instance.
56+
func NewCriticalError(err error) *CriticalError {
57+
return &CriticalError{Err: err}
58+
}
59+
60+
// Error implements the error interface.
61+
func (e *CriticalError) Error() string {
62+
return e.Err.Error()
63+
}
64+
65+
// Unwrap implements the errors.Wrapper interface.
66+
func (e *CriticalError) Unwrap() error {
67+
return e.Err
68+
}
69+
70+
// ErrorAs behaves the same as `errors.As` except there's no need to declare
71+
// the target error as a variable first.
72+
// Instead of writing:
73+
//
74+
// var targetErr *TargetErr
75+
// errors.As(err, &targetErr)
76+
//
77+
// We can write:
78+
//
79+
// lnutils.ErrorAs[*TargetErr](err)
80+
//
81+
// To save us from declaring the target error variable.
82+
func ErrorAs[Target error](err error) bool {
83+
var targetErr Target
84+
85+
return errors.As(err, &targetErr)
86+
}

itest/tapd_harness.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,9 @@ func newTapdHarness(t *testing.T, ht *harnessTest, cfg tapdConfig,
222222
// Set the experimental config for the RFQ service.
223223
tapCfg.Experimental = &tapcfg.ExperimentalConfig{
224224
Rfq: rfq.CliConfig{
225-
PriceOracleAddress: rfq.MockPriceOracleServiceAddress,
226-
MockOracleCentPerSat: 5_820_600,
225+
//nolint:lll
226+
PriceOracleAddress: rfq.MockPriceOracleServiceAddress,
227+
MockOracleAssetsPerBTC: 5_820_600,
227228
},
228229
}
229230

rfq/cli.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import (
77
const (
88
MockPriceOracleServiceAddress = "use_mock_price_oracle_service_" +
99
"promise_to_not_use_on_mainnet"
10+
11+
// MinAssetsPerBTC is the minimum number of asset units that one BTC
12+
// should cost. If the value is lower, then one asset unit would cost
13+
// too much to be able to represent small amounts of satoshis. With this
14+
// value, one asset unit would still cost 1k sats.
15+
MinAssetsPerBTC = 100_000
1016
)
1117

1218
// CliConfig is a struct that holds tapd cli configuration options for the RFQ
@@ -18,18 +24,21 @@ type CliConfig struct {
1824

1925
SkipAcceptQuotePriceCheck bool `long:"skipacceptquotepricecheck" description:"Accept any price quote returned by RFQ peer, skipping price validation"`
2026

21-
MockOracleCentPerSat uint64 `long:"mockoraclecentpersat" description:"Mock price oracle static USD cent per sat rate"`
27+
MockOracleAssetsPerBTC uint64 `long:"mockoracleassetsperbtc" description:"Mock price oracle static asset units per BTC rate (for example number of USD cents per BTC if one asset unit represents a USD cent); whole numbers only, use either this or mockoraclesatsperasset depending on required precision"`
28+
29+
MockOracleSatsPerAsset uint64 `long:"mockoraclesatsperasset" description:"Mock price oracle static satoshis per asset unit rate (for example number of satoshis to pay for one USD cent if one asset unit represents a USD cent); whole numbers only, use either this or mockoracleassetsperbtc depending on required precision"`
2230
}
2331

2432
// Validate returns an error if the configuration is invalid.
2533
func (c *CliConfig) Validate() error {
2634
// If the user has specified a mock oracle USD per BTC rate but the
2735
// price oracle address is not the mock price oracle service address,
2836
// then we'll return an error.
29-
if c.MockOracleCentPerSat > 0 &&
37+
if (c.MockOracleAssetsPerBTC > 0 || c.MockOracleSatsPerAsset > 0) &&
3038
c.PriceOracleAddress != MockPriceOracleServiceAddress {
3139

32-
return fmt.Errorf("mockoraclecentpersat can only be used "+
40+
return fmt.Errorf("mockoracleassetsperbtc or "+
41+
"mockoraclesatsperasset can only be used "+
3342
"with the mock price oracle service, set "+
3443
"priceoracleaddress to %s",
3544
MockPriceOracleServiceAddress)
@@ -39,12 +48,35 @@ func (c *CliConfig) Validate() error {
3948
// has not set the mock oracle USD per BTC rate, then we'll return an
4049
// error.
4150
if c.PriceOracleAddress == MockPriceOracleServiceAddress &&
42-
c.MockOracleCentPerSat == 0 {
51+
c.MockOracleAssetsPerBTC == 0 && c.MockOracleSatsPerAsset == 0 {
4352

44-
return fmt.Errorf("mockoraclecentpersat must be set when " +
53+
return fmt.Errorf("mockoracleassetsperbtc or " +
54+
"mockoraclesatsperasset must be set when " +
4555
"using the mock price oracle service")
4656
}
4757

58+
// Only one of the mock oracle rates can be set.
59+
if c.MockOracleAssetsPerBTC > 0 && c.MockOracleSatsPerAsset > 0 {
60+
return fmt.Errorf("only one of mockoracleassetsperbtc or " +
61+
"mockoraclesatsperasset can be set")
62+
}
63+
64+
// The MockOracleAssetsPerBTC is more precise for tracking the actual
65+
// BTC price but less optimal for just specifying a dummy or test rate
66+
// for an asset that isn't BTC. The smaller the value, the more one
67+
// asset costs. If we allowed a value of 1, then one asset unit would
68+
// cost 1 BTC, which cannot really easily be transported over the
69+
// network. So we require a value of at least 100k, which would still
70+
// mean that one asset unit costs 1k sats.
71+
if c.MockOracleAssetsPerBTC > 0 &&
72+
c.MockOracleAssetsPerBTC < MinAssetsPerBTC {
73+
74+
return fmt.Errorf("mockoracleassetsperbtc must be at least "+
75+
"%d asset units per BTC, otherwise one asset "+
76+
"unit would cost more than 1k sats per unit",
77+
MinAssetsPerBTC)
78+
}
79+
4880
// Ensure that if the price oracle address not the mock price oracle
4981
// service address then it must be a valid gRPC address.
5082
if c.PriceOracleAddress != "" &&

rfq/manager.go

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,18 @@ func (m *Manager) startSubsystems(ctx context.Context) error {
239239
return err
240240
}
241241

242+
// handleError logs an error and sends it to the main server error channel if
243+
// it is a critical error.
244+
func (m *Manager) handleError(err error) {
245+
log.Errorf("Error in RFQ manager: %v", err)
246+
247+
// If the error is a critical error, send it to the main server error
248+
// channel, which will cause the daemon to shut down.
249+
if fn.ErrorAs[*fn.CriticalError](err) {
250+
m.cfg.ErrChan <- err
251+
}
252+
}
253+
242254
// Start attempts to start a new RFQ manager.
243255
func (m *Manager) Start() error {
244256
var startErr error
@@ -363,8 +375,10 @@ func (m *Manager) handleIncomingMessage(incomingMsg rfqmsg.IncomingMsg) error {
363375
*msg.Request.AssetID, msg.Peer,
364376
)
365377
if err != nil {
366-
m.cfg.ErrChan <- fmt.Errorf("error adding "+
367-
"local alias: %w", err)
378+
m.handleError(
379+
fmt.Errorf("error adding local alias: "+
380+
"%w", err),
381+
)
368382
return
369383
}
370384

@@ -488,7 +502,12 @@ func (m *Manager) addScidAlias(scidAlias uint64, assetID asset.ID,
488502
ctxb := context.Background()
489503
localChans, err := m.cfg.ChannelLister.ListChannels(ctxb)
490504
if err != nil {
491-
return fmt.Errorf("error listing local channels: %w", err)
505+
// Not being able to call lnd to add the alias is a critical
506+
// error, which warrants shutting down, as something is wrong.
507+
return fn.NewCriticalError(
508+
fmt.Errorf("add alias: error listing local channels: "+
509+
"%w", err),
510+
)
492511
}
493512

494513
// Filter for channels with the given peer.
@@ -534,15 +553,26 @@ func (m *Manager) addScidAlias(scidAlias uint64, assetID asset.ID,
534553
// At this point, if the base SCID is still not found, we return an
535554
// error. We can't map the SCID alias to a base SCID.
536555
if baseSCID == 0 {
537-
return fmt.Errorf("base SCID not found for asset: %v", assetID)
556+
return fmt.Errorf("add alias: base SCID not found for asset: "+
557+
"%v", assetID)
538558
}
539559

540560
log.Debugf("Adding SCID alias %d for base SCID %d", scidAlias, baseSCID)
541561

542-
return m.cfg.AliasManager.AddLocalAlias(
562+
err = m.cfg.AliasManager.AddLocalAlias(
543563
ctxb, lnwire.NewShortChanIDFromInt(scidAlias),
544564
lnwire.NewShortChanIDFromInt(baseSCID),
545565
)
566+
if err != nil {
567+
// Not being able to call lnd to add the alias is a critical
568+
// error, which warrants shutting down, as something is wrong.
569+
return fn.NewCriticalError(
570+
fmt.Errorf("add alias: error adding SCID alias to "+
571+
"lnd alias manager: %w", err),
572+
)
573+
}
574+
575+
return nil
546576
}
547577

548578
// mainEventLoop is the main event loop of the RFQ manager.
@@ -556,8 +586,10 @@ func (m *Manager) mainEventLoop() {
556586

557587
err := m.handleIncomingMessage(incomingMsg)
558588
if err != nil {
559-
m.cfg.ErrChan <- fmt.Errorf("failed to "+
560-
"handle incoming message: %w", err)
589+
m.handleError(
590+
fmt.Errorf("failed to handle "+
591+
"incoming message: %w", err),
592+
)
561593
}
562594

563595
// Handle outgoing message.
@@ -567,8 +599,10 @@ func (m *Manager) mainEventLoop() {
567599

568600
err := m.handleOutgoingMessage(outgoingMsg)
569601
if err != nil {
570-
m.cfg.ErrChan <- fmt.Errorf("failed to "+
571-
"handle outgoing message: %w", err)
602+
m.handleError(
603+
fmt.Errorf("failed to handle outgoing "+
604+
"message: %w", err),
605+
)
572606
}
573607

574608
case acceptHtlcEvent := <-m.acceptHtlcEvents:
@@ -577,12 +611,12 @@ func (m *Manager) mainEventLoop() {
577611

578612
// Handle subsystem errors.
579613
case err := <-m.subsystemErrChan:
580-
log.Errorf("Manager main event loop received "+
581-
"subsystem error: %v", err)
582-
583-
// Report the subsystem error to the main server.
584-
m.cfg.ErrChan <- fmt.Errorf("encountered RFQ "+
585-
"subsystem error: %w", err)
614+
// Report the subsystem error to the main server, in
615+
// case the root cause is a critical error.
616+
m.handleError(
617+
fmt.Errorf("encountered RFQ subsystem error "+
618+
"in main event loop: %w", err),
619+
)
586620

587621
case <-m.Quit:
588622
log.Debug("Manager main event loop has received the " +

rfq/oracle.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -368,15 +368,29 @@ var _ PriceOracle = (*RpcPriceOracle)(nil)
368368
// MockPriceOracle is a mock implementation of the PriceOracle interface.
369369
// It returns the suggested rate as the exchange rate.
370370
type MockPriceOracle struct {
371-
expiryDelay uint64
372-
usdPerBTC uint64
371+
expiryDelay uint64
372+
mSatPerAsset lnwire.MilliSatoshi
373373
}
374374

375375
// NewMockPriceOracle creates a new mock price oracle.
376-
func NewMockPriceOracle(expiryDelay, usdPerBTC uint64) *MockPriceOracle {
376+
func NewMockPriceOracle(expiryDelay, assetsPerBTC uint64) *MockPriceOracle {
377+
mSatPerAsset := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) /
378+
lnwire.MilliSatoshi(assetsPerBTC)
379+
380+
return &MockPriceOracle{
381+
expiryDelay: expiryDelay,
382+
mSatPerAsset: mSatPerAsset,
383+
}
384+
}
385+
386+
// NewMockPriceOracleSatPerAsset creates a new mock price oracle with a
387+
// specified satoshis per asset rate.
388+
func NewMockPriceOracleSatPerAsset(expiryDelay uint64,
389+
satPerAsset btcutil.Amount) *MockPriceOracle {
390+
377391
return &MockPriceOracle{
378-
expiryDelay: expiryDelay,
379-
usdPerBTC: usdPerBTC,
392+
expiryDelay: expiryDelay,
393+
mSatPerAsset: lnwire.NewMSatFromSatoshis(satPerAsset),
380394
}
381395
}
382396

@@ -388,11 +402,8 @@ func (m *MockPriceOracle) QueryAskPrice(_ context.Context,
388402
// Calculate the rate expiryDelay lifetime.
389403
expiry := uint64(time.Now().Unix()) + m.expiryDelay
390404

391-
mSatPerUsd := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) /
392-
lnwire.MilliSatoshi(m.usdPerBTC)
393-
394405
return &OracleAskResponse{
395-
AskPrice: &mSatPerUsd,
406+
AskPrice: &m.mSatPerAsset,
396407
Expiry: expiry,
397408
}, nil
398409
}
@@ -404,11 +415,8 @@ func (m *MockPriceOracle) QueryBidPrice(_ context.Context, _ *asset.ID,
404415
// Calculate the rate expiryDelay lifetime.
405416
expiry := uint64(time.Now().Unix()) + m.expiryDelay
406417

407-
mSatPerUsd := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) /
408-
lnwire.MilliSatoshi(m.usdPerBTC)
409-
410418
return &OracleBidResponse{
411-
BidPrice: &mSatPerUsd,
419+
BidPrice: &m.mSatPerAsset,
412420
Expiry: expiry,
413421
}, nil
414422
}

sample-tapd.conf

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,5 +320,13 @@
320320
; Accept any price quote returned by RFQ peer, skipping price validation
321321
; experimental.rfq.skipacceptquotepricecheck=false
322322

323-
; Mock price oracle static USD cent per sat rate
324-
; experimental.rfq.mockoraclecentpersat=
323+
; Mock price oracle static asset units per BTC rate (for example number of USD
324+
; cents per BTC if one asset unit represents a USD cent); whole numbers only,
325+
; use either this or mockoraclesatsperasset depending on required precision
326+
; experimental.rfq.mockoracleassetsperbtc=
327+
328+
; Mock price oracle static satoshis per asset unit rate (for example number of
329+
; satoshis to pay for one USD cent if one asset unit represents a USD cent);
330+
; whole numbers only, use either this or mockoracleassetsperbtc depending on
331+
; required precision
332+
; experimental.rfq.mockoraclesatsperasset=

0 commit comments

Comments
 (0)