From 511e817624490916e310fbeeab21a11404e370f7 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Thu, 18 Jun 2020 21:42:59 +0200 Subject: [PATCH 1/7] lncfg+channeldb: add config to be able to run lnd on embedded etcd --- channeldb/kvdb/config.go | 2 ++ lncfg/db.go | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/channeldb/kvdb/config.go b/channeldb/kvdb/config.go index ccd1c1d75..9ea50adc0 100644 --- a/channeldb/kvdb/config.go +++ b/channeldb/kvdb/config.go @@ -30,6 +30,8 @@ type BoltConfig struct { // EtcdConfig holds etcd configuration. type EtcdConfig struct { + Embedded bool `long:"embedded" description:"Use embedded etcd instance instead of the external one."` + Host string `long:"host" description:"Etcd database host."` User string `long:"user" description:"Etcd database user."` diff --git a/lncfg/db.go b/lncfg/db.go index 2f0eade59..63b58c469 100644 --- a/lncfg/db.go +++ b/lncfg/db.go @@ -38,7 +38,7 @@ func (db *DB) Validate() error { case BoltBackend: case EtcdBackend: - if db.Etcd.Host == "" { + if !db.Etcd.Embedded && db.Etcd.Host == "" { return fmt.Errorf("etcd host must be set") } @@ -76,8 +76,12 @@ func (db *DB) GetBackends(ctx context.Context, dbPath string, ) if db.Backend == EtcdBackend { - // Prefix will separate key/values in the db. - remoteDB, err = kvdb.GetEtcdBackend(ctx, networkName, db.Etcd) + if db.Etcd.Embedded { + remoteDB, _, err = kvdb.GetEtcdTestBackend(dbPath, dbName) + } else { + // Prefix will separate key/values in the db. + remoteDB, err = kvdb.GetEtcdBackend(ctx, networkName, db.Etcd) + } if err != nil { return nil, err } From 98342433abea510bbef6ad4168811165665c8958 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Mon, 22 Jun 2020 19:36:12 +0200 Subject: [PATCH 2/7] itest: add etcd flag to control if new lnd nodes should run on etcd --- lntest/harness.go | 10 ++++++++-- lntest/itest/lnd_test.go | 7 ++++++- lntest/node.go | 7 +++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lntest/harness.go b/lntest/harness.go index 91aa88b0f..417169dd3 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -60,6 +60,10 @@ type NetworkHarness struct { Alice *HarnessNode Bob *HarnessNode + // useEtcd is set to true if new nodes are to be created with an + // embedded etcd backend instead of just bbolt. + useEtcd bool + // Channel for transmitting stderr output from failed lightning node // to main process. lndErrorChan chan error @@ -77,8 +81,8 @@ type NetworkHarness struct { // TODO(roasbeef): add option to use golang's build library to a binary of the // current repo. This will save developers from having to manually `go install` // within the repo each time before changes -func NewNetworkHarness(r *rpctest.Harness, b BackendConfig, lndBinary string) ( - *NetworkHarness, error) { +func NewNetworkHarness(r *rpctest.Harness, b BackendConfig, lndBinary string, + useEtcd bool) (*NetworkHarness, error) { feeService := startFeeService() @@ -92,6 +96,7 @@ func NewNetworkHarness(r *rpctest.Harness, b BackendConfig, lndBinary string) ( feeService: feeService, quit: make(chan struct{}), lndBinary: lndBinary, + useEtcd: useEtcd, } return &n, nil } @@ -376,6 +381,7 @@ func (n *NetworkHarness) newNode(name string, extraArgs []string, hasSeed bool, NetParams: n.netParams, ExtraArgs: extraArgs, FeeURL: n.feeService.url, + Etcd: n.useEtcd, }) if err != nil { return nil, err diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 93afb8bee..1f1360bb5 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -81,6 +81,9 @@ var ( "runtranche", defaultRunTranche, "run the tranche of the "+ "split test cases with the given (0-based) index", ) + + // useEtcd test LND nodes use (embedded) etcd as remote db. + useEtcd = flag.Bool("etcd", false, "Use etcd backend for lnd.") ) // getTestCaseSplitTranche returns the sub slice of the test cases that should @@ -14250,7 +14253,9 @@ func TestLightningNetworkDaemon(t *testing.T) { // Now we can set up our test harness (LND instance), with the chain // backend we just created. binary := ht.getLndBinary() - lndHarness, err = lntest.NewNetworkHarness(miner, chainBackend, binary) + lndHarness, err = lntest.NewNetworkHarness( + miner, chainBackend, binary, *useEtcd, + ) if err != nil { ht.Fatalf("unable to create lightning network harness: %v", err) } diff --git a/lntest/node.go b/lntest/node.go index a437adb45..ae3ebfc0b 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -183,6 +183,8 @@ type NodeConfig struct { AcceptKeySend bool FeeURL string + + Etcd bool } func (cfg NodeConfig) P2PAddr() string { @@ -261,6 +263,11 @@ func (cfg NodeConfig) genArgs() []string { args = append(args, "--accept-keysend") } + if cfg.Etcd { + args = append(args, "--db.backend=etcd") + args = append(args, "--db.etcd.embedded") + } + if cfg.FeeURL != "" { args = append(args, "--feeurl="+cfg.FeeURL) } From 9138efc2c3b94b97a3e94365960789dc4f985660 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Mon, 22 Jun 2020 19:36:48 +0200 Subject: [PATCH 3/7] build: extend itest with etcd=1 param which will run itests on etcd --- make/testing_flags.mk | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/make/testing_flags.mk b/make/testing_flags.mk index 33bcaa3e6..a4de6e5ad 100644 --- a/make/testing_flags.mk +++ b/make/testing_flags.mk @@ -2,6 +2,7 @@ DEV_TAGS = dev RPC_TAGS = autopilotrpc chainrpc invoicesrpc routerrpc signrpc verrpc walletrpc watchtowerrpc wtclientrpc LOG_TAGS = TEST_FLAGS = +ITEST_FLAGS = COVER_PKG = $$(go list -deps ./... | grep '$(PKG)' | grep -v lnrpc) NUM_ITEST_TRANCHES = 6 ITEST_PARALLELISM = $(NUM_ITEST_TRANCHES) @@ -41,6 +42,12 @@ ifneq ($(icase),) TEST_FLAGS += -test.run="TestLightningNetworkDaemon/.*-of-.*/.*/$(icase)" endif +# Run itests with etcd backend. +ifeq ($(etcd),1) +ITEST_FLAGS += -etcd +DEV_TAGS += kvdb_etcd +endif + ifneq ($(tags),) DEV_TAGS += ${tags} endif @@ -89,4 +96,4 @@ endif # Construct the integration test command with the added build flags. ITEST_TAGS := $(DEV_TAGS) $(RPC_TAGS) rpctest $(backend) -ITEST := rm lntest/itest/*.log; date; $(GOTEST) -v ./lntest/itest -tags="$(ITEST_TAGS)" $(TEST_FLAGS) -logoutput -goroutinedump +ITEST := rm -f lntest/itest/*.log; date; $(GOTEST) -v ./lntest/itest -tags="$(ITEST_TAGS)" $(TEST_FLAGS) $(ITEST_FLAGS) -logoutput -goroutinedump From 369ae5e372eceaef2bfc52995e8e4b89f62662a7 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Mon, 26 Oct 2020 16:36:09 +0100 Subject: [PATCH 4/7] itest: move all test db files when using both etcd and bbolt In some tests we moved channeld.db to a temp location in order to "time travel". This commit extends the existing semantics by moving all files, including embedded etcd db too besides the channeld.db file. --- lntest/harness.go | 45 ++++++++++++++++++++++++++++++++++++++++ lntest/itest/lnd_test.go | 26 +++++++++-------------- lntest/node.go | 13 ++++++++++-- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/lntest/harness.go b/lntest/harness.go index 417169dd3..2b8761163 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net/http" "os" + "path/filepath" "strings" "sync" "time" @@ -1403,3 +1404,47 @@ func CopyFile(dest, src string) error { return d.Close() } + +// FileExists returns true if the file at path exists. +func FileExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + + return true +} + +// CopyAll copies all files and directories from srcDir to dstDir recursively. +// Note that this function does not support links. +func CopyAll(dstDir, srcDir string) error { + 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 + } + + err = CopyAll(dstPath, srcPath) + if err != nil { + return err + } + } else if err := CopyFile(dstPath, srcPath); err != nil { + return err + } + } + + return nil +} diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 1f1360bb5..c522798da 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -12,7 +12,6 @@ import ( "io/ioutil" "math" "os" - "path/filepath" "reflect" "strings" "sync" @@ -8241,13 +8240,12 @@ func testRevokedCloseRetribution(net *lntest.NetworkHarness, t *harnessTest) { if err != nil { t.Fatalf("unable to create temp db folder: %v", err) } - bobTempDbFile := filepath.Join(bobTempDbPath, "channel.db") defer os.Remove(bobTempDbPath) // With the temporary file created, copy Bob's current state into the // temporary file we created above. Later after more updates, we'll // restore this state. - if err := lntest.CopyFile(bobTempDbFile, net.Bob.DBPath()); err != nil { + if err := lntest.CopyAll(bobTempDbPath, net.Bob.DBDir()); err != nil { t.Fatalf("unable to copy database files: %v", err) } @@ -8273,7 +8271,7 @@ func testRevokedCloseRetribution(net *lntest.NetworkHarness, t *harnessTest) { // state. With this, we essentially force Bob to travel back in time // within the channel's history. if err = net.RestartNode(net.Bob, func() error { - return os.Rename(bobTempDbFile, net.Bob.DBPath()) + return lntest.CopyAll(net.Bob.DBDir(), bobTempDbPath) }); err != nil { t.Fatalf("unable to restart node: %v", err) } @@ -8496,13 +8494,12 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness if err != nil { t.Fatalf("unable to create temp db folder: %v", err) } - carolTempDbFile := filepath.Join(carolTempDbPath, "channel.db") defer os.Remove(carolTempDbPath) // With the temporary file created, copy Carol's current state into the // temporary file we created above. Later after more updates, we'll // restore this state. - if err := lntest.CopyFile(carolTempDbFile, carol.DBPath()); err != nil { + if err := lntest.CopyAll(carolTempDbPath, carol.DBDir()); err != nil { t.Fatalf("unable to copy database files: %v", err) } @@ -8527,7 +8524,7 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness // state. With this, we essentially force Carol to travel back in time // within the channel's history. if err = net.RestartNode(carol, func() error { - return os.Rename(carolTempDbFile, carol.DBPath()) + return lntest.CopyAll(carol.DBDir(), carolTempDbPath) }); err != nil { t.Fatalf("unable to restart node: %v", err) } @@ -8820,13 +8817,12 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, if err != nil { t.Fatalf("unable to create temp db folder: %v", err) } - carolTempDbFile := filepath.Join(carolTempDbPath, "channel.db") defer os.Remove(carolTempDbPath) // With the temporary file created, copy Carol's current state into the // temporary file we created above. Later after more updates, we'll // restore this state. - if err := lntest.CopyFile(carolTempDbFile, carol.DBPath()); err != nil { + if err := lntest.CopyAll(carolTempDbPath, carol.DBDir()); err != nil { t.Fatalf("unable to copy database files: %v", err) } @@ -8860,7 +8856,7 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // state. With this, we essentially force Carol to travel back in time // within the channel's history. if err = net.RestartNode(carol, func() error { - return os.Rename(carolTempDbFile, carol.DBPath()) + return lntest.CopyAll(carol.DBDir(), carolTempDbPath) }); err != nil { t.Fatalf("unable to restart node: %v", err) } @@ -9222,13 +9218,12 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness, if err != nil { t.Fatalf("unable to create temp db folder: %v", err) } - carolTempDbFile := filepath.Join(carolTempDbPath, "channel.db") defer os.Remove(carolTempDbPath) // With the temporary file created, copy Carol's current state into the // temporary file we created above. Later after more updates, we'll // restore this state. - if err := lntest.CopyFile(carolTempDbFile, carol.DBPath()); err != nil { + if err := lntest.CopyAll(carolTempDbPath, carol.DBDir()); err != nil { t.Fatalf("unable to copy database files: %v", err) } @@ -9285,7 +9280,7 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness, // state. With this, we essentially force Carol to travel back in time // within the channel's history. if err = net.RestartNode(carol, func() error { - return os.Rename(carolTempDbFile, carol.DBPath()) + return lntest.CopyAll(carol.DBDir(), carolTempDbPath) }); err != nil { t.Fatalf("unable to restart node: %v", err) } @@ -9769,13 +9764,12 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { if err != nil { t.Fatalf("unable to create temp db folder: %v", err) } - tempDbFile := filepath.Join(tempDbPath, "channel.db") defer os.Remove(tempDbPath) // With the temporary file created, copy the current state into // the temporary file we created above. Later after more // updates, we'll restore this state. - if err := lntest.CopyFile(tempDbFile, node.DBPath()); err != nil { + if err := lntest.CopyAll(tempDbPath, node.DBDir()); err != nil { t.Fatalf("unable to copy database files: %v", err) } @@ -9802,7 +9796,7 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { // force the node to travel back in time within the channel's // history. if err = net.RestartNode(node, func() error { - return os.Rename(tempDbFile, node.DBPath()) + return lntest.CopyAll(node.DBDir(), tempDbPath) }); err != nil { t.Fatalf("unable to restart node: %v", err) } diff --git a/lntest/node.go b/lntest/node.go index ae3ebfc0b..4fcbc78ba 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -199,9 +199,13 @@ func (cfg NodeConfig) RESTAddr() string { return net.JoinHostPort("127.0.0.1", strconv.Itoa(cfg.RESTPort)) } +// DBDir returns the holding directory path of the graph database. +func (cfg NodeConfig) DBDir() string { + return filepath.Join(cfg.DataDir, "graph", cfg.NetParams.Name) +} + func (cfg NodeConfig) DBPath() string { - return filepath.Join(cfg.DataDir, "graph", - fmt.Sprintf("%v/channel.db", cfg.NetParams.Name)) + return filepath.Join(cfg.DBDir(), "channel.db") } func (cfg NodeConfig) ChanBackupPath() string { @@ -440,6 +444,11 @@ func (hn *HarnessNode) DBPath() string { return hn.Cfg.DBPath() } +// DBDir returns the path for the directory holding channeldb file(s). +func (hn *HarnessNode) DBDir() string { + return hn.Cfg.DBDir() +} + // Name returns the name of this node set during initialization. func (hn *HarnessNode) Name() string { return hn.Cfg.Name From efedb5547c55061445e3a833d8978a2a13071647 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Mon, 26 Oct 2020 19:28:04 +0100 Subject: [PATCH 5/7] itest: timeouts for itets with etcd --- lntest/timeouts.go | 2 +- lntest/timeouts_etcd.go | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 lntest/timeouts_etcd.go diff --git a/lntest/timeouts.go b/lntest/timeouts.go index 305bf8727..f6d6a9615 100644 --- a/lntest/timeouts.go +++ b/lntest/timeouts.go @@ -1,4 +1,4 @@ -// +build !darwin +// +build !darwin, !kvdb_etcd package lntest diff --git a/lntest/timeouts_etcd.go b/lntest/timeouts_etcd.go new file mode 100644 index 000000000..9d6062f93 --- /dev/null +++ b/lntest/timeouts_etcd.go @@ -0,0 +1,27 @@ +// +build !darwin, kvdb_etcd + +package lntest + +import "time" + +const ( + // MinerMempoolTimeout is the max time we will wait for a transaction + // to propagate to the mining node's mempool. + MinerMempoolTimeout = time.Minute + + // ChannelOpenTimeout is the max time we will wait before a channel to + // be considered opened. + ChannelOpenTimeout = time.Second * 30 + + // ChannelCloseTimeout is the max time we will wait before a channel is + // considered closed. + ChannelCloseTimeout = time.Second * 120 + + // DefaultTimeout is a timeout that will be used for various wait + // scenarios where no custom timeout value is defined. + DefaultTimeout = time.Second * 30 + + // AsyncBenchmarkTimeout is the timeout used when running the async + // payments benchmark. + AsyncBenchmarkTimeout = 2 * time.Minute +) From 5f3b8006304f023ad5966bbcf52d9d1e651f6764 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Wed, 18 Nov 2020 15:26:27 +0100 Subject: [PATCH 6/7] lint: fix error reported by the linter --- lntest/itest/lnd_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index c522798da..05c00d85a 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -11604,10 +11604,7 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { var predErr error err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(nodes, numPayments) - if predErr != nil { - return false - } - return true + return predErr == nil }, time.Second*15) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) From 5e19c41cb8280ab092911a3c104e4980334f68dc Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Wed, 18 Nov 2020 15:34:38 +0100 Subject: [PATCH 7/7] build: extend sample-lnd-conf with db.etcd.embedded --- sample-lnd.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sample-lnd.conf b/sample-lnd.conf index 01bc5eaad..c3a23160f 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -952,6 +952,10 @@ litecoin.node=ltcd ; Whether to collect etcd commit stats. ; db.etcd.collect_stats=true +; If set LND will use an embedded etcd instance instead of the external one. +; Useful for testing. +; db.etcd.embedded=false + [bolt] ; If true, prevents the database from syncing its freelist to disk. ; db.bolt.nofreelistsync=1