Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable and test SPV mode for remote wallets #95

Merged
merged 2 commits into from
Jul 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
fail-fast: false
matrix:
go: [1.14]
testsuite: ["unit-race", "itest-only", "itest-only walletimpl=remotewallet", "itest-only walletimpl=embedded_dcrw"]
testsuite: ["unit-race", "itest-only", "itest-only walletimpl=remotewallet", "itest-only walletimpl=embedded_dcrw", "itest-only walletimpl=remotewallet backend=spv"]
steps:
- name: Set up Go
uses: actions/setup-go@v1
Expand Down
7 changes: 7 additions & 0 deletions chainregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,13 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
srvrLog.Info("Using underlying wallet for chain operations")
}

case "spv":
// TODO: support SPV mode with an embedded wallet.
if conn == nil {
return nil, fmt.Errorf("SPV mode is not supported when " +
"using an embedded wallet. Use --node=dcrd or " +
"--node=dcrw")
}
default:
return nil, fmt.Errorf("unknown node type: %s",
cfg.Node)
Expand Down
6 changes: 4 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ type config struct {
BackupFilePath string `long:"backupfilepath" description:"The target location of the channel backup file"`

ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."`
Node string `long:"node" description:"The blockchain interface to use." choice:"dcrd" choice:"dcrw"`
Node string `long:"node" description:"The blockchain interface to use." choice:"dcrd" choice:"dcrw" choice:"spv"`
TestNet3 bool `long:"testnet" description:"Use the test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"`
RegTest bool `long:"regtest" description:"Use the regression test network"`
Expand Down Expand Up @@ -676,7 +676,9 @@ func loadConfig() (*config, error) {
"credentials for dcrd: %v", err)
return nil, err
}

