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 2022-08-16 17:31:20 +08:00
parent 9d79e76876
commit 353b744039
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868
4 changed files with 97 additions and 54 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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...")

View File

@ -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)