lntemp+itest: create interface WebFeeService

This commit adds a new interface, `WebFeeService`, so external projects
can create their own mocked fee services.
This commit is contained in:
yyforyongyu committed 2022-10-14 15:45:25 +08:00
1 parent 9d79e76876
commit 353b744039
4 files changed
+97 -54

No files matched your search

+69 -38
View File
@@ -11,46 +11,68 @@ import (
"github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "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 ( const (
// feeServiceTarget is the confirmation target for which a fee estimate // feeServiceTarget is the confirmation target for which a fee estimate
// is returned. Requests for higher confirmation targets will fall back // is returned. Requests for higher confirmation targets will fall back
// to this. // to this.
feeServiceTarget = 1 feeServiceTarget = 1
// DefaultFeeRateSatPerKw specifies the default fee rate used in the
// tests.
DefaultFeeRateSatPerKw = 12500
) )
// feeService runs a web service that provides fee estimation information. // FeeService runs a web service that provides fee estimation information.
type feeService struct { type FeeService struct {
feeEstimates *testing.T
t *testing.T feeRateMap map[uint32]uint32
url string
srv *http.Server
wg sync.WaitGroup
url string
srv *http.Server
wg sync.WaitGroup
lock sync.Mutex lock sync.Mutex
} }
// feeEstimates contains the current fee estimates. // Compile-time check for the WebFeeService interface.
type feeEstimates struct { var _ WebFeeService = (*FeeService)(nil)
Fees map[uint32]uint32 `json:"fee_by_block_target"`
}
// startFeeService spins up a go-routine to serve fee estimates. // Start spins up a go-routine to serve fee estimates.
func startFeeService(t *testing.T) *feeService { func NewFeeService(t *testing.T) *FeeService {
port := lntest.NextAvailablePort() port := lntest.NextAvailablePort()
f := feeService{ f := FeeService{
t: t, T: t,
url: fmt.Sprintf( url: fmt.Sprintf(
"http://localhost:%v/fee-estimates.json", port, "http://localhost:%v/fee-estimates.json", port,
), ),
} }
// Initialize default fee estimate. // Initialize default fee estimate.
f.Fees = map[uint32]uint32{feeServiceTarget: 50000} f.feeRateMap = map[uint32]uint32{
feeServiceTarget: DefaultFeeRateSatPerKw,
}
listenAddr := fmt.Sprintf(":%v", port) listenAddr := fmt.Sprintf(":%v", port)
mux := http.NewServeMux() mux := http.NewServeMux()
@@ -60,50 +82,59 @@ func startFeeService(t *testing.T) *feeService {
Addr: listenAddr, Addr: listenAddr,
Handler: mux, Handler: mux,
} }
return &f
}
// Start starts the web server.
func (f *FeeService) Start() error {
f.wg.Add(1) f.wg.Add(1)
go func() { go func() {
defer f.wg.Done() defer f.wg.Done()
if err := f.srv.ListenAndServe(); err != http.ErrServerClosed { 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. // 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() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
bytes, err := json.Marshal(f.feeEstimates) bytes, err := json.Marshal(
if err != nil { struct {
f.t.Errorf("error: cannot serialize estimates: %v", err) Fees map[uint32]uint32 `json:"fee_by_block_target"`
}{
return Fees: f.feeRateMap,
} },
)
require.NoErrorf(f, err, "cannot serialize estimates")
_, err = io.WriteString(w, string(bytes)) _, err = io.WriteString(w, string(bytes))
if err != nil { require.NoError(f, err, "cannot send estimates")
f.t.Errorf("error: cannot send estimates: %v", err)
}
} }
// stop stops the web server. // Stop stops the web server.
func (f *feeService) stop() { func (f *FeeService) Stop() error {
if err := f.srv.Shutdown(context.Background()); err != nil { err := f.srv.Shutdown(context.Background())
f.t.Errorf("error: cannot stop fee api: %v", err) require.NoError(f, err, "cannot stop fee api")
}
f.wg.Wait() f.wg.Wait()
return nil
} }
// setFee changes the current fee estimate for the fixed confirmation target. // SetFeeRate sets a fee for the given confirmation target.
func (f *feeService) setFee(fee chainfee.SatPerKWeight) { func (f *FeeService) SetFeeRate(fee chainfee.SatPerKWeight, conf uint32) {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() 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
} }
+18 -10
View File
@@ -56,7 +56,7 @@ type HarnessTest struct {
// feeService is a web service that provides external fee estimates to // feeService is a web service that provides external fee estimates to
// lnd. // lnd.
feeService *feeService feeService WebFeeService
// Channel for transmitting stderr output from failed lightning node // Channel for transmitting stderr output from failed lightning node
// to main process. // to main process.
@@ -79,7 +79,7 @@ type HarnessTest struct {
// NewHarnessTest creates a new instance of a harnessTest from a regular // NewHarnessTest creates a new instance of a harnessTest from a regular
// testing.T instance. // testing.T instance.
func NewHarnessTest(t *testing.T, lndBinary string, func NewHarnessTest(t *testing.T, lndBinary string, feeService WebFeeService,
dbBackend lntest.DatabaseBackend) *HarnessTest { dbBackend lntest.DatabaseBackend) *HarnessTest {
// Create the run context. // Create the run context.
@@ -87,10 +87,11 @@ func NewHarnessTest(t *testing.T, lndBinary string,
manager := newNodeManager(lndBinary, dbBackend) manager := newNodeManager(lndBinary, dbBackend)
return &HarnessTest{ return &HarnessTest{
T: t, T: t,
manager: manager, manager: manager,
runCtx: ctxt, feeService: feeService,
cancel: cancel, runCtx: ctxt,
cancel: cancel,
// We need to use buffered channel here as we don't want to // We need to use buffered channel here as we don't want to
// block sending errors. // block sending errors.
lndErrorChan: make(chan error, 10), lndErrorChan: make(chan error, 10),
@@ -118,11 +119,12 @@ func (h *HarnessTest) Start(chain node.BackendConfig, miner *HarnessMiner) {
}() }()
// Start the fee service. // 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. // Assemble the node manager with chainBackend and feeServiceURL.
h.manager.chainBackend = chain h.manager.chainBackend = chain
h.manager.feeServiceURL = h.feeService.url h.manager.feeServiceURL = h.feeService.URL()
// Assemble the miner. // Assemble the miner.
h.Miner = miner h.Miner = miner
@@ -229,7 +231,8 @@ func (h *HarnessTest) Stop() {
close(h.lndErrorChan) close(h.lndErrorChan)
// Stop the fee service. // Stop the fee service.
h.feeService.stop() err := h.feeService.Stop()
require.NoError(h, err, "failed to stop fee service")
// Stop the chainBackend. // Stop the chainBackend.
h.stopChainBackend() h.stopChainBackend()
@@ -436,8 +439,13 @@ func (h *HarnessTest) RestartNodeWithExtraArgs(hn *node.HarnessNode,
} }
// SetFeeEstimate sets a fee rate to be returned from fee estimator. // 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) { 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 // validateNodeState checks that the node doesn't have any uncleaned states
+4 -2
View File
@@ -17,7 +17,9 @@ import (
// 3. start a chain backend(btcd, bitcoind, or neutrino). // 3. start a chain backend(btcd, bitcoind, or neutrino).
// 4. connect the miner and the chain backend. // 4. connect the miner and the chain backend.
// 5. start the HarnessTest. // 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...") t.Log("Setting up HarnessTest...")
// Parse testing flags that influence our test execution. // 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) dbBackend := prepareDbBackend(t, dbBackendName)
// Create a new HarnessTest. // Create a new HarnessTest.
ht := NewHarnessTest(t, binaryPath, dbBackend) ht := NewHarnessTest(t, binaryPath, feeService, dbBackend)
// Init the miner. // Init the miner.
t.Log("Prepare the miner and mine blocks to activate segwit...") t.Log("Prepare the miner and mine blocks to activate segwit...")
+6 -4
View File
@@ -68,9 +68,14 @@ func TestLightningNetworkDaemonTemp(t *testing.T) {
testCases, trancheIndex, trancheOffset := getTestCaseSplitTranche() testCases, trancheIndex, trancheOffset := getTestCaseSplitTranche()
lntest.ApplyPortOffset(uint32(trancheIndex) * 1000) lntest.ApplyPortOffset(uint32(trancheIndex) * 1000)
// Create a simple fee service.
feeService := lntemp.NewFeeService(t)
// Get the binary path and setup the harness test. // Get the binary path and setup the harness test.
binary := getLndBinary(t) binary := getLndBinary(t)
harnessTest := lntemp.SetupHarness(t, binary, *dbBackendFlag) harnessTest := lntemp.SetupHarness(
t, binary, *dbBackendFlag, feeService,
)
defer harnessTest.Stop() defer harnessTest.Stop()
// Setup standby nodes, Alice and Bob, which will be alive and shared // 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.Alice.AddToLogf(logLine)
ht.Bob.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.EnsureConnected(ht.Alice, ht.Bob)
ht.RunTestCase(testCase) ht.RunTestCase(testCase)