package itest import ( "context" "flag" "fmt" "os" "strings" "testing" "time" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/lightningnetwork/lnd/lntest" "github.com/stretchr/testify/require" ) const ( // defaultSplitTranches is the default number of tranches we split the // test cases into. defaultSplitTranches uint = 1 // defaultRunTranche is the default index of the test cases tranche that // we run. defaultRunTranche uint = 0 ) var ( // testCasesSplitParts is the number of tranches the test cases should // be split into. By default this is set to 1, so no splitting happens. // If this value is increased, then the -runtranche flag must be // specified as well to indicate which part should be run in the current // invocation. testCasesSplitTranches = flag.Uint( "splittranches", defaultSplitTranches, "split the test cases "+ "in this many tranches and run the tranche at "+ "0-based index specified by the -runtranche flag", ) // testCasesRunTranche is the 0-based index of the split test cases // tranche to run in the current invocation. testCasesRunTranche = flag.Uint( "runtranche", defaultRunTranche, "run the tranche of the "+ "split test cases with the given (0-based) index", ) // dbBackendFlag specifies the backend to use dbBackendFlag = flag.String("dbbackend", "bbolt", "Database backend (bbolt, etcd)") ) // getTestCaseSplitTranche returns the sub slice of the test cases that should // be run as the current split tranche as well as the index and slice offset of // the tranche. func getTestCaseSplitTranche() ([]*testCase, uint, uint) { numTranches := defaultSplitTranches if testCasesSplitTranches != nil { numTranches = *testCasesSplitTranches } runTranche := defaultRunTranche if testCasesRunTranche != nil { runTranche = *testCasesRunTranche } // There's a special flake-hunt mode where we run the same test multiple // times in parallel. In that case the tranche index is equal to the // thread ID, but we need to actually run all tests for the regex // selection to work. threadID := runTranche if numTranches == 1 { runTranche = 0 } numCases := uint(len(allTestCases)) testsPerTranche := numCases / numTranches trancheOffset := runTranche * testsPerTranche trancheEnd := trancheOffset + testsPerTranche if trancheEnd > numCases || runTranche == numTranches-1 { trancheEnd = numCases } return allTestCases[trancheOffset:trancheEnd], threadID, trancheOffset } // TestLightningNetworkDaemon performs a series of integration tests amongst a // programmatically driven network of lnd nodes. func TestLightningNetworkDaemon(t *testing.T) { // If no tests are registered, then we can exit early. if len(allTestCases) == 0 { t.Skip("integration tests not selected with flag 'rpctest'") } // Parse testing flags that influence our test execution. logDir := lntest.GetLogDir() require.NoError(t, os.MkdirAll(logDir, 0700)) testCases, trancheIndex, trancheOffset := getTestCaseSplitTranche() lntest.ApplyPortOffset(uint32(trancheIndex) * 1000) // Before we start any node, we need to make sure that any btcd node // that is started through the RPC harness uses a unique port as well to // avoid any port collisions. rpctest.ListenAddressGenerator = lntest.GenerateBtcdListenerAddresses // Declare the network harness here to gain access to its // 'OnTxAccepted' call back. var lndHarness *lntest.NetworkHarness // Create an instance of the btcd's rpctest.Harness that will act as // the miner for all tests. This will be used to fund the wallets of // the nodes within the test network and to drive blockchain related // events within the network. Revert the default setting of accepting // non-standard transactions on simnet to reject them. Transactions on // the lightning network should always be standard to get better // guarantees of getting included in to blocks. // // We will also connect it to our chain backend. minerLogDir := fmt.Sprintf("%s/.minerlogs", logDir) miner, minerCleanUp, err := lntest.NewMiner( minerLogDir, "output_btcd_miner.log", harnessNetParams, &rpcclient.NotificationHandlers{}, lntest.GetBtcdBinary(), ) require.NoError(t, err, "failed to create new miner") defer func() { require.NoError(t, minerCleanUp(), "failed to clean up miner") }() // Start a chain backend. chainBackend, cleanUp, err := lntest.NewBackend( miner.P2PAddress(), harnessNetParams, ) require.NoError(t, err, "new backend") defer func() { require.NoError(t, cleanUp(), "cleanup") }() // Before we start anything, we want to overwrite some of the connection // settings to make the tests more robust. We might need to restart the // miner while there are already blocks present, which will take a bit // longer than the 1 second the default settings amount to. Doubling // both values will give us retries up to 4 seconds. miner.MaxConnRetries = rpctest.DefaultMaxConnectionRetries * 2 miner.ConnectionRetryTimeout = rpctest.DefaultConnectionRetryTimeout * 2 // Set up miner and connect chain backend to it. require.NoError(t, miner.SetUp(true, 50)) require.NoError(t, miner.Client.NotifyNewTransactions(false)) require.NoError(t, chainBackend.ConnectMiner(), "connect miner") // Parse database backend var dbBackend lntest.DatabaseBackend switch *dbBackendFlag { case "bbolt": dbBackend = lntest.BackendBbolt case "etcd": dbBackend = lntest.BackendEtcd default: require.Fail(t, "unknown db backend") } // Now we can set up our test harness (LND instance), with the chain // backend we just created. ht := newHarnessTest(t, nil) binary := ht.getLndBinary() lndHarness, err = lntest.NewNetworkHarness( miner, chainBackend, binary, dbBackend, ) if err != nil { ht.Fatalf("unable to create lightning network harness: %v", err) } defer lndHarness.Stop() // Spawn a new goroutine to watch for any fatal errors that any of the // running lnd processes encounter. If an error occurs, then the test // case should naturally as a result and we log the server error here to // help debug. go func() { for { select { case err, more := <-lndHarness.ProcessErrors(): if !more { return } ht.Logf("lnd finished with error (stderr):\n%v", err) } } }() // Next mine enough blocks in order for segwit and the CSV package // soft-fork to activate on SimNet. numBlocks := harnessNetParams.MinerConfirmationWindow * 2 if _, err := miner.Client.Generate(numBlocks); err != nil { ht.Fatalf("unable to generate blocks: %v", err) } // With the btcd harness created, we can now complete the // initialization of the network. args - list of lnd arguments, // example: "--debuglevel=debug" // TODO(roasbeef): create master balanced channel with all the monies? aliceBobArgs := []string{ "--default-remote-max-htlcs=483", } // Run the subset of the test cases selected in this tranche. for idx, testCase := range testCases { testCase := testCase name := fmt.Sprintf("%02d-of-%d/%s/%s", trancheOffset+uint(idx)+1, len(allTestCases), chainBackend.Name(), testCase.name) success := t.Run(name, func(t1 *testing.T) { cleanTestCaseName := strings.ReplaceAll( testCase.name, " ", "_", ) err = lndHarness.SetUp( t1, cleanTestCaseName, aliceBobArgs, ) require.NoError(t1, err, "unable to set up test lightning network", ) defer func() { require.NoError(t1, lndHarness.TearDown()) }() lndHarness.EnsureConnected( context.Background(), t1, lndHarness.Alice, lndHarness.Bob, ) logLine := fmt.Sprintf( "STARTING ============ %v ============\n", testCase.name, ) AddToNodeLog(t, lndHarness.Alice, logLine) AddToNodeLog(t, lndHarness.Bob, logLine) // Start every test with the default static fee estimate. lndHarness.SetFeeEstimate(12500) // Create a separate harness test for the testcase to // avoid overwriting the external harness test that is // tied to the parent test. ht := newHarnessTest(t1, lndHarness) ht.RunTestCase(testCase) }) // Stop at the first failure. Mimic behavior of original test // framework. if !success { // Log failure time to help relate the lnd logs to the // failure. t.Logf("Failure time: %v", time.Now().Format( "2006-01-02 15:04:05.000", )) break } } }