284 lines
7.4 KiB
Go
284 lines
7.4 KiB
Go
package itest
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/rpcclient"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/go-errors/errors"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
|
)
|
|
|
|
var (
|
|
harnessNetParams = &chaincfg.RegressionNetParams
|
|
|
|
// lndExecutable is the full path to the lnd binary.
|
|
lndExecutable = flag.String(
|
|
"lndexec", itestLndBinary, "full path to lnd binary",
|
|
)
|
|
)
|
|
|
|
const (
|
|
testFeeBase = 1e+6
|
|
defaultCSV = lntest.DefaultCSV
|
|
defaultTimeout = lntest.DefaultTimeout
|
|
minerMempoolTimeout = lntest.MinerMempoolTimeout
|
|
channelOpenTimeout = lntest.ChannelOpenTimeout
|
|
channelCloseTimeout = lntest.ChannelCloseTimeout
|
|
itestLndBinary = "../../lnd-itest"
|
|
anchorSize = 330
|
|
noFeeLimitMsat = math.MaxInt64
|
|
|
|
AddrTypeWitnessPubkeyHash = lnrpc.AddressType_WITNESS_PUBKEY_HASH
|
|
AddrTypeNestedPubkeyHash = lnrpc.AddressType_NESTED_PUBKEY_HASH
|
|
)
|
|
|
|
// harnessTest wraps a regular testing.T providing enhanced error detection
|
|
// and propagation. All error will be augmented with a full stack-trace in
|
|
// order to aid in debugging. Additionally, any panics caused by active
|
|
// test cases will also be handled and represented as fatals.
|
|
type harnessTest struct {
|
|
t *testing.T
|
|
|
|
// testCase is populated during test execution and represents the
|
|
// current test case.
|
|
testCase *testCase
|
|
|
|
// lndHarness is a reference to the current network harness. Will be
|
|
// nil if not yet set up.
|
|
lndHarness *lntest.NetworkHarness
|
|
}
|
|
|
|
// newHarnessTest creates a new instance of a harnessTest from a regular
|
|
// testing.T instance.
|
|
func newHarnessTest(t *testing.T, net *lntest.NetworkHarness) *harnessTest {
|
|
return &harnessTest{t, nil, net}
|
|
}
|
|
|
|
// Skipf calls the underlying testing.T's Skip method, causing the current test
|
|
// to be skipped.
|
|
func (h *harnessTest) Skipf(format string, args ...interface{}) {
|
|
h.t.Skipf(format, args...)
|
|
}
|
|
|
|
// Fatalf causes the current active test case to fail with a fatal error. All
|
|
// integration tests should mark test failures solely with this method due to
|
|
// the error stack traces it produces.
|
|
func (h *harnessTest) Fatalf(format string, a ...interface{}) {
|
|
if h.lndHarness != nil {
|
|
h.lndHarness.SaveProfilesPages()
|
|
}
|
|
|
|
stacktrace := errors.Wrap(fmt.Sprintf(format, a...), 1).ErrorStack()
|
|
|
|
if h.testCase != nil {
|
|
h.t.Fatalf("Failed: (%v): exited with error: \n"+
|
|
"%v", h.testCase.name, stacktrace)
|
|
} else {
|
|
h.t.Fatalf("Error outside of test: %v", stacktrace)
|
|
}
|
|
}
|
|
|
|
// RunTestCase executes a harness test case. Any errors or panics will be
|
|
// represented as fatal.
|
|
func (h *harnessTest) RunTestCase(testCase *testCase) {
|
|
|
|
h.testCase = testCase
|
|
defer func() {
|
|
h.testCase = nil
|
|
}()
|
|
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
description := errors.Wrap(err, 2).ErrorStack()
|
|
h.t.Fatalf("Failed: (%v) panicked with: \n%v",
|
|
h.testCase.name, description)
|
|
}
|
|
}()
|
|
|
|
testCase.test(h.lndHarness, h)
|
|
}
|
|
|
|
func (h *harnessTest) Logf(format string, args ...interface{}) {
|
|
h.t.Logf(format, args...)
|
|
}
|
|
|
|
func (h *harnessTest) Log(args ...interface{}) {
|
|
h.t.Log(args...)
|
|
}
|
|
|
|
func (h *harnessTest) getLndBinary() string {
|
|
binary := itestLndBinary
|
|
lndExec := ""
|
|
if lndExecutable != nil && *lndExecutable != "" {
|
|
lndExec = *lndExecutable
|
|
}
|
|
if lndExec == "" && runtime.GOOS == "windows" {
|
|
// Windows (even in a bash like environment like git bash as on
|
|
// Travis) doesn't seem to like relative paths to exe files...
|
|
currentDir, err := os.Getwd()
|
|
if err != nil {
|
|
h.Fatalf("unable to get working directory: %v", err)
|
|
}
|
|
targetPath := filepath.Join(currentDir, "../../lnd-itest.exe")
|
|
binary, err = filepath.Abs(targetPath)
|
|
if err != nil {
|
|
h.Fatalf("unable to get absolute path: %v", err)
|
|
}
|
|
} else if lndExec != "" {
|
|
binary = lndExec
|
|
}
|
|
|
|
return binary
|
|
}
|
|
|
|
type testCase struct {
|
|
name string
|
|
test func(net *lntest.NetworkHarness, t *harnessTest)
|
|
}
|
|
|
|
// waitForTxInMempool polls until finding one transaction in the provided
|
|
// miner's mempool. An error is returned if *one* transaction isn't found within
|
|
// the given timeout.
|
|
func waitForTxInMempool(miner *rpcclient.Client,
|
|
timeout time.Duration) (*chainhash.Hash, error) {
|
|
|
|
txs, err := waitForNTxsInMempool(miner, 1, timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return txs[0], err
|
|
}
|
|
|
|
// waitForNTxsInMempool polls until finding the desired number of transactions
|
|
// in the provided miner's mempool. An error is returned if this number is not
|
|
// met after the given timeout.
|
|
func waitForNTxsInMempool(miner *rpcclient.Client, n int,
|
|
timeout time.Duration) ([]*chainhash.Hash, error) {
|
|
|
|
breakTimeout := time.After(timeout)
|
|
ticker := time.NewTicker(50 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
var err error
|
|
var mempool []*chainhash.Hash
|
|
for {
|
|
select {
|
|
case <-breakTimeout:
|
|
return nil, fmt.Errorf("wanted %v, found %v txs "+
|
|
"in mempool: %v", n, len(mempool), mempool)
|
|
case <-ticker.C:
|
|
mempool, err = miner.GetRawMempool()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(mempool) == n {
|
|
return mempool, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// mineBlocks mine 'num' of blocks and check that blocks are present in
|
|
// node blockchain. numTxs should be set to the number of transactions
|
|
// (excluding the coinbase) we expect to be included in the first mined block.
|
|
func mineBlocks(t *harnessTest, net *lntest.NetworkHarness,
|
|
num uint32, numTxs int) []*wire.MsgBlock {
|
|
|
|
// If we expect transactions to be included in the blocks we'll mine,
|
|
// we wait here until they are seen in the miner's mempool.
|
|
var txids []*chainhash.Hash
|
|
var err error
|
|
if numTxs > 0 {
|
|
txids, err = waitForNTxsInMempool(
|
|
net.Miner.Node, numTxs, minerMempoolTimeout,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to find txns in mempool: %v", err)
|
|
}
|
|
}
|
|
|
|
blocks := make([]*wire.MsgBlock, num)
|
|
|
|
blockHashes, err := net.Miner.Node.Generate(num)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate blocks: %v", err)
|
|
}
|
|
|
|
for i, blockHash := range blockHashes {
|
|
block, err := net.Miner.Node.GetBlock(blockHash)
|
|
if err != nil {
|
|
t.Fatalf("unable to get block: %v", err)
|
|
}
|
|
|
|
blocks[i] = block
|
|
}
|
|
|
|
// Finally, assert that all the transactions were included in the first
|
|
// block.
|
|
for _, txid := range txids {
|
|
assertTxInBlock(t, blocks[0], txid)
|
|
}
|
|
|
|
return blocks
|
|
}
|
|
|
|
func assertTxInBlock(t *harnessTest, block *wire.MsgBlock, txid *chainhash.Hash) {
|
|
for _, tx := range block.Transactions {
|
|
sha := tx.TxHash()
|
|
if bytes.Equal(txid[:], sha[:]) {
|
|
return
|
|
}
|
|
}
|
|
|
|
t.Fatalf("tx was not included in block")
|
|
}
|
|
|
|
func assertWalletUnspent(t *harnessTest, node *lntest.HarnessNode, out *lnrpc.OutPoint) {
|
|
t.t.Helper()
|
|
|
|
err := wait.NoError(func() error {
|
|
ctxt, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
|
defer cancel()
|
|
unspent, err := node.ListUnspent(ctxt, &lnrpc.ListUnspentRequest{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = errors.New("tx with wanted txhash never found")
|
|
for _, utxo := range unspent.Utxos {
|
|
if !bytes.Equal(utxo.Outpoint.TxidBytes, out.TxidBytes) {
|
|
continue
|
|
}
|
|
|
|
err = errors.New("wanted output is not a wallet utxo")
|
|
if utxo.Outpoint.OutputIndex != out.OutputIndex {
|
|
continue
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}, defaultTimeout)
|
|
if err != nil {
|
|
t.Fatalf("outpoint %s not unspent by %s's wallet: %v", out,
|
|
node.Name(), err)
|
|
}
|
|
}
|