case "spv":
// In SPV mode we use the underlying wallet for chain
// operations.
default:
str := "%s: only dcrd mode supported for Decred at " +
"this time"
Expand Down
14 changes: 14 additions & 0 deletions lntest/itest/lnd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2400,6 +2400,13 @@ func assertMinerBlockHeightDelta(t *harnessTest,
func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) {
var ctxb = context.Background()

// Currently disabled due to
// https://github.com/decred/dcrwallet/issues/1710. Re-assess after
// that is fixed.
if net.BackendCfg.Name() == "spv" {
t.Skipf("Skipping for SPV for the moment")
}

// Set up a new miner that we can use to cause a reorg.
args := []string{"--rejectnonstd", "--txindex"}
tempMiner, err := testutils.NewSetupRPCTest(
Expand Down Expand Up @@ -8714,6 +8721,13 @@ func testRevokedCloseRetributionRemoteHodlSecondLevel(net *lntest.NetworkHarness
t *harnessTest) {
ctxb := context.Background()

// Currently disabled in SPV due to
// https://github.com/decred/dcrlnd/issues/96. Re-assess after that is
// fixed.
if net.BackendCfg.Name() == "spv" {
t.Skipf("Skipping for SPV for the moment")
}

const (
initialBalance = int64(dcrutil.AtomsPerCoin)
chanAmt = defaultChanAmt
Expand Down
4 changes: 2 additions & 2 deletions lntest/itest/onchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import (
func testCPFP(net *lntest.NetworkHarness, t *harnessTest) {
// Skip this test for neutrino, as it's not aware of mempool
// transactions.
if net.BackendCfg.Name() == "neutrino" {
t.Skipf("skipping reorg test for neutrino backend")
if net.BackendCfg.Name() == "spv" {
t.Skipf("skipping cpfp test for spv backend")
}

// We'll start the test by sending Alice some coins, which she'll use to
Expand Down
2 changes: 1 addition & 1 deletion lntest/remotewallet.go → lntest/remotewallet-dcrw.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build remotewallet
// +build remotewallet,!spv

package lntest

Expand Down
11 changes: 11 additions & 0 deletions lntest/remotewallet-spv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// +build remotewallet,spv

package lntest

func useRemoteWallet() bool {
return true
}

func useDcrwNode() bool {
return false
}
171 changes: 171 additions & 0 deletions lntest/spv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// +build spv

package lntest

import (
"context"
"fmt"
"os"
"testing"

pb "decred.org/dcrwallet/rpc/walletrpc"
"github.com/decred/dcrd/chaincfg/v2"
"github.com/decred/dcrd/rpctest"
"github.com/decred/dcrlnd/internal/testutils"
)

// logDir is the name of the temporary log directory.
const logDir = "./.backendlogs"

// SpvBackendConfig is an implementation of the BackendConfig interface
// backed by a btcd node.
type SpvBackendConfig struct {
// connectAddr is the address that SPV clients may use to connect to
// this node via the p2p interface.
connectAddr string

// harness is this backend's node.
harness *rpctest.Harness

// miner is the backing miner used during tests.
miner *rpctest.Harness
}

// GenArgs returns the arguments needed to be passed to LND at startup for
// using this node as a chain backend.
func (b SpvBackendConfig) GenArgs() []string {
return []string{
"--node=spv",
}
}

func (b SpvBackendConfig) StartWalletSync(loader pb.WalletLoaderServiceClient, password []byte) error {
req := &pb.SpvSyncRequest{
SpvConnect: []string{b.connectAddr},
DiscoverAccounts: true,
PrivatePassphrase: password,
}

stream, err := loader.SpvSync(context.Background(), req)
if err != nil {
return err
}

syncDone := make(chan error)
go func() {
for {
resp, err := stream.Recv()
if err != nil {
syncDone <- err
return
}
if resp.Synced {
close(syncDone)
break
}
}

// After sync is complete, just drain the notifications until
// the connection is closed.
for {
_, err := stream.Recv()
if err != nil {
return
}
}
}()

return <-syncDone
}

// ConnectMiner connects the backend to the underlying miner.
func (b SpvBackendConfig) ConnectMiner() error {
return rpctest.ConnectNode(b.harness, b.miner)
}

// DisconnectMiner disconnects the backend to the underlying miner.
func (b SpvBackendConfig) DisconnectMiner() error {
return rpctest.RemoveNode(context.Background(), b.harness, b.miner)
}

// Name returns the name of the backend type.
func (b SpvBackendConfig) Name() string {
return "spv"
}

func unsafeFindP2PAddr(miner, chainBackend *rpctest.Harness) (string, error) {
// This assumes the miner doesn't have any connections yet.
err := rpctest.ConnectNode(miner, chainBackend)
if err != nil {
return "", err
}

peers, err := miner.Node.GetPeerInfo(context.Background())
if err != nil {
return "", err
}

err = rpctest.RemoveNode(context.Background(), miner, chainBackend)
if err != nil {
return "", err
}

return peers[0].Addr, nil
}

// NewBackend starts a new rpctest.Harness and returns a SpvBackendConfig for
// that node.
func NewBackend(t *testing.T, miner *rpctest.Harness) (*SpvBackendConfig, func(), error) {
args := []string{
// rejectnonstd cannot be used in decred due to votes in simnet
// using a non-standard signature script.
//
// "--rejectnonstd",
"--txindex",
"--debuglevel=debug",
"--logdir=" + logDir,
}
netParams := chaincfg.SimNetParams()
chainBackend, err := testutils.NewSetupRPCTest(
t, 5, netParams, nil, args, false, 0,
)
if err != nil {
return nil, nil, fmt.Errorf("unable to create dcrd node: %v", err)
}

// FIXME: This is really brittle and needs to be fixed with a specific
// call in the upstream dcrd/rpctest package.
//
// Determine the p2p address by having the miner connect to this node
// and figuring out the address from the miner.
connectAddr, err := unsafeFindP2PAddr(miner, chainBackend)
if err != nil {
return nil, nil, err
}

bd := &SpvBackendConfig{
connectAddr: connectAddr,
harness: chainBackend,
miner: miner,
}

// Connect this newly created node to the miner.
rpctest.ConnectNode(chainBackend, miner)

cleanUp := func() {
chainBackend.TearDown()

// After shutting down the chain backend, we'll make a copy of
// the log file before deleting the temporary log dir.
logFile := logDir + "/" + netParams.Name + "/dcrd.log"
err := CopyFile("./output_dcrd_chainbackend.log", logFile)
if err != nil {
fmt.Printf("unable to copy file: %v\n", err)
}
if err = os.RemoveAll(logDir); err != nil {
fmt.Printf("Cannot remove dir %s: %v\n", logDir, err)
}
}

return bd, cleanUp, nil
}