From 353b7440398e6903b911cc3c40069565ba4d8e4b Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Tue, 16 Aug 2022 17:31:20 +0800 Subject: [PATCH] lntemp+itest: create interface `WebFeeService` This commit adds a new interface, `WebFeeService`, so external projects can create their own mocked fee services. --- lntemp/fee_service.go | 107 ++++++++++++++++++++++------------ lntemp/harness.go | 28 +++++---- lntemp/harness_setup.go | 6 +- lntest/itest/temp_lnd_test.go | 10 ++-- 4 files changed, 97 insertions(+), 54 deletions(-) diff --git a/lntemp/fee_service.go b/lntemp/fee_service.go index 17b4836f3..aac4cbd1d 100644 --- a/lntemp/fee_service.go +++ b/lntemp/fee_service.go @@ -11,46 +11,68 @@ import ( "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/stretchr/testify/require" ) +// WebFeeService defines an interface that's used to provide fee estimation +// service used in the integration tests. It must provide an URL so that a lnd +// node can be started with the flag `--feeurl` and uses the customized fee +// estimator. +type WebFeeService interface { + // Start starts the service. + Start() error + + // Stop stops the service. + Stop() error + + // URL returns the service's endpoint. + URL() string + + // SetFeeRate sets the estimated fee rate for a given confirmation + // target. + SetFeeRate(feeRate chainfee.SatPerKWeight, conf uint32) +} + const ( // feeServiceTarget is the confirmation target for which a fee estimate // is returned. Requests for higher confirmation targets will fall back // to this. feeServiceTarget = 1 + + // DefaultFeeRateSatPerKw specifies the default fee rate used in the + // tests. + DefaultFeeRateSatPerKw = 12500 ) -// feeService runs a web service that provides fee estimation information. -type feeService struct { - feeEstimates +// FeeService runs a web service that provides fee estimation information. +type FeeService struct { + *testing.T - t *testing.T - - srv *http.Server - wg sync.WaitGroup - - url string + feeRateMap map[uint32]uint32 + url string + srv *http.Server + wg sync.WaitGroup lock sync.Mutex } -// feeEstimates contains the current fee estimates. -type feeEstimates struct { - Fees map[uint32]uint32 `json:"fee_by_block_target"` -} +// Compile-time check for the WebFeeService interface. +var _ WebFeeService = (*FeeService)(nil) -// startFeeService spins up a go-routine to serve fee estimates. -func startFeeService(t *testing.T) *feeService { +// Start spins up a go-routine to serve fee estimates. +func NewFeeService(t *testing.T) *FeeService { port := lntest.NextAvailablePort() - f := feeService{ - t: t, + f := FeeService{ + T: t, url: fmt.Sprintf( "http://localhost:%v/fee-estimates.json", port, ), } // Initialize default fee estimate. - f.Fees = map[uint32]uint32{feeServiceTarget: 50000} + f.feeRateMap = map[uint32]uint32{ + feeServiceTarget: DefaultFeeRateSatPerKw, + } listenAddr := fmt.Sprintf(":%v", port) mux := http.NewServeMux() @@ -60,50 +82,59 @@ func startFeeService(t *testing.T) *feeService { Addr: listenAddr, Handler: mux, } + return &f +} +// Start starts the web server. +func (f *FeeService) Start() error { f.wg.Add(1) go func() { defer f.wg.Done() if err := f.srv.ListenAndServe(); err != http.ErrServerClosed { - f.t.Errorf("error: cannot start fee api: %v", err) + require.NoErrorf(f, err, "cannot start fee api") } }() - return &f + return nil } // handleRequest handles a client request for fee estimates. -func (f *feeService) handleRequest(w http.ResponseWriter, r *http.Request) { +func (f *FeeService) handleRequest(w http.ResponseWriter, r *http.Request) { f.lock.Lock() defer f.lock.Unlock() - bytes, err := json.Marshal(f.feeEstimates) - if err != nil { - f.t.Errorf("error: cannot serialize estimates: %v", err) - - return - } + bytes, err := json.Marshal( + struct { + Fees map[uint32]uint32 `json:"fee_by_block_target"` + }{ + Fees: f.feeRateMap, + }, + ) + require.NoErrorf(f, err, "cannot serialize estimates") _, err = io.WriteString(w, string(bytes)) - if err != nil { - f.t.Errorf("error: cannot send estimates: %v", err) - } + require.NoError(f, err, "cannot send estimates") } -// stop stops the web server. -func (f *feeService) stop() { - if err := f.srv.Shutdown(context.Background()); err != nil { - f.t.Errorf("error: cannot stop fee api: %v", err) - } +// Stop stops the web server. +func (f *FeeService) Stop() error { + err := f.srv.Shutdown(context.Background()) + require.NoError(f, err, "cannot stop fee api") f.wg.Wait() + return nil } -// setFee changes the current fee estimate for the fixed confirmation target. -func (f *feeService) setFee(fee chainfee.SatPerKWeight) { +// SetFeeRate sets a fee for the given confirmation target. +func (f *FeeService) SetFeeRate(fee chainfee.SatPerKWeight, conf uint32) { f.lock.Lock() defer f.lock.Unlock() - f.Fees[feeServiceTarget] = uint32(fee.FeePerKVByte()) + f.feeRateMap[conf] = uint32(fee.FeePerKVByte()) +} + +// URL returns the service endpoint. +func (f *FeeService) URL() string { + return f.url } diff --git a/lntemp/harness.go b/lntemp/harness.go index d9222443a..35f3f2d04 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -56,7 +56,7 @@ type HarnessTest struct { // feeService is a web service that provides external fee estimates to // lnd. - feeService *feeService + feeService WebFeeService // Channel for transmitting stderr output from failed lightning node // to main process. @@ -79,7 +79,7 @@ type HarnessTest struct { // NewHarnessTest creates a new instance of a harnessTest from a regular // testing.T instance. -func NewHarnessTest(t *testing.T, lndBinary string, +func NewHarnessTest(t *testing.T, lndBinary string, feeService WebFeeService, dbBackend lntest.DatabaseBackend) *HarnessTest { // Create the run context. @@ -87,10 +87,11 @@ func NewHarnessTest(t *testing.T, lndBinary string, manager := newNodeManager(lndBinary, dbBackend) return &HarnessTest{ - T: t, - manager: manager, - runCtx: ctxt, - cancel: cancel, + T: t, + manager: manager, + feeService: feeService, + runCtx: ctxt, + cancel: cancel, // We need to use buffered channel here as we don't want to // block sending errors. lndErrorChan: make(chan error, 10), @@ -118,11 +119,12 @@ func (h *HarnessTest) Start(chain node.BackendConfig, miner *HarnessMiner) { }() // Start the fee service. - h.feeService = startFeeService(h.T) + err := h.feeService.Start() + require.NoError(h, err, "failed to start fee service") // Assemble the node manager with chainBackend and feeServiceURL. h.manager.chainBackend = chain - h.manager.feeServiceURL = h.feeService.url + h.manager.feeServiceURL = h.feeService.URL() // Assemble the miner. h.Miner = miner @@ -229,7 +231,8 @@ func (h *HarnessTest) Stop() { close(h.lndErrorChan) // Stop the fee service. - h.feeService.stop() + err := h.feeService.Stop() + require.NoError(h, err, "failed to stop fee service") // Stop the chainBackend. h.stopChainBackend() @@ -436,8 +439,13 @@ func (h *HarnessTest) RestartNodeWithExtraArgs(hn *node.HarnessNode, } // SetFeeEstimate sets a fee rate to be returned from fee estimator. +// +// NOTE: this method will set the fee rate for a conf target of 1, which is the +// fallback fee rate for a `WebAPIEstimator` if a higher conf target's fee rate +// is not set. This means if the fee rate for conf target 6 is set, the fee +// estimator will use that value instead. func (h *HarnessTest) SetFeeEstimate(fee chainfee.SatPerKWeight) { - h.feeService.setFee(fee) + h.feeService.SetFeeRate(fee, 1) } // validateNodeState checks that the node doesn't have any uncleaned states diff --git a/lntemp/harness_setup.go b/lntemp/harness_setup.go index 161df7f4c..4165fe182 100644 --- a/lntemp/harness_setup.go +++ b/lntemp/harness_setup.go @@ -17,7 +17,9 @@ import ( // 3. start a chain backend(btcd, bitcoind, or neutrino). // 4. connect the miner and the chain backend. // 5. start the HarnessTest. -func SetupHarness(t *testing.T, binaryPath, dbBackendName string) *HarnessTest { +func SetupHarness(t *testing.T, binaryPath, dbBackendName string, + feeService WebFeeService) *HarnessTest { + t.Log("Setting up HarnessTest...") // Parse testing flags that influence our test execution. @@ -28,7 +30,7 @@ func SetupHarness(t *testing.T, binaryPath, dbBackendName string) *HarnessTest { dbBackend := prepareDbBackend(t, dbBackendName) // Create a new HarnessTest. - ht := NewHarnessTest(t, binaryPath, dbBackend) + ht := NewHarnessTest(t, binaryPath, feeService, dbBackend) // Init the miner. t.Log("Prepare the miner and mine blocks to activate segwit...") diff --git a/lntest/itest/temp_lnd_test.go b/lntest/itest/temp_lnd_test.go index 6c19c6e7f..ee8e2ca71 100644 --- a/lntest/itest/temp_lnd_test.go +++ b/lntest/itest/temp_lnd_test.go @@ -68,9 +68,14 @@ func TestLightningNetworkDaemonTemp(t *testing.T) { testCases, trancheIndex, trancheOffset := getTestCaseSplitTranche() lntest.ApplyPortOffset(uint32(trancheIndex) * 1000) + // Create a simple fee service. + feeService := lntemp.NewFeeService(t) + // Get the binary path and setup the harness test. binary := getLndBinary(t) - harnessTest := lntemp.SetupHarness(t, binary, *dbBackendFlag) + harnessTest := lntemp.SetupHarness( + t, binary, *dbBackendFlag, feeService, + ) defer harnessTest.Stop() // Setup standby nodes, Alice and Bob, which will be alive and shared @@ -105,9 +110,6 @@ func TestLightningNetworkDaemonTemp(t *testing.T) { ht.Alice.AddToLogf(logLine) ht.Bob.AddToLogf(logLine) - // Start every test with the default static fee - // estimate. - ht.SetFeeEstimate(12500) ht.EnsureConnected(ht.Alice, ht.Bob) ht.RunTestCase(testCase)