2017-11-17 00:37:08 +01:00
|
|
|
package lntest
|
2017-06-19 15:53:52 +02:00
|
|
|
|
2017-11-03 19:52:02 +01:00
|
|
|
import (
|
2019-09-29 00:43:42 +02:00
|
|
|
"context"
|
2019-02-11 22:02:11 +01:00
|
|
|
"encoding/hex"
|
2018-03-31 07:31:37 +02:00
|
|
|
"errors"
|
2017-11-03 19:52:02 +01:00
|
|
|
"fmt"
|
2018-10-10 12:55:14 +02:00
|
|
|
"io"
|
2017-11-03 19:52:02 +01:00
|
|
|
"io/ioutil"
|
2019-04-15 16:12:59 +02:00
|
|
|
"net/http"
|
2018-10-10 12:55:14 +02:00
|
|
|
"os"
|
2020-10-26 16:36:09 +01:00
|
|
|
"path/filepath"
|
2018-03-14 10:18:32 +01:00
|
|
|
"strings"
|
2017-11-03 19:52:02 +01:00
|
|
|
"sync"
|
2021-06-07 22:05:12 +02:00
|
|
|
"testing"
|
2017-11-03 19:52:02 +01:00
|
|
|
"time"
|
|
|
|
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2018-06-05 03:34:16 +02:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
|
|
"github.com/btcsuite/btcd/txscript"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
2019-10-23 23:35:41 +02:00
|
|
|
"github.com/lightningnetwork/lnd"
|
2021-04-26 19:08:11 +02:00
|
|
|
"github.com/lightningnetwork/lnd/kvdb/etcd"
|
2018-07-31 09:17:17 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
2019-09-19 21:46:29 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
2020-09-10 15:48:39 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
2018-08-22 09:32:42 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
2021-06-07 22:05:12 +02:00
|
|
|
"github.com/stretchr/testify/require"
|
2021-09-29 10:12:25 +02:00
|
|
|
"golang.org/x/sync/errgroup"
|
2019-10-23 23:35:41 +02:00
|
|
|
"google.golang.org/grpc/grpclog"
|
2017-11-03 19:52:02 +01:00
|
|
|
)
|
|
|
|
|
2019-03-28 03:00:39 +01:00
|
|
|
// DefaultCSV is the CSV delay (remotedelay) we will start our test nodes with.
|
|
|
|
const DefaultCSV = 4
|
2018-10-29 05:52:08 +01:00
|
|
|
|
2021-05-14 10:09:05 +02:00
|
|
|
// NodeOption is a function for updating a node's configuration.
|
2022-01-06 12:52:02 +01:00
|
|
|
type NodeOption func(*BaseNodeConfig)
|
2021-05-14 10:09:05 +02:00
|
|
|
|
2017-11-03 19:52:02 +01:00
|
|
|
// NetworkHarness is an integration testing harness for the lightning network.
|
2021-09-14 18:58:25 +02:00
|
|
|
// Building on top of HarnessNode, it is responsible for handling interactions
|
|
|
|
// among different nodes. The harness by default is created with two active
|
|
|
|
// nodes on the network:
|
2016-08-30 06:55:01 +02:00
|
|
|
// Alice and Bob.
|
2017-11-03 19:52:02 +01:00
|
|
|
type NetworkHarness struct {
|
2016-08-30 06:55:01 +02:00
|
|
|
netParams *chaincfg.Params
|
|
|
|
|
2020-10-29 15:37:17 +01:00
|
|
|
// currentTestCase holds the name for the currently run test case.
|
|
|
|
currentTestCase string
|
|
|
|
|
2019-12-17 15:01:23 +01:00
|
|
|
// lndBinary is the full path to the lnd binary that was specifically
|
|
|
|
// compiled with all required itest flags.
|
|
|
|
lndBinary string
|
|
|
|
|
2021-09-14 18:58:25 +02:00
|
|
|
// Miner is a reference to a running full node that can be used to
|
|
|
|
// create new blocks on the network.
|
2021-09-18 08:41:20 +02:00
|
|
|
Miner *HarnessMiner
|
2017-11-03 19:52:02 +01:00
|
|
|
|
2018-12-14 09:24:00 +01:00
|
|
|
// BackendCfg houses the information necessary to use a node as LND
|
|
|
|
// chain backend, such as rpc configuration, P2P information etc.
|
|
|
|
BackendCfg BackendConfig
|
|
|
|
|
2017-11-03 19:52:02 +01:00
|
|
|
activeNodes map[int]*HarnessNode
|
2016-08-30 06:55:01 +02:00
|
|
|
|
2017-12-21 11:32:54 +01:00
|
|
|
nodesByPub map[string]*HarnessNode
|
|
|
|
|
2016-09-10 22:14:28 +02:00
|
|
|
// Alice and Bob are the initial seeder nodes that are automatically
|
|
|
|
// created to be the initial participants of the test network.
|
2017-11-03 19:52:02 +01:00
|
|
|
Alice *HarnessNode
|
|
|
|
Bob *HarnessNode
|
2016-08-31 04:34:13 +02:00
|
|
|
|
2021-06-14 12:31:08 +02:00
|
|
|
// dbBackend sets the database backend to use.
|
|
|
|
dbBackend DatabaseBackend
|
2020-06-22 19:36:12 +02:00
|
|
|
|
2016-10-15 15:47:09 +02:00
|
|
|
// Channel for transmitting stderr output from failed lightning node
|
|
|
|
// to main process.
|
|
|
|
lndErrorChan chan error
|
|
|
|
|
2020-09-10 15:48:39 +02:00
|
|
|
// feeService is a web service that provides external fee estimates to
|
|
|
|
// lnd.
|
|
|
|
feeService *feeService
|
|
|
|
|
2021-09-14 18:58:25 +02:00
|
|
|
// runCtx is a context with cancel method. It's used to signal when the
|
|
|
|
// node needs to quit, and used as the parent context when spawning
|
|
|
|
// children contexts for RPC requests.
|
|
|
|
runCtx context.Context
|
|
|
|
cancel context.CancelFunc
|
2017-10-25 04:54:18 +02:00
|
|
|
|
2017-11-17 02:31:41 +01:00
|
|
|
mtx sync.Mutex
|
2016-08-30 06:55:01 +02:00
|
|
|
}
|
|
|
|
|
2017-11-03 19:52:02 +01:00
|
|
|
// NewNetworkHarness creates a new network test harness.
|
2016-08-30 07:07:54 +02:00
|
|
|
// TODO(roasbeef): add option to use golang's build library to a binary of the
|
2018-02-07 04:11:11 +01:00
|
|
|
// current repo. This will save developers from having to manually `go install`
|
2016-09-15 20:59:51 +02:00
|
|
|
// within the repo each time before changes
|
2021-09-18 08:41:20 +02:00
|
|
|
func NewNetworkHarness(m *HarnessMiner, b BackendConfig, lndBinary string,
|
2021-06-14 12:31:08 +02:00
|
|
|
dbBackend DatabaseBackend) (*NetworkHarness, error) {
|
2019-12-17 15:01:23 +01:00
|
|
|
|
2021-09-14 18:58:25 +02:00
|
|
|
ctxt, cancel := context.WithCancel(context.Background())
|
|
|
|
|
2017-11-17 02:31:41 +01:00
|
|
|
n := NetworkHarness{
|
2020-10-26 14:21:08 +01:00
|
|
|
activeNodes: make(map[int]*HarnessNode),
|
|
|
|
nodesByPub: make(map[string]*HarnessNode),
|
|
|
|
lndErrorChan: make(chan error),
|
2021-09-18 08:41:20 +02:00
|
|
|
netParams: m.ActiveNet,
|
|
|
|
Miner: m,
|
2020-10-26 14:21:08 +01:00
|
|
|
BackendCfg: b,
|
2021-09-14 18:58:25 +02:00
|
|
|
runCtx: ctxt,
|
|
|
|
cancel: cancel,
|
2020-10-26 14:21:08 +01:00
|
|
|
lndBinary: lndBinary,
|
2021-06-14 12:31:08 +02:00
|
|
|
dbBackend: dbBackend,
|
2020-10-26 14:21:08 +01:00
|
|
|
}
|
2017-11-17 02:31:41 +01:00
|
|
|
return &n, nil
|
2016-08-30 06:55:01 +02:00
|
|
|
}
|
|
|
|
|
2017-12-21 11:32:54 +01:00
|
|
|
// LookUpNodeByPub queries the set of active nodes to locate a node according
|
2022-01-23 18:00:24 +01:00
|
|
|
// to its public key. The error is returned if the node was not found.
|
2017-12-21 11:32:54 +01:00
|
|
|
func (n *NetworkHarness) LookUpNodeByPub(pubStr string) (*HarnessNode, error) {
|
|
|
|
n.mtx.Lock()
|
|
|
|
defer n.mtx.Unlock()
|
|
|
|
|
|
|
|
node, ok := n.nodesByPub[pubStr]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("unable to find node")
|
|
|
|
}
|
|
|
|
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
2016-10-24 04:00:09 +02:00
|
|
|
// ProcessErrors returns a channel used for reporting any fatal process errors.
|
|
|
|
// If any of the active nodes within the harness' test network incur a fatal
|
|
|
|
// error, that error is sent over this channel.
|
2017-11-03 19:52:02 +01:00
|
|
|
func (n *NetworkHarness) ProcessErrors() <-chan error {
|
2016-10-24 04:00:09 +02:00
|
|
|
return n.lndErrorChan
|
|
|
|
}
|
|
|
|
|
2016-08-30 06:55:01 +02:00
|
|
|
// SetUp starts the initial seeder nodes within the test harness. The initial
|
|
|
|
// node's wallets will be funded wallets with ten 1 BTC outputs each. Finally
|
|
|
|
// rpc clients capable of communicating with the initial seeder nodes are
|
2017-11-17 02:31:41 +01:00
|
|
|
// created. Nodes are initialized with the given extra command line flags, which
|
|
|
|
// should be formatted properly - "--arg=value".
|
2021-06-07 22:05:12 +02:00
|
|
|
func (n *NetworkHarness) SetUp(t *testing.T,
|
|
|
|
testCase string, lndArgs []string) error {
|
|
|
|
|
2016-08-30 06:55:01 +02:00
|
|
|
// Swap out grpc's default logger with out fake logger which drops the
|
|
|
|
// statements on the floor.
|
2021-09-15 22:24:48 +02:00
|
|
|
fakeLogger := grpclog.NewLoggerV2(io.Discard, io.Discard, io.Discard)
|
|
|
|
grpclog.SetLoggerV2(fakeLogger)
|
2020-10-29 15:37:17 +01:00
|
|
|
n.currentTestCase = testCase
|
2022-02-07 13:58:25 +01:00
|
|
|
n.feeService = startFeeService(t)
|
2017-10-25 04:35:54 +02:00
|
|
|
|
2016-08-30 06:55:01 +02:00
|
|
|
// Start the initial seeder nodes within the test network, then connect
|
|
|
|
// their respective RPC clients.
|
2021-09-29 10:12:25 +02:00
|
|
|
eg := errgroup.Group{}
|
|
|
|
eg.Go(func() error {
|
|
|
|
var err error
|
|
|
|
n.Alice, err = n.newNode(
|
|
|
|
"Alice", lndArgs, false, nil, n.dbBackend, true,
|
|
|
|
)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
eg.Go(func() error {
|
|
|
|
var err error
|
|
|
|
n.Bob, err = n.newNode(
|
|
|
|
"Bob", lndArgs, false, nil, n.dbBackend, true,
|
|
|
|
)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
require.NoError(t, eg.Wait())
|
2016-08-30 06:55:01 +02:00
|
|
|
|
2021-05-07 19:11:23 +02:00
|
|
|
// First, make a connection between the two nodes. This will wait until
|
|
|
|
// both nodes are fully started since the Connect RPC is guarded behind
|
|
|
|
// the server.Started() flag that waits for all subsystems to be ready.
|
2021-08-19 14:30:33 +02:00
|
|
|
n.ConnectNodes(t, n.Alice, n.Bob)
|
2021-05-07 19:11:23 +02:00
|
|
|
|
2016-08-30 06:55:01 +02:00
|
|
|
// Load up the wallets of the seeder nodes with 10 outputs of 1 BTC
|
|
|
|
// each.
|
2017-02-10 00:28:32 +01:00
|
|
|
addrReq := &lnrpc.NewAddressRequest{
|
2018-09-27 15:49:44 +02:00
|
|
|
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
2017-02-10 00:28:32 +01:00
|
|
|
}
|
2016-09-10 22:14:28 +02:00
|
|
|
clients := []lnrpc.LightningClient{n.Alice, n.Bob}
|
2016-08-30 06:55:01 +02:00
|
|
|
for _, client := range clients {
|
|
|
|
for i := 0; i < 10; i++ {
|
2021-09-14 18:58:25 +02:00
|
|
|
resp, err := client.NewAddress(n.runCtx, addrReq)
|
2016-08-30 06:55:01 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addr, err := btcutil.DecodeAddress(resp.Address, n.netParams)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addrScript, err := txscript.PayToAddrScript(addr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
output := &wire.TxOut{
|
|
|
|
PkScript: addrScript,
|
|
|
|
Value: btcutil.SatoshiPerBitcoin,
|
|
|
|
}
|
2018-07-28 03:20:58 +02:00
|
|
|
_, err = n.Miner.SendOutputs([]*wire.TxOut{output}, 7500)
|
|
|
|
if err != nil {
|
2016-08-30 06:55:01 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We generate several blocks in order to give the outputs created
|
|
|
|
// above a good number of confirmations.
|
2021-03-09 23:12:26 +01:00
|
|
|
if _, err := n.Miner.Client.Generate(10); err != nil {
|
2016-08-30 06:55:01 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-05-07 19:11:23 +02:00
|
|
|
// Now we want to wait for the nodes to catch up.
|
2021-09-14 12:40:02 +02:00
|
|
|
if err := n.Alice.WaitForBlockchainSync(); err != nil {
|
2021-05-07 19:11:23 +02:00
|
|
|
return err
|
|
|
|
}
|
2021-09-14 12:40:02 +02:00
|
|
|
if err := n.Bob.WaitForBlockchainSync(); err != nil {
|
2016-08-30 06:55:01 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-08-31 01:44:47 +02:00
|
|
|
// Now block until both wallets have fully synced up.
|
2017-07-05 00:52:58 +02:00
|
|
|
expectedBalance := int64(btcutil.SatoshiPerBitcoin * 10)
|
2016-08-31 01:44:47 +02:00
|
|
|
balReq := &lnrpc.WalletBalanceRequest{}
|
2020-12-08 16:26:59 +01:00
|
|
|
balanceTicker := time.NewTicker(time.Millisecond * 200)
|
2020-04-14 18:17:28 +02:00
|
|
|
defer balanceTicker.Stop()
|
2020-12-08 16:27:01 +01:00
|
|
|
balanceTimeout := time.After(DefaultTimeout)
|
2016-08-31 01:44:47 +02:00
|
|
|
out:
|
|
|
|
for {
|
|
|
|
select {
|
2020-04-14 18:17:28 +02:00
|
|
|
case <-balanceTicker.C:
|
2021-09-14 18:58:25 +02:00
|
|
|
aliceResp, err := n.Alice.WalletBalance(n.runCtx, balReq)
|
2016-08-31 01:44:47 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-09-14 18:58:25 +02:00
|
|
|
bobResp, err := n.Bob.WalletBalance(n.runCtx, balReq)
|
2016-08-31 01:44:47 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-11-26 14:17:57 +01:00
|
|
|
if aliceResp.ConfirmedBalance == expectedBalance &&
|
|
|
|
bobResp.ConfirmedBalance == expectedBalance {
|
2016-08-31 01:44:47 +02:00
|
|
|
break out
|
|
|
|
}
|
2017-08-10 23:05:04 +02:00
|
|
|
case <-balanceTimeout:
|
2016-09-18 02:34:39 +02:00
|
|
|
return fmt.Errorf("balances not synced after deadline")
|
2016-08-31 01:44:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-30 06:55:01 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-10-29 15:37:17 +01:00
|
|
|
// TearDown tears down all active nodes within the test lightning network.
|
|
|
|
func (n *NetworkHarness) TearDown() error {
|
2016-08-31 20:59:08 +02:00
|
|
|
for _, node := range n.activeNodes {
|
2017-11-03 22:40:57 +01:00
|
|
|
if err := n.ShutdownNode(node); err != nil {
|
2016-08-31 20:59:08 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-29 15:37:17 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop stops the test harness.
|
|
|
|
func (n *NetworkHarness) Stop() {
|
2017-10-25 04:35:54 +02:00
|
|
|
close(n.lndErrorChan)
|
2021-09-14 18:58:25 +02:00
|
|
|
n.cancel()
|
2017-10-25 04:54:18 +02:00
|
|
|
|
2022-02-22 06:14:48 +01:00
|
|
|
// feeService may not be created. For instance, running a non-exist
|
|
|
|
// test case.
|
|
|
|
if n.feeService != nil {
|
|
|
|
n.feeService.stop()
|
|
|
|
}
|
2016-08-31 20:59:08 +02:00
|
|
|
}
|
|
|
|
|
2021-03-04 23:15:04 +01:00
|
|
|
// extraArgsEtcd returns extra args for configuring LND to use an external etcd
|
|
|
|
// database (for remote channel DB and wallet DB).
|
2022-03-17 18:13:11 +01:00
|
|
|
func extraArgsEtcd(etcdCfg *etcd.Config, name string, cluster bool,
|
|
|
|
leaderSessionTTL int) []string {
|
|
|
|
|
2021-03-04 23:15:04 +01:00
|
|
|
extraArgs := []string{
|
|
|
|
"--db.backend=etcd",
|
|
|
|
fmt.Sprintf("--db.etcd.host=%v", etcdCfg.Host),
|
|
|
|
fmt.Sprintf("--db.etcd.user=%v", etcdCfg.User),
|
|
|
|
fmt.Sprintf("--db.etcd.pass=%v", etcdCfg.Pass),
|
|
|
|
fmt.Sprintf("--db.etcd.namespace=%v", etcdCfg.Namespace),
|
|
|
|
}
|
|
|
|
|
|
|
|
if etcdCfg.InsecureSkipVerify {
|
|
|
|
extraArgs = append(extraArgs, "--db.etcd.insecure_skip_verify")
|
|
|
|
}
|
|
|
|
|
|
|
|
if cluster {
|
2022-03-17 18:13:11 +01:00
|
|
|
clusterArgs := []string{
|
|
|
|
"--cluster.enable-leader-election",
|
|
|
|
fmt.Sprintf("--cluster.id=%v", name),
|
|
|
|
fmt.Sprintf("--cluster.leader-session-ttl=%v",
|
|
|
|
leaderSessionTTL),
|
|
|
|
}
|
|
|
|
extraArgs = append(extraArgs, clusterArgs...)
|
2021-03-04 23:15:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return extraArgs
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewNodeWithSeedEtcd starts a new node with seed that'll use an external
|
|
|
|
// etcd database as its (remote) channel and wallet DB. The passsed cluster
|
|
|
|
// flag indicates that we'd like the node to join the cluster leader election.
|
|
|
|
func (n *NetworkHarness) NewNodeWithSeedEtcd(name string, etcdCfg *etcd.Config,
|
2022-03-17 18:13:11 +01:00
|
|
|
password []byte, entropy []byte, statelessInit, cluster bool,
|
|
|
|
leaderSessionTTL int) (*HarnessNode, []string, []byte, error) {
|
2021-03-04 23:15:04 +01:00
|
|
|
|
|
|
|
// We don't want to use the embedded etcd instance.
|
2021-06-14 12:31:08 +02:00
|
|
|
const dbBackend = BackendBbolt
|
2021-03-04 23:15:04 +01:00
|
|
|
|
2022-03-17 18:13:11 +01:00
|
|
|
extraArgs := extraArgsEtcd(etcdCfg, name, cluster, leaderSessionTTL)
|
2021-03-04 23:15:04 +01:00
|
|
|
return n.newNodeWithSeed(
|
2021-06-14 12:31:08 +02:00
|
|
|
name, extraArgs, password, entropy, statelessInit, dbBackend,
|
2021-03-04 23:15:04 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewNodeWithSeedEtcd starts a new node with seed that'll use an external
|
|
|
|
// etcd database as its (remote) channel and wallet DB. The passsed cluster
|
|
|
|
// flag indicates that we'd like the node to join the cluster leader election.
|
|
|
|
// If the wait flag is false then we won't wait until RPC is available (this is
|
|
|
|
// useful when the node is not expected to become the leader right away).
|
|
|
|
func (n *NetworkHarness) NewNodeEtcd(name string, etcdCfg *etcd.Config,
|
2022-03-17 18:13:11 +01:00
|
|
|
password []byte, cluster, wait bool, leaderSessionTTL int) (
|
|
|
|
*HarnessNode, error) {
|
2021-03-04 23:15:04 +01:00
|
|
|
|
|
|
|
// We don't want to use the embedded etcd instance.
|
2021-06-14 12:31:08 +02:00
|
|
|
const dbBackend = BackendBbolt
|
2021-03-04 23:15:04 +01:00
|
|
|
|
2022-03-17 18:13:11 +01:00
|
|
|
extraArgs := extraArgsEtcd(etcdCfg, name, cluster, leaderSessionTTL)
|
2021-06-14 12:31:08 +02:00
|
|
|
return n.newNode(name, extraArgs, true, password, dbBackend, wait)
|
2021-03-04 23:15:04 +01:00
|
|
|
}
|
|
|
|
|
2018-02-07 04:11:11 +01:00
|
|
|
// NewNode fully initializes a returns a new HarnessNode bound to the
|
2016-09-26 19:30:24 +02:00
|
|
|
// current instance of the network harness. The created node is running, but
|
|
|
|
// not yet connected to other nodes within the network.
|
2021-06-07 22:05:12 +02:00
|
|
|
func (n *NetworkHarness) NewNode(t *testing.T,
|
2021-08-31 14:48:33 +02:00
|
|
|
name string, extraArgs []string, opts ...NodeOption) *HarnessNode {
|
2021-06-07 22:05:12 +02:00
|
|
|
|
|
|
|
node, err := n.newNode(
|
2021-08-31 14:48:33 +02:00
|
|
|
name, extraArgs, false, nil, n.dbBackend, true, opts...,
|
2021-06-07 22:05:12 +02:00
|
|
|
)
|
|
|
|
require.NoErrorf(t, err, "unable to create new node for %s", name)
|
2021-03-04 23:15:04 +01:00
|
|
|
|
2021-06-07 22:05:12 +02:00
|
|
|
return node
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewNodeWithSeed fully initializes a new HarnessNode after creating a fresh
|
|
|
|
// aezeed. The provided password is used as both the aezeed password and the
|
|
|
|
// wallet password. The generated mnemonic is returned along with the
|
|
|
|
// initialized harness node.
|
2018-04-27 10:42:04 +02:00
|
|
|
func (n *NetworkHarness) NewNodeWithSeed(name string, extraArgs []string,
|
2020-10-06 17:23:40 +02:00
|
|
|
password []byte, statelessInit bool) (*HarnessNode, []string, []byte,
|
|
|
|
error) {
|
2018-04-03 01:57:04 +02:00
|
|
|
|
2021-03-04 23:15:04 +01:00
|
|
|
return n.newNodeWithSeed(
|
2021-06-14 12:31:08 +02:00
|
|
|
name, extraArgs, password, nil, statelessInit, n.dbBackend,
|
2021-03-04 23:15:04 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *NetworkHarness) newNodeWithSeed(name string, extraArgs []string,
|
2021-06-14 12:31:08 +02:00
|
|
|
password, entropy []byte, statelessInit bool, dbBackend DatabaseBackend) (
|
2021-03-04 23:15:04 +01:00
|
|
|
*HarnessNode, []string, []byte, error) {
|
|
|
|
|
|
|
|
node, err := n.newNode(
|
2021-06-14 12:31:08 +02:00
|
|
|
name, extraArgs, true, password, dbBackend, true,
|
2021-03-04 23:15:04 +01:00
|
|
|
)
|
2018-04-03 01:57:04 +02:00
|
|
|
if err != nil {
|
2020-10-06 17:23:40 +02:00
|
|
|
return nil, nil, nil, err
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create a request to generate a new aezeed. The new seed will have the
|
|
|
|
// same password as the internal wallet.
|
|
|
|
genSeedReq := &lnrpc.GenSeedRequest{
|
|
|
|
AezeedPassphrase: password,
|
2021-03-04 23:15:04 +01:00
|
|
|
SeedEntropy: entropy,
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
2021-09-14 18:58:25 +02:00
|
|
|
ctxt, cancel := context.WithTimeout(n.runCtx, DefaultTimeout)
|
2020-12-08 16:46:54 +01:00
|
|
|
defer cancel()
|
2021-02-05 13:33:01 +01:00
|
|
|
|
|
|
|
var genSeedResp *lnrpc.GenSeedResponse
|
|
|
|
if err := wait.NoError(func() error {
|
|
|
|
genSeedResp, err = node.GenSeed(ctxt, genSeedReq)
|
|
|
|
return err
|
|
|
|
}, DefaultTimeout); err != nil {
|
2020-10-06 17:23:40 +02:00
|
|
|
return nil, nil, nil, err
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// With the seed created, construct the init request to the node,
|
|
|
|
// including the newly generated seed.
|
|
|
|
initReq := &lnrpc.InitWalletRequest{
|
|
|
|
WalletPassword: password,
|
|
|
|
CipherSeedMnemonic: genSeedResp.CipherSeedMnemonic,
|
|
|
|
AezeedPassphrase: password,
|
2020-10-06 17:23:40 +02:00
|
|
|
StatelessInit: statelessInit,
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Pass the init request via rpc to finish unlocking the node. This will
|
|
|
|
// also initialize the macaroon-authenticated LightningClient.
|
2021-09-14 12:40:02 +02:00
|
|
|
response, err := node.Init(initReq)
|
2018-04-03 01:57:04 +02:00
|
|
|
if err != nil {
|
2020-10-06 17:23:40 +02:00
|
|
|
return nil, nil, nil, err
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// With the node started, we can now record its public key within the
|
|
|
|
// global mapping.
|
|
|
|
n.RegisterNode(node)
|
|
|
|
|
2020-10-06 17:23:40 +02:00
|
|
|
// In stateless initialization mode we get a macaroon back that we have
|
|
|
|
// to return to the test, otherwise gRPC calls won't be possible since
|
|
|
|
// there are no macaroon files created in that mode.
|
|
|
|
// In stateful init the admin macaroon will just be nil.
|
|
|
|
return node, genSeedResp.CipherSeedMnemonic, response.AdminMacaroon, nil
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
2021-10-14 15:43:00 +02:00
|
|
|
func (n *NetworkHarness) NewNodeRemoteSigner(name string, extraArgs []string,
|
|
|
|
password []byte, watchOnly *lnrpc.WatchOnly) (*HarnessNode, error) {
|
|
|
|
|
|
|
|
node, err := n.newNode(
|
|
|
|
name, extraArgs, true, password, n.dbBackend, true,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// With the seed created, construct the init request to the node,
|
|
|
|
// including the newly generated seed.
|
|
|
|
initReq := &lnrpc.InitWalletRequest{
|
|
|
|
WalletPassword: password,
|
|
|
|
WatchOnly: watchOnly,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pass the init request via rpc to finish unlocking the node. This will
|
|
|
|
// also initialize the macaroon-authenticated LightningClient.
|
2021-09-14 12:40:02 +02:00
|
|
|
_, err = node.Init(initReq)
|
2021-10-14 15:43:00 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// With the node started, we can now record its public key within the
|
|
|
|
// global mapping.
|
|
|
|
n.RegisterNode(node)
|
|
|
|
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
2018-04-03 01:57:04 +02:00
|
|
|
// RestoreNodeWithSeed fully initializes a HarnessNode using a chosen mnemonic,
|
2019-03-12 00:18:10 +01:00
|
|
|
// password, recovery window, and optionally a set of static channel backups.
|
|
|
|
// After providing the initialization request to unlock the node, this method
|
|
|
|
// will finish initializing the LightningClient such that the HarnessNode can
|
|
|
|
// be used for regular rpc operations.
|
2018-04-27 10:42:04 +02:00
|
|
|
func (n *NetworkHarness) RestoreNodeWithSeed(name string, extraArgs []string,
|
2020-10-24 23:18:56 +02:00
|
|
|
password []byte, mnemonic []string, rootKey string, recoveryWindow int32,
|
2021-05-14 10:09:05 +02:00
|
|
|
chanBackups *lnrpc.ChanBackupSnapshot,
|
|
|
|
opts ...NodeOption) (*HarnessNode, error) {
|
2018-04-03 01:57:04 +02:00
|
|
|
|
2021-03-04 23:15:04 +01:00
|
|
|
node, err := n.newNode(
|
2021-06-14 12:31:08 +02:00
|
|
|
name, extraArgs, true, password, n.dbBackend, true, opts...,
|
2021-03-04 23:15:04 +01:00
|
|
|
)
|
2018-04-03 01:57:04 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
initReq := &lnrpc.InitWalletRequest{
|
|
|
|
WalletPassword: password,
|
|
|
|
CipherSeedMnemonic: mnemonic,
|
|
|
|
AezeedPassphrase: password,
|
2020-10-24 23:18:56 +02:00
|
|
|
ExtendedMasterKey: rootKey,
|
2018-04-03 01:57:04 +02:00
|
|
|
RecoveryWindow: recoveryWindow,
|
2019-03-12 00:18:10 +01:00
|
|
|
ChannelBackups: chanBackups,
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
2021-09-14 12:40:02 +02:00
|
|
|
_, err = node.Init(initReq)
|
2018-04-03 01:57:04 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// With the node started, we can now record its public key within the
|
|
|
|
// global mapping.
|
|
|
|
n.RegisterNode(node)
|
|
|
|
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// newNode initializes a new HarnessNode, supporting the ability to initialize a
|
|
|
|
// wallet with or without a seed. If hasSeed is false, the returned harness node
|
|
|
|
// can be used immediately. Otherwise, the node will require an additional
|
|
|
|
// initialization phase where the wallet is either created or restored.
|
2020-10-29 15:37:17 +01:00
|
|
|
func (n *NetworkHarness) newNode(name string, extraArgs []string, hasSeed bool,
|
2021-06-14 12:31:08 +02:00
|
|
|
password []byte, dbBackend DatabaseBackend, wait bool, opts ...NodeOption) (
|
2021-03-04 23:15:04 +01:00
|
|
|
*HarnessNode, error) {
|
2019-03-11 00:31:43 +01:00
|
|
|
|
2022-01-06 12:52:02 +01:00
|
|
|
cfg := &BaseNodeConfig{
|
2020-10-29 15:37:17 +01:00
|
|
|
Name: name,
|
|
|
|
LogFilenamePrefix: n.currentTestCase,
|
|
|
|
HasSeed: hasSeed,
|
|
|
|
Password: password,
|
|
|
|
BackendCfg: n.BackendCfg,
|
|
|
|
NetParams: n.netParams,
|
|
|
|
ExtraArgs: extraArgs,
|
|
|
|
FeeURL: n.feeService.url,
|
2021-06-14 12:31:08 +02:00
|
|
|
DbBackend: dbBackend,
|
2021-05-14 10:09:05 +02:00
|
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt(cfg)
|
|
|
|
}
|
|
|
|
|
2022-01-06 12:52:02 +01:00
|
|
|
node, err := newNode(cfg)
|
2016-09-26 19:30:24 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-10-25 04:39:50 +02:00
|
|
|
// Put node in activeNodes to ensure Shutdown is called even if Start
|
|
|
|
// returns an error.
|
2017-11-17 02:31:41 +01:00
|
|
|
n.mtx.Lock()
|
2017-11-03 19:52:02 +01:00
|
|
|
n.activeNodes[node.NodeID] = node
|
2017-11-17 02:31:41 +01:00
|
|
|
n.mtx.Unlock()
|
2017-10-25 04:39:50 +02:00
|
|
|
|
2021-03-04 23:15:04 +01:00
|
|
|
err = node.start(n.lndBinary, n.lndErrorChan, wait)
|
|
|
|
if err != nil {
|
2016-09-26 19:30:24 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-04-03 01:57:04 +02:00
|
|
|
// If this node is to have a seed, it will need to be unlocked or
|
|
|
|
// initialized via rpc. Delay registering it with the network until it
|
|
|
|
// can be driven via an unlocked rpc connection.
|
2019-12-17 10:48:17 +01:00
|
|
|
if node.Cfg.HasSeed {
|
2018-04-03 01:57:04 +02:00
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
2017-12-21 11:32:54 +01:00
|
|
|
// With the node started, we can now record its public key within the
|
|
|
|
// global mapping.
|
2018-04-03 01:57:04 +02:00
|
|
|
n.RegisterNode(node)
|
|
|
|
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterNode records a new HarnessNode in the NetworkHarnesses map of known
|
|
|
|
// nodes. This method should only be called with nodes that have successfully
|
|
|
|
// retrieved their public keys via FetchNodeInfo.
|
|
|
|
func (n *NetworkHarness) RegisterNode(node *HarnessNode) {
|
2017-12-21 11:32:54 +01:00
|
|
|
n.mtx.Lock()
|
|
|
|
n.nodesByPub[node.PubKeyStr] = node
|
|
|
|
n.mtx.Unlock()
|
2016-09-26 19:30:24 +02:00
|
|
|
}
|
2016-09-26 19:31:07 +02:00
|
|
|
|
2018-10-29 05:52:09 +01:00
|
|
|
func (n *NetworkHarness) connect(ctx context.Context,
|
|
|
|
req *lnrpc.ConnectPeerRequest, a *HarnessNode) error {
|
|
|
|
|
2020-12-08 16:27:01 +01:00
|
|
|
syncTimeout := time.After(DefaultTimeout)
|
2018-10-29 05:52:09 +01:00
|
|
|
tryconnect:
|
|
|
|
if _, err := a.ConnectPeer(ctx, req); err != nil {
|
|
|
|
// If the chain backend is still syncing, retry.
|
2020-07-27 14:15:36 +02:00
|
|
|
if strings.Contains(err.Error(), lnd.ErrServerNotActive.Error()) ||
|
|
|
|
strings.Contains(err.Error(), "i/o timeout") {
|
|
|
|
|
2018-10-29 05:52:09 +01:00
|
|
|
select {
|
|
|
|
case <-time.After(100 * time.Millisecond):
|
|
|
|
goto tryconnect
|
|
|
|
case <-syncTimeout:
|
|
|
|
return fmt.Errorf("chain backend did not " +
|
|
|
|
"finish syncing")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-14 10:18:32 +01:00
|
|
|
// EnsureConnected will try to connect to two nodes, returning no error if they
|
|
|
|
// are already connected. If the nodes were not connected previously, this will
|
|
|
|
// behave the same as ConnectNodes. If a pending connection request has already
|
|
|
|
// been made, the method will block until the two nodes appear in each other's
|
|
|
|
// peers list, or until the 15s timeout expires.
|
2021-08-19 15:00:21 +02:00
|
|
|
func (n *NetworkHarness) EnsureConnected(t *testing.T, a, b *HarnessNode) {
|
2021-09-14 18:58:25 +02:00
|
|
|
ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout*2)
|
2021-08-19 15:00:21 +02:00
|
|
|
defer cancel()
|
2021-06-11 06:35:30 +02:00
|
|
|
|
2018-03-31 07:31:37 +02:00
|
|
|
// errConnectionRequested is used to signal that a connection was
|
|
|
|
// requested successfully, which is distinct from already being
|
|
|
|
// connected to the peer.
|
|
|
|
errConnectionRequested := errors.New("connection request in progress")
|
|
|
|
|
|
|
|
tryConnect := func(a, b *HarnessNode) error {
|
2021-08-20 11:33:42 +02:00
|
|
|
bInfo, err := b.GetInfo(ctx, &lnrpc.GetInfoRequest{})
|
2018-03-31 07:31:37 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-14 10:18:32 +01:00
|
|
|
|
2018-03-31 07:31:37 +02:00
|
|
|
req := &lnrpc.ConnectPeerRequest{
|
|
|
|
Addr: &lnrpc.LightningAddress{
|
|
|
|
Pubkey: bInfo.IdentityPubkey,
|
2019-12-17 10:48:17 +01:00
|
|
|
Host: b.Cfg.P2PAddr(),
|
2018-03-31 07:31:37 +02:00
|
|
|
},
|
|
|
|
}
|
2018-03-14 10:18:32 +01:00
|
|
|
|
2019-09-25 12:43:09 +02:00
|
|
|
var predErr error
|
|
|
|
err = wait.Predicate(func() bool {
|
2020-12-08 16:27:01 +01:00
|
|
|
ctx, cancel := context.WithTimeout(ctx, DefaultTimeout)
|
2019-09-25 12:43:09 +02:00
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
err := n.connect(ctx, req, a)
|
|
|
|
switch {
|
|
|
|
// Request was successful, wait for both to display the
|
|
|
|
// connection.
|
|
|
|
case err == nil:
|
|
|
|
predErr = errConnectionRequested
|
|
|
|
return true
|
2018-03-14 10:18:32 +01:00
|
|
|
|
2019-09-25 12:43:09 +02:00
|
|
|
// If the two are already connected, we return early
|
|
|
|
// with no error.
|
|
|
|
case strings.Contains(
|
|
|
|
err.Error(), "already connected to peer",
|
|
|
|
):
|
|
|
|
predErr = nil
|
|
|
|
return true
|
2018-03-14 10:18:32 +01:00
|
|
|
|
2019-09-25 12:43:09 +02:00
|
|
|
default:
|
|
|
|
predErr = err
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}, DefaultTimeout)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("connection not succeeded within 15 "+
|
|
|
|
"seconds: %v", predErr)
|
2018-03-31 07:31:37 +02:00
|
|
|
}
|
2019-09-25 12:43:09 +02:00
|
|
|
|
|
|
|
return predErr
|
2018-03-31 07:31:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
aErr := tryConnect(a, b)
|
|
|
|
bErr := tryConnect(b, a)
|
|
|
|
switch {
|
2019-09-25 12:43:09 +02:00
|
|
|
// If both reported already being connected to each other, we can exit
|
|
|
|
// early.
|
2018-03-31 07:31:37 +02:00
|
|
|
case aErr == nil && bErr == nil:
|
2018-03-14 10:18:32 +01:00
|
|
|
|
2019-09-25 12:43:09 +02:00
|
|
|
// Return any critical errors returned by either alice.
|
2019-02-25 17:03:56 +01:00
|
|
|
case aErr != nil && aErr != errConnectionRequested:
|
2021-06-11 06:35:30 +02:00
|
|
|
t.Fatalf(
|
|
|
|
"ensure connection between %s and %s failed "+
|
|
|
|
"with error from %s: %v",
|
|
|
|
a.Cfg.Name, b.Cfg.Name, a.Cfg.Name, aErr,
|
|
|
|
)
|
2018-03-31 07:31:37 +02:00
|
|
|
|
2019-09-25 12:43:09 +02:00
|
|
|
// Return any critical errors returned by either bob.
|
2019-02-25 17:03:56 +01:00
|
|
|
case bErr != nil && bErr != errConnectionRequested:
|
2021-06-11 06:35:30 +02:00
|
|
|
t.Fatalf("ensure connection between %s and %s failed "+
|
|
|
|
"with error from %s: %v",
|
|
|
|
a.Cfg.Name, b.Cfg.Name, b.Cfg.Name, bErr,
|
|
|
|
)
|
2018-03-31 07:31:37 +02:00
|
|
|
|
2019-09-25 12:43:09 +02:00
|
|
|
// Otherwise one or both requested a connection, so we wait for the
|
|
|
|
// peers lists to reflect the connection.
|
2018-03-14 10:18:32 +01:00
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
2018-03-31 07:31:37 +02:00
|
|
|
findSelfInPeerList := func(a, b *HarnessNode) bool {
|
2018-03-14 10:18:32 +01:00
|
|
|
// If node B is seen in the ListPeers response from node A,
|
|
|
|
// then we can exit early as the connection has been fully
|
|
|
|
// established.
|
2021-08-20 11:33:42 +02:00
|
|
|
resp, err := b.ListPeers(ctx, &lnrpc.ListPeersRequest{})
|
2018-03-14 10:18:32 +01:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, peer := range resp.Peers {
|
2018-03-31 07:31:37 +02:00
|
|
|
if peer.PubKey == a.PubKeyStr {
|
2018-03-14 10:18:32 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
2018-03-31 07:31:37 +02:00
|
|
|
}
|
|
|
|
|
2019-09-19 21:46:29 +02:00
|
|
|
err := wait.Predicate(func() bool {
|
2018-03-31 07:31:37 +02:00
|
|
|
return findSelfInPeerList(a, b) && findSelfInPeerList(b, a)
|
2020-12-08 16:27:01 +01:00
|
|
|
}, DefaultTimeout)
|
2018-03-14 10:18:32 +01:00
|
|
|
|
2021-06-11 06:35:30 +02:00
|
|
|
require.NoErrorf(
|
|
|
|
t, err, "unable to connect %s to %s, "+
|
|
|
|
"got error: peers not connected within %v seconds",
|
|
|
|
a.Cfg.Name, b.Cfg.Name, DefaultTimeout,
|
|
|
|
)
|
2018-03-14 10:18:32 +01:00
|
|
|
}
|
|
|
|
|
2021-07-17 12:01:39 +02:00
|
|
|
// ConnectNodes attempts to create a connection between nodes a and b.
|
|
|
|
func (n *NetworkHarness) ConnectNodes(t *testing.T, a, b *HarnessNode) {
|
|
|
|
n.connectNodes(t, a, b, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConnectNodesPerm attempts to connect nodes a and b and sets node b as
|
|
|
|
// a peer that node a should persistently attempt to reconnect to if they
|
|
|
|
// become disconnected.
|
|
|
|
func (n *NetworkHarness) ConnectNodesPerm(t *testing.T,
|
|
|
|
a, b *HarnessNode) {
|
|
|
|
|
|
|
|
n.connectNodes(t, a, b, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// connectNodes establishes an encrypted+authenticated p2p connection from node
|
2017-04-14 00:11:20 +02:00
|
|
|
// a towards node b. The function will return a non-nil error if the connection
|
2021-07-17 12:01:39 +02:00
|
|
|
// was unable to be established. If the perm parameter is set to true then
|
|
|
|
// node a will persistently attempt to reconnect to node b if they get
|
|
|
|
// disconnected.
|
2017-04-14 00:11:20 +02:00
|
|
|
//
|
|
|
|
// NOTE: This function may block for up to 15-seconds as it will not return
|
|
|
|
// until the new connection is detected as being known to both nodes.
|
2021-07-17 12:01:39 +02:00
|
|
|
func (n *NetworkHarness) connectNodes(t *testing.T, a, b *HarnessNode,
|
|
|
|
perm bool) {
|
|
|
|
|
2021-09-14 18:58:25 +02:00
|
|
|
ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout)
|
2021-08-19 14:30:33 +02:00
|
|
|
defer cancel()
|
2021-06-11 06:35:30 +02:00
|
|
|
|
2016-09-26 19:31:07 +02:00
|
|
|
bobInfo, err := b.GetInfo(ctx, &lnrpc.GetInfoRequest{})
|
2021-06-11 06:35:30 +02:00
|
|
|
require.NoErrorf(
|
|
|
|
t, err, "unable to connect %s to %s, got error: %v",
|
|
|
|
a.Cfg.Name, b.Cfg.Name, err,
|
|
|
|
)
|
2016-09-26 19:31:07 +02:00
|
|
|
|
|
|
|
req := &lnrpc.ConnectPeerRequest{
|
|
|
|
Addr: &lnrpc.LightningAddress{
|
2016-10-28 04:43:31 +02:00
|
|
|
Pubkey: bobInfo.IdentityPubkey,
|
2019-12-17 10:48:17 +01:00
|
|
|
Host: b.Cfg.P2PAddr(),
|
2016-09-26 19:31:07 +02:00
|
|
|
},
|
2021-07-17 12:01:39 +02:00
|
|
|
Perm: perm,
|
2016-09-26 19:31:07 +02:00
|
|
|
}
|
2018-10-29 05:52:09 +01:00
|
|
|
|
2021-06-11 06:35:30 +02:00
|
|
|
err = n.connect(ctx, req, a)
|
|
|
|
require.NoErrorf(
|
|
|
|
t, err, "unable to connect %s to %s, got error: %v",
|
|
|
|
a.Cfg.Name, b.Cfg.Name, err,
|
|
|
|
)
|
2017-04-14 00:11:20 +02:00
|
|
|
|
2019-09-19 21:46:29 +02:00
|
|
|
err = wait.Predicate(func() bool {
|
2017-04-14 00:11:20 +02:00
|
|
|
// If node B is seen in the ListPeers response from node A,
|
|
|
|
// then we can exit early as the connection has been fully
|
|
|
|
// established.
|
|
|
|
resp, err := a.ListPeers(ctx, &lnrpc.ListPeersRequest{})
|
|
|
|
if err != nil {
|
2017-12-21 11:44:15 +01:00
|
|
|
return false
|
2017-04-14 00:11:20 +02:00
|
|
|
}
|
2017-12-21 11:44:15 +01:00
|
|
|
|
2017-04-14 00:11:20 +02:00
|
|
|
for _, peer := range resp.Peers {
|
|
|
|
if peer.PubKey == b.PubKeyStr {
|
2017-12-21 11:44:15 +01:00
|
|
|
return true
|
2017-04-14 00:11:20 +02:00
|
|
|
}
|
|
|
|
}
|
2017-12-21 11:44:15 +01:00
|
|
|
|
|
|
|
return false
|
2020-12-08 16:27:01 +01:00
|
|
|
}, DefaultTimeout)
|
2017-12-21 11:44:15 +01:00
|
|
|
|
2021-06-11 06:35:30 +02:00
|
|
|
require.NoErrorf(
|
|
|
|
t, err, "unable to connect %s to %s, "+
|
|
|
|
"got error: peers not connected within %v seconds",
|
|
|
|
a.Cfg.Name, b.Cfg.Name, DefaultTimeout,
|
|
|
|
)
|
2016-09-26 19:31:07 +02:00
|
|
|
}
|
|
|
|
|
2017-05-05 09:00:39 +02:00
|
|
|
// DisconnectNodes disconnects node a from node b by sending RPC message
|
|
|
|
// from a node to b node
|
2021-08-19 19:48:27 +02:00
|
|
|
func (n *NetworkHarness) DisconnectNodes(a, b *HarnessNode) error {
|
2021-09-14 18:58:25 +02:00
|
|
|
ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout)
|
2021-08-19 19:48:27 +02:00
|
|
|
defer cancel()
|
|
|
|
|
2017-05-05 09:00:39 +02:00
|
|
|
bobInfo, err := b.GetInfo(ctx, &lnrpc.GetInfoRequest{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req := &lnrpc.DisconnectPeerRequest{
|
|
|
|
PubKey: bobInfo.IdentityPubkey,
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := a.DisconnectPeer(ctx, req); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-09 10:03:15 +02:00
|
|
|
// RestartNode attempts to restart a lightning node by shutting it down
|
2016-11-15 00:49:02 +01:00
|
|
|
// cleanly, then restarting the process. This function is fully blocking. Upon
|
|
|
|
// restart, the RPC connection to the node will be re-attempted, continuing iff
|
2016-11-22 04:08:44 +01:00
|
|
|
// the connection attempt is successful. If the callback parameter is non-nil,
|
|
|
|
// then the function will be executed after the node shuts down, but *before*
|
|
|
|
// the process has been started up again.
|
2016-11-15 00:49:02 +01:00
|
|
|
//
|
|
|
|
// This method can be useful when testing edge cases such as a node broadcast
|
|
|
|
// and invalidated prior state, or persistent state recovery, simulating node
|
2019-03-12 00:18:10 +01:00
|
|
|
// crashes, etc. Additionally, each time the node is restarted, the caller can
|
|
|
|
// pass a set of SCBs to pass in via the Unlock method allowing them to restore
|
|
|
|
// channels during restart.
|
|
|
|
func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error,
|
|
|
|
chanBackups ...*lnrpc.ChanBackupSnapshot) error {
|
2019-03-11 00:32:44 +01:00
|
|
|
|
2021-08-12 16:07:27 +02:00
|
|
|
err := n.RestartNodeNoUnlock(node, callback, true)
|
2020-10-06 17:23:40 +02:00
|
|
|
if err != nil {
|
2019-03-11 00:32:44 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the node doesn't have a password set, then we can exit here as we
|
|
|
|
// don't need to unlock it.
|
2019-12-17 10:48:17 +01:00
|
|
|
if len(node.Cfg.Password) == 0 {
|
2019-03-11 00:32:44 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we'll unlock the wallet, then complete the final steps
|
|
|
|
// for the node initialization process.
|
2019-03-12 00:18:10 +01:00
|
|
|
unlockReq := &lnrpc.UnlockWalletRequest{
|
2019-12-17 10:48:17 +01:00
|
|
|
WalletPassword: node.Cfg.Password,
|
2019-03-12 00:18:10 +01:00
|
|
|
}
|
|
|
|
if len(chanBackups) != 0 {
|
|
|
|
unlockReq.ChannelBackups = chanBackups[0]
|
|
|
|
unlockReq.RecoveryWindow = 1000
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:40:02 +02:00
|
|
|
if err := node.Unlock(unlockReq); err != nil {
|
2021-07-13 18:12:34 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-09-14 12:40:02 +02:00
|
|
|
// Give the node some time to catch up with the chain before we
|
|
|
|
// continue with the tests.
|
|
|
|
return node.WaitForBlockchainSync()
|
2016-11-15 00:49:02 +01:00
|
|
|
}
|
|
|
|
|
2020-10-06 17:23:40 +02:00
|
|
|
// RestartNodeNoUnlock attempts to restart a lightning node by shutting it down
|
|
|
|
// cleanly, then restarting the process. In case the node was setup with a seed,
|
|
|
|
// it will be left in the unlocked state. This function is fully blocking. If
|
|
|
|
// the callback parameter is non-nil, then the function will be executed after
|
|
|
|
// the node shuts down, but *before* the process has been started up again.
|
|
|
|
func (n *NetworkHarness) RestartNodeNoUnlock(node *HarnessNode,
|
2021-08-12 16:07:27 +02:00
|
|
|
callback func() error, wait bool) error {
|
2020-10-06 17:23:40 +02:00
|
|
|
|
|
|
|
if err := node.stop(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if callback != nil {
|
|
|
|
if err := callback(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-12 16:07:27 +02:00
|
|
|
return node.start(n.lndBinary, n.lndErrorChan, wait)
|
2020-10-06 17:23:40 +02:00
|
|
|
}
|
|
|
|
|
2018-08-09 10:03:15 +02:00
|
|
|
// SuspendNode stops the given node and returns a callback that can be used to
|
|
|
|
// start it again.
|
|
|
|
func (n *NetworkHarness) SuspendNode(node *HarnessNode) (func() error, error) {
|
|
|
|
if err := node.stop(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
restart := func() error {
|
2021-03-04 23:15:04 +01:00
|
|
|
return node.start(n.lndBinary, n.lndErrorChan, true)
|
2018-08-09 10:03:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return restart, nil
|
|
|
|
}
|
|
|
|
|
2017-11-03 22:40:57 +01:00
|
|
|
// ShutdownNode stops an active lnd process and returns when the process has
|
|
|
|
// exited and any temporary directories have been cleaned up.
|
|
|
|
func (n *NetworkHarness) ShutdownNode(node *HarnessNode) error {
|
|
|
|
if err := node.shutdown(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(n.activeNodes, node.NodeID)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-03-11 17:23:18 +01:00
|
|
|
// KillNode kills the node (but won't wait for the node process to stop).
|
|
|
|
func (n *NetworkHarness) KillNode(node *HarnessNode) error {
|
2021-07-15 11:26:27 +02:00
|
|
|
if err := node.kill(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(n.activeNodes, node.NodeID)
|
|
|
|
return nil
|
2021-03-11 17:23:18 +01:00
|
|
|
}
|
|
|
|
|
2018-06-09 05:37:04 +02:00
|
|
|
// StopNode stops the target node, but doesn't yet clean up its directories.
|
|
|
|
// This can be used to temporarily bring a node down during a test, to be later
|
|
|
|
// started up again.
|
|
|
|
func (n *NetworkHarness) StopNode(node *HarnessNode) error {
|
|
|
|
return node.stop()
|
|
|
|
}
|
|
|
|
|
2019-04-15 16:12:59 +02:00
|
|
|
// SaveProfilesPages hits profiles pages of all active nodes and writes it to
|
|
|
|
// disk using a similar naming scheme as to the regular set of logs.
|
2021-07-13 18:12:32 +02:00
|
|
|
func (n *NetworkHarness) SaveProfilesPages(t *testing.T) {
|
2019-04-15 16:28:01 +02:00
|
|
|
// Only write gorutine dumps if flag is active.
|
|
|
|
if !(*goroutineDump) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-15 16:12:59 +02:00
|
|
|
for _, node := range n.activeNodes {
|
|
|
|
if err := saveProfilesPage(node); err != nil {
|
2021-07-13 18:12:32 +02:00
|
|
|
t.Logf("Logging follow-up error only, see rest of "+
|
|
|
|
"the log for actual cause: %v\n", err)
|
2019-04-15 16:12:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// saveProfilesPage saves the profiles page for the given node to file.
|
|
|
|
func saveProfilesPage(node *HarnessNode) error {
|
|
|
|
resp, err := http.Get(
|
|
|
|
fmt.Sprintf(
|
|
|
|
"http://localhost:%d/debug/pprof/goroutine?debug=1",
|
2019-12-17 10:48:17 +01:00
|
|
|
node.Cfg.ProfilePort,
|
2019-04-15 16:12:59 +02:00
|
|
|
),
|
|
|
|
)
|
|
|
|
if err != nil {
|
2020-04-14 19:56:05 +02:00
|
|
|
return fmt.Errorf("failed to get profile page "+
|
|
|
|
"(node_id=%d, name=%s): %v",
|
2019-12-17 10:48:17 +01:00
|
|
|
node.NodeID, node.Cfg.Name, err)
|
2019-04-15 16:12:59 +02:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
2020-04-14 19:56:05 +02:00
|
|
|
return fmt.Errorf("failed to read profile page "+
|
|
|
|
"(node_id=%d, name=%s): %v",
|
2019-12-17 10:48:17 +01:00
|
|
|
node.NodeID, node.Cfg.Name, err)
|
2019-04-15 16:12:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fileName := fmt.Sprintf(
|
2019-12-17 10:48:17 +01:00
|
|
|
"pprof-%d-%s-%s.log", node.NodeID, node.Cfg.Name,
|
2019-04-15 16:12:59 +02:00
|
|
|
hex.EncodeToString(node.PubKey[:logPubKeyBytes]),
|
|
|
|
)
|
|
|
|
|
|
|
|
logFile, err := os.Create(fileName)
|
|
|
|
if err != nil {
|
2020-04-14 19:56:05 +02:00
|
|
|
return fmt.Errorf("failed to create file for profile page "+
|
|
|
|
"(node_id=%d, name=%s): %v",
|
2019-12-17 10:48:17 +01:00
|
|
|
node.NodeID, node.Cfg.Name, err)
|
2019-04-15 16:12:59 +02:00
|
|
|
}
|
|
|
|
defer logFile.Close()
|
|
|
|
|
|
|
|
_, err = logFile.Write(body)
|
|
|
|
if err != nil {
|
2020-04-14 19:56:05 +02:00
|
|
|
return fmt.Errorf("failed to save profile page "+
|
|
|
|
"(node_id=%d, name=%s): %v",
|
2019-12-17 10:48:17 +01:00
|
|
|
node.NodeID, node.Cfg.Name, err)
|
2019-04-15 16:12:59 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-22 09:32:42 +02:00
|
|
|
// OpenChannelParams houses the params to specify when opening a new channel.
|
|
|
|
type OpenChannelParams struct {
|
|
|
|
// Amt is the local amount being put into the channel.
|
|
|
|
Amt btcutil.Amount
|
|
|
|
|
|
|
|
// PushAmt is the amount that should be pushed to the remote when the
|
|
|
|
// channel is opened.
|
|
|
|
PushAmt btcutil.Amount
|
|
|
|
|
|
|
|
// Private is a boolan indicating whether the opened channel should be
|
|
|
|
// private.
|
|
|
|
Private bool
|
|
|
|
|
|
|
|
// SpendUnconfirmed is a boolean indicating whether we can utilize
|
|
|
|
// unconfirmed outputs to fund the channel.
|
|
|
|
SpendUnconfirmed bool
|
|
|
|
|
2018-09-06 10:48:46 +02:00
|
|
|
// MinHtlc is the htlc_minimum_msat value set when opening the channel.
|
2018-08-22 09:32:42 +02:00
|
|
|
MinHtlc lnwire.MilliSatoshi
|
2019-12-11 05:00:09 +01:00
|
|
|
|
2020-08-13 00:46:17 +02:00
|
|
|
// RemoteMaxHtlcs is the remote_max_htlcs value set when opening the
|
|
|
|
// channel, restricting the number of concurrent HTLCs the remote party
|
|
|
|
// can add to a commitment.
|
|
|
|
RemoteMaxHtlcs uint16
|
|
|
|
|
2019-12-11 05:00:09 +01:00
|
|
|
// FundingShim is an optional funding shim that the caller can specify
|
|
|
|
// in order to modify the channel funding workflow.
|
|
|
|
FundingShim *lnrpc.FundingShim
|
2021-03-22 21:08:38 +01:00
|
|
|
|
|
|
|
// SatPerVByte is the amount of satoshis to spend in chain fees per virtual
|
|
|
|
// byte of the transaction.
|
|
|
|
SatPerVByte btcutil.Amount
|
2021-06-10 01:38:45 +02:00
|
|
|
|
|
|
|
// CommitmentType is the commitment type that should be used for the
|
|
|
|
// channel to be opened.
|
|
|
|
CommitmentType lnrpc.CommitmentType
|
2018-08-22 09:32:42 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 15:18:38 +02:00
|
|
|
// OpenChannel attempts to open a channel between srcNode and destNode with the
|
2016-09-18 02:34:39 +02:00
|
|
|
// passed channel funding parameters. If the passed context has a timeout, then
|
2016-10-15 15:18:38 +02:00
|
|
|
// if the timeout is reached before the channel pending notification is
|
2018-08-10 06:17:06 +02:00
|
|
|
// received, an error is returned. The confirmed boolean determines whether we
|
|
|
|
// should fund the channel with confirmed outputs or not.
|
2021-08-19 19:48:27 +02:00
|
|
|
func (n *NetworkHarness) OpenChannel(srcNode, destNode *HarnessNode,
|
|
|
|
p OpenChannelParams) (lnrpc.Lightning_OpenChannelClient, error) {
|
|
|
|
|
2017-07-12 01:27:11 +02:00
|
|
|
// Wait until srcNode and destNode have the latest chain synced.
|
|
|
|
// Otherwise, we may run into a check within the funding manager that
|
|
|
|
// prevents any funding workflows from being kicked off if the chain
|
|
|
|
// isn't yet synced.
|
2021-09-14 12:40:02 +02:00
|
|
|
if err := srcNode.WaitForBlockchainSync(); err != nil {
|
2020-08-13 00:45:25 +02:00
|
|
|
return nil, fmt.Errorf("unable to sync srcNode chain: %v", err)
|
2017-06-19 15:53:52 +02:00
|
|
|
}
|
2021-09-14 12:40:02 +02:00
|
|
|
if err := destNode.WaitForBlockchainSync(); err != nil {
|
2020-04-14 19:56:05 +02:00
|
|
|
return nil, fmt.Errorf("unable to sync destNode chain: %v", err)
|
2017-06-19 15:53:52 +02:00
|
|
|
}
|
|
|
|
|
2018-08-22 09:32:42 +02:00
|
|
|
minConfs := int32(1)
|
|
|
|
if p.SpendUnconfirmed {
|
|
|
|
minConfs = 0
|
2018-08-10 06:17:06 +02:00
|
|
|
}
|
|
|
|
|
2016-08-31 20:59:08 +02:00
|
|
|
openReq := &lnrpc.OpenChannelRequest{
|
2016-10-28 04:43:31 +02:00
|
|
|
NodePubkey: destNode.PubKey[:],
|
2018-08-22 09:32:42 +02:00
|
|
|
LocalFundingAmount: int64(p.Amt),
|
|
|
|
PushSat: int64(p.PushAmt),
|
|
|
|
Private: p.Private,
|
2018-08-10 06:17:06 +02:00
|
|
|
MinConfs: minConfs,
|
2018-10-17 02:02:07 +02:00
|
|
|
SpendUnconfirmed: p.SpendUnconfirmed,
|
2018-08-22 09:32:42 +02:00
|
|
|
MinHtlcMsat: int64(p.MinHtlc),
|
2020-08-13 00:46:17 +02:00
|
|
|
RemoteMaxHtlcs: uint32(p.RemoteMaxHtlcs),
|
2019-12-11 05:00:09 +01:00
|
|
|
FundingShim: p.FundingShim,
|
2021-03-22 21:08:38 +01:00
|
|
|
SatPerByte: int64(p.SatPerVByte),
|
2021-06-10 01:38:45 +02:00
|
|
|
CommitmentType: p.CommitmentType,
|
2016-08-31 20:59:08 +02:00
|
|
|
}
|
2016-09-15 20:59:51 +02:00
|
|
|
|
2021-09-14 19:41:24 +02:00
|
|
|
// We need to use n.runCtx here to keep the response stream alive after
|
|
|
|
// the function is returned.
|
|
|
|
respStream, err := srcNode.OpenChannel(n.runCtx, openReq)
|
2016-08-31 20:59:08 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to open channel between "+
|
|
|
|
"alice and bob: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-09-18 02:34:39 +02:00
|
|
|
chanOpen := make(chan struct{})
|
|
|
|
errChan := make(chan error)
|
|
|
|
go func() {
|
2021-09-14 19:41:24 +02:00
|
|
|
// Consume the "channel pending" update. This waits until the
|
|
|
|
// node notifies us that the final message in the channel
|
|
|
|
// funding workflow has been sent to the remote node.
|
2016-09-18 02:34:39 +02:00
|
|
|
resp, err := respStream.Recv()
|
|
|
|
if err != nil {
|
|
|
|
errChan <- err
|
2016-09-26 19:52:25 +02:00
|
|
|
return
|
2016-09-18 02:34:39 +02:00
|
|
|
}
|
2021-09-14 19:41:24 +02:00
|
|
|
_, ok := resp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
|
|
|
if !ok {
|
|
|
|
errChan <- fmt.Errorf("expected channel pending: "+
|
|
|
|
"update, instead got %v", resp)
|
2016-09-26 19:52:25 +02:00
|
|
|
return
|
2016-09-18 02:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
close(chanOpen)
|
|
|
|
}()
|
2016-08-31 20:59:08 +02:00
|
|
|
|
2016-09-18 02:34:39 +02:00
|
|
|
select {
|
2021-09-14 19:41:24 +02:00
|
|
|
case <-time.After(ChannelOpenTimeout):
|
2017-01-24 03:19:54 +01:00
|
|
|
return nil, fmt.Errorf("timeout reached before chan pending "+
|
|
|
|
"update sent: %v", err)
|
2016-09-18 02:34:39 +02:00
|
|
|
case err := <-errChan:
|
|
|
|
return nil, err
|
|
|
|
case <-chanOpen:
|
|
|
|
return respStream, nil
|
|
|
|
}
|
2016-08-31 20:59:08 +02:00
|
|
|
}
|
|
|
|
|
2021-09-14 19:41:24 +02:00
|
|
|
// OpenPendingChannel attempts to open a channel between srcNode and destNode
|
|
|
|
// with the passed channel funding parameters. If the passed context has a
|
|
|
|
// timeout, then if the timeout is reached before the channel pending
|
|
|
|
// notification is received, an error is returned.
|
2021-08-19 19:48:27 +02:00
|
|
|
func (n *NetworkHarness) OpenPendingChannel(srcNode, destNode *HarnessNode,
|
|
|
|
amt btcutil.Amount,
|
2017-07-30 23:25:38 +02:00
|
|
|
pushAmt btcutil.Amount) (*lnrpc.PendingUpdate, error) {
|
2017-01-31 05:21:52 +01:00
|
|
|
|
2017-06-19 15:53:52 +02:00
|
|
|
// Wait until srcNode and destNode have blockchain synced
|
2021-09-14 12:40:02 +02:00
|
|
|
if err := srcNode.WaitForBlockchainSync(); err != nil {
|
2020-04-14 19:56:05 +02:00
|
|
|
return nil, fmt.Errorf("unable to sync srcNode chain: %v", err)
|
2017-06-19 15:53:52 +02:00
|
|
|
}
|
2021-09-14 12:40:02 +02:00
|
|
|
if err := destNode.WaitForBlockchainSync(); err != nil {
|
2020-04-14 19:56:05 +02:00
|
|
|
return nil, fmt.Errorf("unable to sync destNode chain: %v", err)
|
2017-06-19 15:53:52 +02:00
|
|
|
}
|
|
|
|
|
2017-01-31 05:21:52 +01:00
|
|
|
openReq := &lnrpc.OpenChannelRequest{
|
|
|
|
NodePubkey: destNode.PubKey[:],
|
|
|
|
LocalFundingAmount: int64(amt),
|
|
|
|
PushSat: int64(pushAmt),
|
2017-11-14 02:35:15 +01:00
|
|
|
Private: false,
|
2017-01-31 05:21:52 +01:00
|
|
|
}
|
|
|
|
|
2021-09-14 19:41:24 +02:00
|
|
|
// We need to use n.runCtx here to keep the response stream alive after
|
|
|
|
// the function is returned.
|
|
|
|
respStream, err := srcNode.OpenChannel(n.runCtx, openReq)
|
2017-01-31 05:21:52 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to open channel between "+
|
|
|
|
"alice and bob: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
chanPending := make(chan *lnrpc.PendingUpdate)
|
|
|
|
errChan := make(chan error)
|
|
|
|
go func() {
|
2021-09-14 19:41:24 +02:00
|
|
|
// Consume the "channel pending" update. This waits until the
|
|
|
|
// node notifies us that the final message in the channel
|
|
|
|
// funding workflow has been sent to the remote node.
|
2017-01-31 05:21:52 +01:00
|
|
|
resp, err := respStream.Recv()
|
|
|
|
if err != nil {
|
|
|
|
errChan <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pendingResp, ok := resp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
|
|
|
if !ok {
|
2021-09-14 19:41:24 +02:00
|
|
|
errChan <- fmt.Errorf("expected channel pending "+
|
|
|
|
"update, instead got %v", resp)
|
2017-01-31 05:21:52 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
chanPending <- pendingResp.ChanPending
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
2021-09-14 19:41:24 +02:00
|
|
|
case <-time.After(ChannelOpenTimeout):
|
2017-01-31 05:21:52 +01:00
|
|
|
return nil, fmt.Errorf("timeout reached before chan pending " +
|
|
|
|
"update sent")
|
|
|
|
case err := <-errChan:
|
|
|
|
return nil, err
|
|
|
|
case pendingChan := <-chanPending:
|
|
|
|
return pendingChan, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-31 20:59:08 +02:00
|
|
|
// WaitForChannelOpen waits for a notification that a channel is open by
|
2016-09-18 02:34:39 +02:00
|
|
|
// consuming a message from the past open channel stream. If the passed context
|
|
|
|
// has a timeout, then if the timeout is reached before the channel has been
|
|
|
|
// opened, then an error is returned.
|
2021-08-19 19:20:06 +02:00
|
|
|
func (n *NetworkHarness) WaitForChannelOpen(
|
2021-09-14 19:41:24 +02:00
|
|
|
openChanStream lnrpc.Lightning_OpenChannelClient) (
|
|
|
|
*lnrpc.ChannelPoint, error) {
|
2016-09-18 02:34:39 +02:00
|
|
|
|
2021-09-14 18:58:25 +02:00
|
|
|
ctx, cancel := context.WithTimeout(n.runCtx, ChannelOpenTimeout)
|
2021-08-19 19:20:06 +02:00
|
|
|
defer cancel()
|
|
|
|
|
2016-09-18 02:34:39 +02:00
|
|
|
errChan := make(chan error)
|
|
|
|
respChan := make(chan *lnrpc.ChannelPoint)
|
|
|
|
go func() {
|
|
|
|
resp, err := openChanStream.Recv()
|
|
|
|
if err != nil {
|
|
|
|
errChan <- fmt.Errorf("unable to read rpc resp: %v", err)
|
2016-09-26 19:52:25 +02:00
|
|
|
return
|
2016-09-18 02:34:39 +02:00
|
|
|
}
|
|
|
|
fundingResp, ok := resp.Update.(*lnrpc.OpenStatusUpdate_ChanOpen)
|
|
|
|
if !ok {
|
|
|
|
errChan <- fmt.Errorf("expected channel open update, "+
|
|
|
|
"instead got %v", resp)
|
2016-09-26 19:52:25 +02:00
|
|
|
return
|
2016-09-18 02:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
respChan <- fundingResp.ChanOpen.ChannelPoint
|
|
|
|
}()
|
2016-08-31 20:59:08 +02:00
|
|
|
|
2016-09-18 02:34:39 +02:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return nil, fmt.Errorf("timeout reached while waiting for " +
|
|
|
|
"channel open")
|
|
|
|
case err := <-errChan:
|
|
|
|
return nil, err
|
|
|
|
case chanPoint := <-respChan:
|
|
|
|
return chanPoint, nil
|
|
|
|
}
|
2016-08-31 20:59:08 +02:00
|
|
|
}
|
|
|
|
|
2018-09-28 05:59:59 +02:00
|
|
|
// CloseChannel attempts to close the channel indicated by the
|
2016-09-18 02:34:39 +02:00
|
|
|
// passed channel point, initiated by the passed lnNode. If the passed context
|
2018-09-28 05:59:59 +02:00
|
|
|
// has a timeout, an error is returned if that timeout is reached before the
|
|
|
|
// channel close is pending.
|
2021-08-19 19:35:02 +02:00
|
|
|
func (n *NetworkHarness) CloseChannel(lnNode *HarnessNode,
|
2021-09-14 19:41:24 +02:00
|
|
|
cp *lnrpc.ChannelPoint, force bool) (lnrpc.Lightning_CloseChannelClient,
|
|
|
|
*chainhash.Hash, error) {
|
2016-08-31 20:59:08 +02:00
|
|
|
|
2021-08-20 11:33:42 +02:00
|
|
|
// The cancel is intentionally left out here because the returned
|
|
|
|
// item(close channel client) relies on the context being active. This
|
|
|
|
// will be fixed once we finish refactoring the NetworkHarness.
|
2021-09-14 19:41:24 +02:00
|
|
|
ctxt, cancel := context.WithTimeout(n.runCtx, ChannelCloseTimeout)
|
|
|
|
defer cancel()
|
2021-08-19 19:35:02 +02:00
|
|
|
|
2017-11-19 01:38:30 +01:00
|
|
|
// Create a channel outpoint that we can use to compare to channels
|
|
|
|
// from the ListChannelsResponse.
|
2018-01-11 05:59:30 +01:00
|
|
|
txidHash, err := getChanPointFundingTxid(cp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
fundingTxID, err := chainhash.NewHash(txidHash)
|
2017-11-19 01:38:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
chanPoint := wire.OutPoint{
|
|
|
|
Hash: *fundingTxID,
|
|
|
|
Index: cp.OutputIndex,
|
|
|
|
}
|
|
|
|
|
2017-12-21 11:43:07 +01:00
|
|
|
// We'll wait for *both* nodes to read the channel as active if we're
|
|
|
|
// performing a cooperative channel closure.
|
|
|
|
if !force {
|
2020-12-08 16:27:01 +01:00
|
|
|
timeout := DefaultTimeout
|
2017-11-19 01:38:30 +01:00
|
|
|
listReq := &lnrpc.ListChannelsRequest{}
|
2017-12-21 11:43:07 +01:00
|
|
|
|
|
|
|
// We define two helper functions, one two locate a particular
|
|
|
|
// channel, and the other to check if a channel is active or
|
|
|
|
// not.
|
|
|
|
filterChannel := func(node *HarnessNode,
|
2018-03-13 20:11:30 +01:00
|
|
|
op wire.OutPoint) (*lnrpc.Channel, error) {
|
2021-09-14 19:41:24 +02:00
|
|
|
listResp, err := node.ListChannels(ctxt, listReq)
|
2017-12-21 11:43:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range listResp.Channels {
|
|
|
|
if c.ChannelPoint == op.String() {
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("unable to find channel")
|
2017-11-19 01:38:30 +01:00
|
|
|
}
|
2017-12-21 11:43:07 +01:00
|
|
|
activeChanPredicate := func(node *HarnessNode) func() bool {
|
|
|
|
return func() bool {
|
|
|
|
channel, err := filterChannel(node, chanPoint)
|
|
|
|
if err != nil {
|
2018-04-18 14:19:58 +02:00
|
|
|
return false
|
2017-12-21 11:43:07 +01:00
|
|
|
}
|
2017-11-19 01:38:30 +01:00
|
|
|
|
2017-12-21 11:43:07 +01:00
|
|
|
return channel.Active
|
2017-11-19 01:38:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-21 11:43:07 +01:00
|
|
|
// Next, we'll fetch the target channel in order to get the
|
2021-09-14 19:41:24 +02:00
|
|
|
// harness node that will be receiving the channel close
|
|
|
|
// request.
|
2017-12-21 11:43:07 +01:00
|
|
|
targetChan, err := filterChannel(lnNode, chanPoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
receivingNode, err := n.LookUpNodeByPub(targetChan.RemotePubkey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
2017-11-19 01:38:30 +01:00
|
|
|
}
|
|
|
|
|
2017-12-21 11:43:07 +01:00
|
|
|
// Before proceeding, we'll ensure that the channel is active
|
|
|
|
// for both nodes.
|
2019-09-19 21:46:29 +02:00
|
|
|
err = wait.Predicate(activeChanPredicate(lnNode), timeout)
|
2017-12-21 11:43:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("channel of closing " +
|
|
|
|
"node not active in time")
|
|
|
|
}
|
2021-09-14 19:41:24 +02:00
|
|
|
err = wait.Predicate(
|
|
|
|
activeChanPredicate(receivingNode), timeout,
|
|
|
|
)
|
2017-12-21 11:43:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("channel of receiving " +
|
|
|
|
"node not active in time")
|
|
|
|
}
|
2017-11-19 01:38:30 +01:00
|
|
|
}
|
|
|
|
|
2021-08-25 09:22:49 +02:00
|
|
|
var (
|
|
|
|
closeRespStream lnrpc.Lightning_CloseChannelClient
|
|
|
|
closeTxid *chainhash.Hash
|
|
|
|
)
|
2016-08-31 20:59:08 +02:00
|
|
|
|
2021-08-25 09:22:49 +02:00
|
|
|
err = wait.NoError(func() error {
|
|
|
|
closeReq := &lnrpc.CloseChannelRequest{
|
|
|
|
ChannelPoint: cp, Force: force,
|
|
|
|
}
|
2021-09-14 19:41:24 +02:00
|
|
|
// We need to use n.runCtx to keep the client stream alive
|
|
|
|
// after the function has returned.
|
|
|
|
closeRespStream, err = lnNode.CloseChannel(n.runCtx, closeReq)
|
2021-08-25 09:22:49 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to close channel: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Consume the "channel close" update in order to wait for the
|
|
|
|
// closing transaction to be broadcast, then wait for the
|
|
|
|
// closing tx to be seen within the network.
|
2016-09-18 02:34:39 +02:00
|
|
|
closeResp, err := closeRespStream.Recv()
|
|
|
|
if err != nil {
|
2021-08-25 09:22:49 +02:00
|
|
|
return fmt.Errorf("unable to recv() from close "+
|
2020-03-09 15:10:14 +01:00
|
|
|
"stream: %v", err)
|
2016-09-18 02:34:39 +02:00
|
|
|
}
|
|
|
|
pendingClose, ok := closeResp.Update.(*lnrpc.CloseStatusUpdate_ClosePending)
|
|
|
|
if !ok {
|
2021-08-25 09:22:49 +02:00
|
|
|
return fmt.Errorf("expected channel close update, "+
|
2016-09-18 02:34:39 +02:00
|
|
|
"instead got %v", pendingClose)
|
|
|
|
}
|
|
|
|
|
2021-08-25 09:22:49 +02:00
|
|
|
closeTxid, err = chainhash.NewHash(
|
|
|
|
pendingClose.ClosePending.Txid,
|
|
|
|
)
|
2016-09-18 02:34:39 +02:00
|
|
|
if err != nil {
|
2021-08-25 09:22:49 +02:00
|
|
|
return fmt.Errorf("unable to decode closeTxid: "+
|
2020-03-09 15:10:14 +01:00
|
|
|
"%v", err)
|
2016-09-18 02:34:39 +02:00
|
|
|
}
|
2021-09-18 08:41:20 +02:00
|
|
|
if err := n.Miner.waitForTxInMempool(*closeTxid); err != nil {
|
2021-08-25 09:22:49 +02:00
|
|
|
return fmt.Errorf("error while waiting for "+
|
2020-03-09 15:10:14 +01:00
|
|
|
"broadcast tx: %v", err)
|
2016-09-18 02:34:39 +02:00
|
|
|
}
|
2021-08-25 09:22:49 +02:00
|
|
|
return nil
|
|
|
|
}, ChannelCloseTimeout)
|
|
|
|
if err != nil {
|
2016-12-14 00:32:44 +01:00
|
|
|
return nil, nil, err
|
2016-09-14 03:56:35 +02:00
|
|
|
}
|
2021-08-25 09:22:49 +02:00
|
|
|
|
|
|
|
return closeRespStream, closeTxid, nil
|
2016-08-31 20:59:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// WaitForChannelClose waits for a notification from the passed channel close
|
2016-09-18 02:34:39 +02:00
|
|
|
// stream that the node has deemed the channel has been fully closed. If the
|
|
|
|
// passed context has a timeout, then if the timeout is reached before the
|
|
|
|
// notification is received then an error is returned.
|
2021-08-19 19:20:06 +02:00
|
|
|
func (n *NetworkHarness) WaitForChannelClose(
|
2021-09-14 19:41:24 +02:00
|
|
|
closeChanStream lnrpc.Lightning_CloseChannelClient) (
|
|
|
|
*chainhash.Hash, error) {
|
2021-08-19 19:20:06 +02:00
|
|
|
|
2016-09-18 02:34:39 +02:00
|
|
|
errChan := make(chan error)
|
|
|
|
updateChan := make(chan *lnrpc.CloseStatusUpdate_ChanClose)
|
|
|
|
go func() {
|
|
|
|
closeResp, err := closeChanStream.Recv()
|
|
|
|
if err != nil {
|
|
|
|
errChan <- err
|
|
|
|
return
|
|
|
|
}
|
2016-08-31 20:59:08 +02:00
|
|
|
|
2016-09-18 02:34:39 +02:00
|
|
|
closeFin, ok := closeResp.Update.(*lnrpc.CloseStatusUpdate_ChanClose)
|
|
|
|
if !ok {
|
|
|
|
errChan <- fmt.Errorf("expected channel close update, "+
|
|
|
|
"instead got %v", closeFin)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
updateChan <- closeFin
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Wait until either the deadline for the context expires, an error
|
|
|
|
// occurs, or the channel close update is received.
|
|
|
|
select {
|
2021-09-14 19:41:24 +02:00
|
|
|
case <-time.After(ChannelCloseTimeout):
|
2016-09-18 02:34:39 +02:00
|
|
|
return nil, fmt.Errorf("timeout reached before update sent")
|
|
|
|
case err := <-errChan:
|
|
|
|
return nil, err
|
|
|
|
case update := <-updateChan:
|
2017-01-05 22:56:27 +01:00
|
|
|
return chainhash.NewHash(update.ChanClose.ClosingTxid)
|
2016-09-18 02:34:39 +02:00
|
|
|
}
|
2016-08-31 20:59:08 +02:00
|
|
|
}
|
|
|
|
|
2018-05-23 07:45:55 +02:00
|
|
|
// AssertChannelExists asserts that an active channel identified by the
|
2020-07-29 09:27:22 +02:00
|
|
|
// specified channel point exists from the point-of-view of the node. It takes
|
|
|
|
// an optional set of check functions which can be used to make further
|
|
|
|
// assertions using channel's values. These functions are responsible for
|
|
|
|
// failing the test themselves if they do not pass.
|
2021-08-19 19:35:02 +02:00
|
|
|
func (n *NetworkHarness) AssertChannelExists(node *HarnessNode,
|
|
|
|
chanPoint *wire.OutPoint, checks ...func(*lnrpc.Channel)) error {
|
|
|
|
|
2021-09-14 18:58:25 +02:00
|
|
|
ctx, cancel := context.WithTimeout(n.runCtx, ChannelCloseTimeout)
|
2021-08-19 19:35:02 +02:00
|
|
|
defer cancel()
|
2016-08-31 20:59:08 +02:00
|
|
|
|
2016-09-26 19:31:07 +02:00
|
|
|
req := &lnrpc.ListChannelsRequest{}
|
2017-12-21 11:35:23 +01:00
|
|
|
|
2019-11-20 05:12:48 +01:00
|
|
|
return wait.NoError(func() error {
|
2017-12-21 11:35:23 +01:00
|
|
|
resp, err := node.ListChannels(ctx, req)
|
|
|
|
if err != nil {
|
2019-11-20 05:12:48 +01:00
|
|
|
return fmt.Errorf("unable fetch node's channels: %v", err)
|
2017-12-21 11:35:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, channel := range resp.Channels {
|
|
|
|
if channel.ChannelPoint == chanPoint.String() {
|
2020-07-29 09:27:22 +02:00
|
|
|
// First check whether our channel is active,
|
|
|
|
// failing early if it is not.
|
|
|
|
if !channel.Active {
|
|
|
|
return fmt.Errorf("channel %s inactive",
|
|
|
|
chanPoint)
|
2019-11-20 05:12:48 +01:00
|
|
|
}
|
2017-12-21 11:35:23 +01:00
|
|
|
|
2020-07-29 09:27:22 +02:00
|
|
|
// Apply any additional checks that we would
|
|
|
|
// like to verify.
|
|
|
|
for _, check := range checks {
|
|
|
|
check(channel)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2019-11-20 05:12:48 +01:00
|
|
|
}
|
2017-12-21 11:35:23 +01:00
|
|
|
}
|
|
|
|
|
2019-11-20 05:12:48 +01:00
|
|
|
return fmt.Errorf("channel %s not found", chanPoint)
|
2020-12-08 16:27:01 +01:00
|
|
|
}, DefaultTimeout)
|
2017-12-21 11:35:23 +01:00
|
|
|
}
|
|
|
|
|
2016-09-10 22:14:28 +02:00
|
|
|
// DumpLogs reads the current logs generated by the passed node, and returns
|
|
|
|
// the logs as a single string. This function is useful for examining the logs
|
|
|
|
// of a particular node in the case of a test failure.
|
2016-09-15 20:59:51 +02:00
|
|
|
// Logs from lightning node being generated with delay - you should
|
|
|
|
// add time.Sleep() in order to get all logs.
|
2017-11-03 19:52:02 +01:00
|
|
|
func (n *NetworkHarness) DumpLogs(node *HarnessNode) (string, error) {
|
2019-12-17 10:48:17 +01:00
|
|
|
logFile := fmt.Sprintf("%v/simnet/lnd.log", node.Cfg.LogDir)
|
2016-09-10 22:14:28 +02:00
|
|
|
|
2016-09-15 20:59:51 +02:00
|
|
|
buf, err := ioutil.ReadFile(logFile)
|
2016-08-30 06:55:01 +02:00
|
|
|
if err != nil {
|
2016-09-10 22:14:28 +02:00
|
|
|
return "", err
|
2016-08-30 06:55:01 +02:00
|
|
|
}
|
|
|
|
|
2016-09-15 20:59:51 +02:00
|
|
|
return string(buf), nil
|
2016-08-30 06:55:01 +02:00
|
|
|
}
|
2016-09-26 19:31:07 +02:00
|
|
|
|
2017-01-11 23:21:04 +01:00
|
|
|
// SendCoins attempts to send amt satoshis from the internal mining node to the
|
2018-08-10 06:16:20 +02:00
|
|
|
// targeted lightning node using a P2WKH address. 6 blocks are mined after in
|
|
|
|
// order to confirm the transaction.
|
2021-08-19 14:49:39 +02:00
|
|
|
func (n *NetworkHarness) SendCoins(t *testing.T, amt btcutil.Amount,
|
|
|
|
target *HarnessNode) {
|
2016-09-26 19:31:07 +02:00
|
|
|
|
2021-06-09 19:29:22 +02:00
|
|
|
err := n.sendCoins(
|
2021-08-19 14:49:39 +02:00
|
|
|
amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, true,
|
2018-08-10 06:16:20 +02:00
|
|
|
)
|
2021-06-09 19:29:22 +02:00
|
|
|
require.NoErrorf(t, err, "unable to send coins for %s", target.Cfg.Name)
|
2018-08-10 06:16:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SendCoinsUnconfirmed sends coins from the internal mining node to the target
|
|
|
|
// lightning node using a P2WPKH address. No blocks are mined after, so the
|
|
|
|
// transaction remains unconfirmed.
|
2021-08-19 14:49:39 +02:00
|
|
|
func (n *NetworkHarness) SendCoinsUnconfirmed(t *testing.T, amt btcutil.Amount,
|
|
|
|
target *HarnessNode) {
|
2018-08-10 06:16:20 +02:00
|
|
|
|
2021-06-09 19:29:22 +02:00
|
|
|
err := n.sendCoins(
|
2021-08-19 14:49:39 +02:00
|
|
|
amt, target, lnrpc.AddressType_WITNESS_PUBKEY_HASH, false,
|
2018-04-03 01:57:04 +02:00
|
|
|
)
|
2021-06-09 19:29:22 +02:00
|
|
|
require.NoErrorf(
|
|
|
|
t, err, "unable to send unconfirmed coins for %s",
|
|
|
|
target.Cfg.Name,
|
|
|
|
)
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SendCoinsNP2WKH attempts to send amt satoshis from the internal mining node
|
|
|
|
// to the targeted lightning node using a NP2WKH address.
|
2021-08-19 14:49:39 +02:00
|
|
|
func (n *NetworkHarness) SendCoinsNP2WKH(t *testing.T, amt btcutil.Amount,
|
|
|
|
target *HarnessNode) {
|
2018-04-03 01:57:04 +02:00
|
|
|
|
2021-06-09 19:29:22 +02:00
|
|
|
err := n.sendCoins(
|
2021-08-19 14:49:39 +02:00
|
|
|
amt, target, lnrpc.AddressType_NESTED_PUBKEY_HASH, true,
|
2018-04-03 01:57:04 +02:00
|
|
|
)
|
2021-06-09 19:29:22 +02:00
|
|
|
require.NoErrorf(
|
|
|
|
t, err, "unable to send NP2WKH coins for %s",
|
|
|
|
target.Cfg.Name,
|
|
|
|
)
|
2018-04-03 01:57:04 +02:00
|
|
|
}
|
|
|
|
|
2022-03-18 18:37:48 +01:00
|
|
|
// SendCoinsP2TR attempts to send amt satoshis from the internal mining node
|
|
|
|
// to the targeted lightning node using a P2TR address.
|
|
|
|
func (n *NetworkHarness) SendCoinsP2TR(t *testing.T, amt btcutil.Amount,
|
|
|
|
target *HarnessNode) {
|
|
|
|
|
|
|
|
err := n.sendCoins(amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true)
|
|
|
|
require.NoErrorf(
|
|
|
|
t, err, "unable to send P2TR coins for %s", target.Cfg.Name,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-04-03 01:57:04 +02:00
|
|
|
// sendCoins attempts to send amt satoshis from the internal mining node to the
|
2018-08-10 06:16:20 +02:00
|
|
|
// targeted lightning node. The confirmed boolean indicates whether the
|
|
|
|
// transaction that pays to the target should confirm.
|
2021-08-19 14:49:39 +02:00
|
|
|
func (n *NetworkHarness) sendCoins(amt btcutil.Amount, target *HarnessNode,
|
|
|
|
addrType lnrpc.AddressType, confirmed bool) error {
|
|
|
|
|
2021-09-14 18:58:25 +02:00
|
|
|
ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout)
|
2021-08-19 14:49:39 +02:00
|
|
|
defer cancel()
|
2018-04-03 01:57:04 +02:00
|
|
|
|
2016-09-26 20:54:13 +02:00
|
|
|
balReq := &lnrpc.WalletBalanceRequest{}
|
|
|
|
initialBalance, err := target.WalletBalance(ctx, balReq)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-09-26 19:31:07 +02:00
|
|
|
// First, obtain an address from the target lightning node, preferring
|
|
|
|
// to receive a p2wkh address s.t the output can immediately be used as
|
|
|
|
// an input to a funding transaction.
|
|
|
|
addrReq := &lnrpc.NewAddressRequest{
|
2018-04-03 01:57:04 +02:00
|
|
|
Type: addrType,
|
2016-09-26 19:31:07 +02:00
|
|
|
}
|
|
|
|
resp, err := target.NewAddress(ctx, addrReq)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addr, err := btcutil.DecodeAddress(resp.Address, n.netParams)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addrScript, err := txscript.PayToAddrScript(addr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a transaction which creates an output to the target
|
|
|
|
// pkScript of the desired amount.
|
|
|
|
output := &wire.TxOut{
|
|
|
|
PkScript: addrScript,
|
|
|
|
Value: int64(amt),
|
|
|
|
}
|
2018-07-28 03:20:58 +02:00
|
|
|
_, err = n.Miner.SendOutputs([]*wire.TxOut{output}, 7500)
|
|
|
|
if err != nil {
|
2016-09-26 19:31:07 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-02-11 22:02:11 +01:00
|
|
|
// Encode the pkScript in hex as this the format that it will be
|
|
|
|
// returned via rpc.
|
|
|
|
expPkScriptStr := hex.EncodeToString(addrScript)
|
|
|
|
|
|
|
|
// Now, wait for ListUnspent to show the unconfirmed transaction
|
|
|
|
// containing the correct pkscript.
|
2019-09-19 21:46:29 +02:00
|
|
|
err = wait.NoError(func() error {
|
2019-05-24 14:17:49 +02:00
|
|
|
// Since neutrino doesn't support unconfirmed outputs, skip
|
|
|
|
// this check.
|
2019-12-17 10:48:17 +01:00
|
|
|
if target.Cfg.BackendCfg.Name() == "neutrino" {
|
2019-05-24 14:17:49 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-02-11 22:02:11 +01:00
|
|
|
req := &lnrpc.ListUnspentRequest{}
|
|
|
|
resp, err := target.ListUnspent(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// When using this method, there should only ever be on
|
|
|
|
// unconfirmed transaction.
|
|
|
|
if len(resp.Utxos) != 1 {
|
|
|
|
return fmt.Errorf("number of unconfirmed utxos "+
|
|
|
|
"should be 1, found %d", len(resp.Utxos))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assert that the lone unconfirmed utxo contains the same
|
|
|
|
// pkscript as the output generated above.
|
2019-02-11 22:02:25 +01:00
|
|
|
pkScriptStr := resp.Utxos[0].PkScript
|
2019-02-11 22:02:11 +01:00
|
|
|
if strings.Compare(pkScriptStr, expPkScriptStr) != 0 {
|
|
|
|
return fmt.Errorf("pkscript mismatch, want: %s, "+
|
|
|
|
"found: %s", expPkScriptStr, pkScriptStr)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-12-08 16:27:01 +01:00
|
|
|
}, DefaultTimeout)
|
2019-02-11 22:02:11 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unconfirmed utxo was not found in "+
|
|
|
|
"ListUnspent: %v", err)
|
|
|
|
}
|
|
|
|
|
2018-08-10 06:16:20 +02:00
|
|
|
// If the transaction should remain unconfirmed, then we'll wait until
|
|
|
|
// the target node's unconfirmed balance reflects the expected balance
|
|
|
|
// and exit.
|
|
|
|
if !confirmed {
|
2018-09-10 15:02:06 +02:00
|
|
|
expectedBalance := btcutil.Amount(initialBalance.UnconfirmedBalance) + amt
|
2018-08-10 06:16:20 +02:00
|
|
|
return target.WaitForBalance(expectedBalance, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we'll generate 6 new blocks to ensure the output gains a
|
|
|
|
// sufficient number of confirmations and wait for the balance to
|
|
|
|
// reflect what's expected.
|
2021-03-09 23:12:26 +01:00
|
|
|
if _, err := n.Miner.Client.Generate(6); err != nil {
|
2016-09-26 19:31:07 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-10-29 14:18:08 +02:00
|
|
|
fullInitialBalance := initialBalance.ConfirmedBalance +
|
|
|
|
initialBalance.UnconfirmedBalance
|
|
|
|
expectedBalance := btcutil.Amount(fullInitialBalance) + amt
|
2018-08-10 06:15:40 +02:00
|
|
|
return target.WaitForBalance(expectedBalance, true)
|
2016-09-26 19:31:07 +02:00
|
|
|
}
|
2018-10-10 12:55:14 +02:00
|
|
|
|
2020-09-10 15:48:39 +02:00
|
|
|
func (n *NetworkHarness) SetFeeEstimate(fee chainfee.SatPerKWeight) {
|
|
|
|
n.feeService.setFee(fee)
|
|
|
|
}
|
|
|
|
|
2021-06-04 00:11:57 +02:00
|
|
|
func (n *NetworkHarness) SetFeeEstimateWithConf(
|
|
|
|
fee chainfee.SatPerKWeight, conf uint32) {
|
|
|
|
|
|
|
|
n.feeService.setFeeWithConf(fee, conf)
|
|
|
|
}
|
|
|
|
|
2021-06-16 07:57:07 +02:00
|
|
|
// copyAll copies all files and directories from srcDir to dstDir recursively.
|
2020-10-26 16:36:09 +01:00
|
|
|
// Note that this function does not support links.
|
2021-06-16 07:57:07 +02:00
|
|
|
func copyAll(dstDir, srcDir string) error {
|
2020-10-26 16:36:09 +01:00
|
|
|
entries, err := ioutil.ReadDir(srcDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
srcPath := filepath.Join(srcDir, entry.Name())
|
|
|
|
dstPath := filepath.Join(dstDir, entry.Name())
|
|
|
|
|
|
|
|
info, err := os.Stat(srcPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
err := os.Mkdir(dstPath, info.Mode())
|
|
|
|
if err != nil && !os.IsExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-06-16 07:57:07 +02:00
|
|
|
err = copyAll(dstPath, srcPath)
|
2020-10-26 16:36:09 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if err := CopyFile(dstPath, srcPath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2021-06-16 07:57:07 +02:00
|
|
|
|
|
|
|
// BackupDb creates a backup of the current database.
|
|
|
|
func (n *NetworkHarness) BackupDb(hn *HarnessNode) error {
|
|
|
|
if hn.backupDbDir != "" {
|
|
|
|
return errors.New("backup already created")
|
|
|
|
}
|
|
|
|
|
2021-06-14 16:24:02 +02:00
|
|
|
restart, err := n.SuspendNode(hn)
|
2021-06-16 07:57:07 +02:00
|
|
|
if err != nil {
|
2021-06-14 16:24:02 +02:00
|
|
|
return err
|
2021-06-16 07:57:07 +02:00
|
|
|
}
|
|
|
|
|
2021-06-14 16:24:02 +02:00
|
|
|
if hn.postgresDbName != "" {
|
|
|
|
// Backup database.
|
|
|
|
backupDbName := hn.postgresDbName + "_backup"
|
|
|
|
err := executePgQuery(
|
|
|
|
"CREATE DATABASE " + backupDbName + " WITH TEMPLATE " +
|
|
|
|
hn.postgresDbName,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Backup files.
|
|
|
|
tempDir, err := ioutil.TempDir("", "past-state")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to create temp db folder: %v",
|
|
|
|
err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := copyAll(tempDir, hn.DBDir()); err != nil {
|
|
|
|
return fmt.Errorf("unable to copy database files: %v",
|
|
|
|
err)
|
|
|
|
}
|
|
|
|
|
|
|
|
hn.backupDbDir = tempDir
|
2021-06-16 07:57:07 +02:00
|
|
|
}
|
|
|
|
|
2021-06-14 16:24:02 +02:00
|
|
|
err = restart()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-16 07:57:07 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RestoreDb restores a database backup.
|
|
|
|
func (n *NetworkHarness) RestoreDb(hn *HarnessNode) error {
|
2021-06-14 16:24:02 +02:00
|
|
|
if hn.postgresDbName != "" {
|
|
|
|
// Restore database.
|
|
|
|
backupDbName := hn.postgresDbName + "_backup"
|
|
|
|
err := executePgQuery(
|
|
|
|
"DROP DATABASE " + hn.postgresDbName,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = executePgQuery(
|
|
|
|
"ALTER DATABASE " + backupDbName + " RENAME TO " + hn.postgresDbName,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Restore files.
|
|
|
|
if hn.backupDbDir == "" {
|
|
|
|
return errors.New("no database backup created")
|
|
|
|
}
|
2021-06-16 07:57:07 +02:00
|
|
|
|
2021-06-14 16:24:02 +02:00
|
|
|
if err := copyAll(hn.DBDir(), hn.backupDbDir); err != nil {
|
|
|
|
return fmt.Errorf("unable to copy database files: %v", err)
|
|
|
|
}
|
2021-06-16 07:57:07 +02:00
|
|
|
|
2021-06-14 16:24:02 +02:00
|
|
|
if err := os.RemoveAll(hn.backupDbDir); err != nil {
|
|
|
|
return fmt.Errorf("unable to remove backup dir: %v", err)
|
|
|
|
}
|
|
|
|
hn.backupDbDir = ""
|
2021-06-16 07:57:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2021-09-18 07:00:39 +02:00
|
|
|
|
|
|
|
// getChanPointFundingTxid returns the given channel point's funding txid in
|
|
|
|
// raw bytes.
|
|
|
|
func getChanPointFundingTxid(chanPoint *lnrpc.ChannelPoint) ([]byte, error) {
|
|
|
|
var txid []byte
|
|
|
|
|
|
|
|
// A channel point's funding txid can be get/set as a byte slice or a
|
|
|
|
// string. In the case it is a string, decode it.
|
|
|
|
switch chanPoint.GetFundingTxid().(type) {
|
|
|
|
case *lnrpc.ChannelPoint_FundingTxidBytes:
|
|
|
|
txid = chanPoint.GetFundingTxidBytes()
|
|
|
|
case *lnrpc.ChannelPoint_FundingTxidStr:
|
|
|
|
s := chanPoint.GetFundingTxidStr()
|
|
|
|
h, err := chainhash.NewHashFromStr(s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
txid = h[:]
|
|
|
|
}
|
|
|
|
|
|
|
|
return txid, nil
|
|
|
|
}
|