lnd_test: add integration tests for stateless init
This commit is contained in:
parent
e7aa9256ab
commit
24adf475ce
@ -270,11 +270,12 @@ func (n *NetworkHarness) NewNode(name string, extraArgs []string) (*HarnessNode,
|
|||||||
// wallet password. The generated mnemonic is returned along with the
|
// wallet password. The generated mnemonic is returned along with the
|
||||||
// initialized harness node.
|
// initialized harness node.
|
||||||
func (n *NetworkHarness) NewNodeWithSeed(name string, extraArgs []string,
|
func (n *NetworkHarness) NewNodeWithSeed(name string, extraArgs []string,
|
||||||
password []byte) (*HarnessNode, []string, error) {
|
password []byte, statelessInit bool) (*HarnessNode, []string, []byte,
|
||||||
|
error) {
|
||||||
|
|
||||||
node, err := n.newNode(name, extraArgs, true, password)
|
node, err := n.newNode(name, extraArgs, true, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := time.Duration(time.Second * 15)
|
timeout := time.Duration(time.Second * 15)
|
||||||
@ -289,7 +290,7 @@ func (n *NetworkHarness) NewNodeWithSeed(name string, extraArgs []string,
|
|||||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||||
genSeedResp, err := node.GenSeed(ctxt, genSeedReq)
|
genSeedResp, err := node.GenSeed(ctxt, genSeedReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the seed created, construct the init request to the node,
|
// With the seed created, construct the init request to the node,
|
||||||
@ -298,20 +299,25 @@ func (n *NetworkHarness) NewNodeWithSeed(name string, extraArgs []string,
|
|||||||
WalletPassword: password,
|
WalletPassword: password,
|
||||||
CipherSeedMnemonic: genSeedResp.CipherSeedMnemonic,
|
CipherSeedMnemonic: genSeedResp.CipherSeedMnemonic,
|
||||||
AezeedPassphrase: password,
|
AezeedPassphrase: password,
|
||||||
|
StatelessInit: statelessInit,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass the init request via rpc to finish unlocking the node. This will
|
// Pass the init request via rpc to finish unlocking the node. This will
|
||||||
// also initialize the macaroon-authenticated LightningClient.
|
// also initialize the macaroon-authenticated LightningClient.
|
||||||
err = node.Init(ctxb, initReq)
|
response, err := node.Init(ctxb, initReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the node started, we can now record its public key within the
|
// With the node started, we can now record its public key within the
|
||||||
// global mapping.
|
// global mapping.
|
||||||
n.RegisterNode(node)
|
n.RegisterNode(node)
|
||||||
|
|
||||||
return node, genSeedResp.CipherSeedMnemonic, nil
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreNodeWithSeed fully initializes a HarnessNode using a chosen mnemonic,
|
// RestoreNodeWithSeed fully initializes a HarnessNode using a chosen mnemonic,
|
||||||
@ -336,7 +342,7 @@ func (n *NetworkHarness) RestoreNodeWithSeed(name string, extraArgs []string,
|
|||||||
ChannelBackups: chanBackups,
|
ChannelBackups: chanBackups,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = node.Init(context.Background(), initReq)
|
_, err = node.Init(context.Background(), initReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -616,17 +622,8 @@ func (n *NetworkHarness) DisconnectNodes(ctx context.Context, a, b *HarnessNode)
|
|||||||
func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error,
|
func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error,
|
||||||
chanBackups ...*lnrpc.ChanBackupSnapshot) error {
|
chanBackups ...*lnrpc.ChanBackupSnapshot) error {
|
||||||
|
|
||||||
if err := node.stop(); err != nil {
|
err := n.RestartNodeNoUnlock(node, callback)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
if callback != nil {
|
|
||||||
if err := callback(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := node.start(n.lndBinary, n.lndErrorChan); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,6 +646,27 @@ func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error,
|
|||||||
return node.Unlock(context.Background(), unlockReq)
|
return node.Unlock(context.Background(), unlockReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
callback func() error) error {
|
||||||
|
|
||||||
|
if err := node.stop(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if callback != nil {
|
||||||
|
if err := callback(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.start(n.lndBinary, n.lndErrorChan)
|
||||||
|
}
|
||||||
|
|
||||||
// SuspendNode stops the given node and returns a callback that can be used to
|
// SuspendNode stops the given node and returns a callback that can be used to
|
||||||
// start it again.
|
// start it again.
|
||||||
func (n *NetworkHarness) SuspendNode(node *HarnessNode) (func() error, error) {
|
func (n *NetworkHarness) SuspendNode(node *HarnessNode) (func() error, error) {
|
||||||
|
@ -797,8 +797,8 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness,
|
|||||||
// First, we'll create a brand new node we'll use within the test. If
|
// First, we'll create a brand new node we'll use within the test. If
|
||||||
// we have a custom backup file specified, then we'll also create that
|
// we have a custom backup file specified, then we'll also create that
|
||||||
// for use.
|
// for use.
|
||||||
dave, mnemonic, err := net.NewNodeWithSeed(
|
dave, mnemonic, _, err := net.NewNodeWithSeed(
|
||||||
"dave", nodeArgs, password,
|
"dave", nodeArgs, password, false,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new node: %v", err)
|
t.Fatalf("unable to create new node: %v", err)
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package itest
|
package itest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
@ -486,6 +489,105 @@ func testDeleteMacaroonID(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
require.Contains(t.t, err.Error(), "cannot get macaroon")
|
require.Contains(t.t, err.Error(), "cannot get macaroon")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testStatelessInit checks that the stateless initialization of the daemon
|
||||||
|
// does not write any macaroon files to the daemon's file system and returns
|
||||||
|
// the admin macaroon in the response. It then checks that the password
|
||||||
|
// change of the wallet can also happen stateless.
|
||||||
|
func testStatelessInit(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
|
var (
|
||||||
|
initPw = []byte("stateless")
|
||||||
|
newPw = []byte("stateless-new")
|
||||||
|
newAddrReq = &lnrpc.NewAddressRequest{
|
||||||
|
Type: AddrTypeWitnessPubkeyHash,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// First, create a new node and request it to initialize stateless.
|
||||||
|
// This should return us the binary serialized admin macaroon that we
|
||||||
|
// can then use for further calls.
|
||||||
|
carol, _, macBytes, err := net.NewNodeWithSeed(
|
||||||
|
"Carol", nil, initPw, true,
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
if len(macBytes) == 0 {
|
||||||
|
t.Fatalf("invalid macaroon returned in stateless init")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now make sure no macaroon files have been created by the node Carol.
|
||||||
|
_, err = os.Stat(carol.AdminMacPath())
|
||||||
|
require.Error(t.t, err)
|
||||||
|
_, err = os.Stat(carol.ReadMacPath())
|
||||||
|
require.Error(t.t, err)
|
||||||
|
_, err = os.Stat(carol.InvoiceMacPath())
|
||||||
|
require.Error(t.t, err)
|
||||||
|
|
||||||
|
// Then check that we can unmarshal the binary serialized macaroon.
|
||||||
|
adminMac := &macaroon.Macaroon{}
|
||||||
|
err = adminMac.UnmarshalBinary(macBytes)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Find out if we can actually use the macaroon that has been returned
|
||||||
|
// to us for a RPC call.
|
||||||
|
conn, err := carol.ConnectRPCWithMacaroon(adminMac)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
defer conn.Close()
|
||||||
|
adminMacClient := lnrpc.NewLightningClient(conn)
|
||||||
|
ctxt, _ := context.WithTimeout(context.Background(), defaultTimeout)
|
||||||
|
res, err := adminMacClient.NewAddress(ctxt, newAddrReq)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
if !strings.HasPrefix(res.Address, harnessNetParams.Bech32HRPSegwit) {
|
||||||
|
t.Fatalf("returned address was not a regtest address")
|
||||||
|
}
|
||||||
|
|
||||||
|
// As a second part, shut down the node and then try to change the
|
||||||
|
// password when we start it up again.
|
||||||
|
if err := net.RestartNodeNoUnlock(carol, nil); err != nil {
|
||||||
|
t.Fatalf("Node restart failed: %v", err)
|
||||||
|
}
|
||||||
|
changePwReq := &lnrpc.ChangePasswordRequest{
|
||||||
|
CurrentPassword: initPw,
|
||||||
|
NewPassword: newPw,
|
||||||
|
StatelessInit: true,
|
||||||
|
}
|
||||||
|
ctxb := context.Background()
|
||||||
|
response, err := carol.InitChangePassword(ctxb, changePwReq)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Again, make sure no macaroon files have been created by the node
|
||||||
|
// Carol.
|
||||||
|
_, err = os.Stat(carol.AdminMacPath())
|
||||||
|
require.Error(t.t, err)
|
||||||
|
_, err = os.Stat(carol.ReadMacPath())
|
||||||
|
require.Error(t.t, err)
|
||||||
|
_, err = os.Stat(carol.InvoiceMacPath())
|
||||||
|
require.Error(t.t, err)
|
||||||
|
|
||||||
|
// Then check that we can unmarshal the new binary serialized macaroon
|
||||||
|
// and that it really is a new macaroon.
|
||||||
|
if err = adminMac.UnmarshalBinary(response.AdminMacaroon); err != nil {
|
||||||
|
t.Fatalf("unable to unmarshal macaroon: %v", err)
|
||||||
|
}
|
||||||
|
if bytes.Equal(response.AdminMacaroon, macBytes) {
|
||||||
|
t.Fatalf("expected new macaroon to be different")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, find out if we can actually use the new macaroon that has
|
||||||
|
// been returned to us for a RPC call.
|
||||||
|
conn2, err := carol.ConnectRPCWithMacaroon(adminMac)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
defer conn2.Close()
|
||||||
|
adminMacClient = lnrpc.NewLightningClient(conn2)
|
||||||
|
|
||||||
|
// Changing the password takes a while, so we use the default timeout
|
||||||
|
// of 30 seconds to wait for the connection to be ready.
|
||||||
|
ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout)
|
||||||
|
res, err = adminMacClient.NewAddress(ctxt, newAddrReq)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
if !strings.HasPrefix(res.Address, harnessNetParams.Bech32HRPSegwit) {
|
||||||
|
t.Fatalf("returned address was not a regtest address")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// readMacaroonFromHex loads a macaroon from a hex string.
|
// readMacaroonFromHex loads a macaroon from a hex string.
|
||||||
func readMacaroonFromHex(macHex string) (*macaroon.Macaroon, error) {
|
func readMacaroonFromHex(macHex string) (*macaroon.Macaroon, error) {
|
||||||
macBytes, err := hex.DecodeString(macHex)
|
macBytes, err := hex.DecodeString(macHex)
|
||||||
|
@ -774,7 +774,9 @@ func testGetRecoveryInfo(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// used for key derivation. This will bring up Carol with an empty
|
// used for key derivation. This will bring up Carol with an empty
|
||||||
// wallet, and such that she is synced up.
|
// wallet, and such that she is synced up.
|
||||||
password := []byte("The Magic Words are Squeamish Ossifrage")
|
password := []byte("The Magic Words are Squeamish Ossifrage")
|
||||||
carol, mnemonic, err := net.NewNodeWithSeed("Carol", nil, password)
|
carol, mnemonic, _, err := net.NewNodeWithSeed(
|
||||||
|
"Carol", nil, password, false,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create node with seed; %v", err)
|
t.Fatalf("unable to create node with seed; %v", err)
|
||||||
}
|
}
|
||||||
@ -875,7 +877,9 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
// used for key derivation. This will bring up Carol with an empty
|
// used for key derivation. This will bring up Carol with an empty
|
||||||
// wallet, and such that she is synced up.
|
// wallet, and such that she is synced up.
|
||||||
password := []byte("The Magic Words are Squeamish Ossifrage")
|
password := []byte("The Magic Words are Squeamish Ossifrage")
|
||||||
carol, mnemonic, err := net.NewNodeWithSeed("Carol", nil, password)
|
carol, mnemonic, _, err := net.NewNodeWithSeed(
|
||||||
|
"Carol", nil, password, false,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create node with seed; %v", err)
|
t.Fatalf("unable to create node with seed; %v", err)
|
||||||
}
|
}
|
||||||
|
@ -282,4 +282,8 @@ var allTestCases = []*testCase{
|
|||||||
name: "connection timeout",
|
name: "connection timeout",
|
||||||
test: testNetworkConnectionTimeout,
|
test: testNetworkConnectionTimeout,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "stateless init",
|
||||||
|
test: testStatelessInit,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -613,22 +613,88 @@ func (hn *HarnessNode) initClientWhenReady() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes a harness node by passing the init request via rpc. After
|
// Init initializes a harness node by passing the init request via rpc. After
|
||||||
// the request is submitted, this method will block until an
|
// the request is submitted, this method will block until a
|
||||||
// macaroon-authenticated rpc connection can be established to the harness node.
|
// macaroon-authenticated RPC connection can be established to the harness node.
|
||||||
// Once established, the new connection is used to initialize the
|
// Once established, the new connection is used to initialize the
|
||||||
// LightningClient and subscribes the HarnessNode to topology changes.
|
// LightningClient and subscribes the HarnessNode to topology changes.
|
||||||
func (hn *HarnessNode) Init(ctx context.Context,
|
func (hn *HarnessNode) Init(ctx context.Context,
|
||||||
initReq *lnrpc.InitWalletRequest) error {
|
initReq *lnrpc.InitWalletRequest) (*lnrpc.InitWalletResponse, error) {
|
||||||
|
|
||||||
ctxt, _ := context.WithTimeout(ctx, DefaultTimeout)
|
ctxt, cancel := context.WithTimeout(ctx, DefaultTimeout)
|
||||||
_, err := hn.InitWallet(ctxt, initReq)
|
defer cancel()
|
||||||
|
response, err := hn.InitWallet(ctxt, initReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the wallet to finish unlocking, such that we can connect to
|
// Wait for the wallet to finish unlocking, such that we can connect to
|
||||||
// it via a macaroon-authenticated rpc connection.
|
// it via a macaroon-authenticated rpc connection.
|
||||||
return hn.initClientWhenReady()
|
var conn *grpc.ClientConn
|
||||||
|
if err = wait.Predicate(func() bool {
|
||||||
|
// If the node has been initialized stateless, we need to pass
|
||||||
|
// the macaroon to the client.
|
||||||
|
if initReq.StatelessInit {
|
||||||
|
adminMac := &macaroon.Macaroon{}
|
||||||
|
err := adminMac.UnmarshalBinary(response.AdminMacaroon)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
conn, err = hn.ConnectRPCWithMacaroon(adminMac)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal initialization, we expect a macaroon to be in the
|
||||||
|
// file system.
|
||||||
|
conn, err = hn.ConnectRPC(true)
|
||||||
|
return err == nil
|
||||||
|
}, DefaultTimeout); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, hn.initLightningClient(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitChangePassword initializes a harness node by passing the change password
|
||||||
|
// request via RPC. After the request is submitted, this method will block until
|
||||||
|
// a macaroon-authenticated RPC connection can be established to the harness
|
||||||
|
// node. Once established, the new connection is used to initialize the
|
||||||
|
// LightningClient and subscribes the HarnessNode to topology changes.
|
||||||
|
func (hn *HarnessNode) InitChangePassword(ctx context.Context,
|
||||||
|
chngPwReq *lnrpc.ChangePasswordRequest) (*lnrpc.ChangePasswordResponse,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
ctxt, cancel := context.WithTimeout(ctx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
response, err := hn.ChangePassword(ctxt, chngPwReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the wallet to finish unlocking, such that we can connect to
|
||||||
|
// it via a macaroon-authenticated rpc connection.
|
||||||
|
var conn *grpc.ClientConn
|
||||||
|
if err = wait.Predicate(func() bool {
|
||||||
|
// If the node has been initialized stateless, we need to pass
|
||||||
|
// the macaroon to the client.
|
||||||
|
if chngPwReq.StatelessInit {
|
||||||
|
adminMac := &macaroon.Macaroon{}
|
||||||
|
err := adminMac.UnmarshalBinary(response.AdminMacaroon)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
conn, err = hn.ConnectRPCWithMacaroon(adminMac)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal initialization, we expect a macaroon to be in the
|
||||||
|
// file system.
|
||||||
|
conn, err = hn.ConnectRPC(true)
|
||||||
|
return err == nil
|
||||||
|
}, DefaultTimeout); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, hn.initLightningClient(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock attempts to unlock the wallet of the target HarnessNode. This method
|
// Unlock attempts to unlock the wallet of the target HarnessNode. This method
|
||||||
|
Loading…
Reference in New Issue
Block a user