// TestLightningNetworkDaemon performs a series of integration tests amongst a // programmatically driven network of lnd nodes. func TestLightningNetworkDaemon(t *testing.T) { ht := newHarnessTest(t) // First create the network harness to gain access to its // 'OnTxAccepted' call back. lndHarness, err := newNetworkHarness() if err != nil { ht.Fatalf("unable to create lightning network harness: %v", err) } defer lndHarness.TearDownAll() handlers := &btcrpcclient.NotificationHandlers{ OnTxAccepted: lndHarness.OnTxAccepted, } // First create an instance of the btcd's rpctest.Harness. This will be // used to fund the wallets of the nodes within the test network and to // drive blockchain related events within the network. btcdHarness, err := rpctest.New(harnessNetParams, handlers, nil) if err != nil { ht.Fatalf("unable to create mining node: %v", err) } defer btcdHarness.TearDown() if err := btcdHarness.SetUp(true, 50); err != nil { ht.Fatalf("unable to set up mining node: %v", err) } if err := btcdHarness.Node.NotifyNewTransactions(false); err != nil { ht.Fatalf("unable to request transaction notifications: %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? if err := lndHarness.InitializeSeedNodes(btcdHarness, nil); err != nil { ht.Fatalf("unable to initialize seed nodes: %v", err) } if err = lndHarness.SetUp(); err != nil { ht.Fatalf("unable to set up test lightning network: %v", err) } // 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 // fails immediately with a fatal error, as far as fatal is happening // inside goroutine main goroutine would not be finished at the same // time as we receive fatal error from lnd process. go func() { err := <-lndHarness.ProcessErrors() ht.Fatalf("lnd finished with error (stderr): "+ "\n%v", err) }() t.Logf("Running %v integration tests", len(testsCases)) for _, testCase := range testsCases { ht.RunTestCase(testCase, lndHarness) } }
// TestInterfaces tests all registered interfaces with a unified set of tests // which excersie each of the required methods found within the ChainNotifier // interface. // // NOTE: In the future, when additional implementations of the ChainNotifier // interface have been implemented, in order to ensure the new concrete // implementation is automatically tested, two steps must be undertaken. First, // one needs add a "non-captured" (_) import from the new sub-package. This // import should trigger an init() method within the package which registeres // the interface. Second, an additional case in the switch within the main loop // below needs to be added which properly initializes the interface. func TestInterfaces(t *testing.T) { // Initialize the harness around a btcd node which will serve as our // dedicated miner to generate blocks, cause re-orgs, etc. We'll set up // this node with a chain length of 125, so we have plentyyy of BTC to // play around with. miner, err := rpctest.New(netParams, nil, nil) if err != nil { t.Fatalf("unable to create mining node: %v", err) } defer miner.TearDown() if err := miner.SetUp(true, 25); err != nil { t.Fatalf("unable to set up mining node: %v", err) } rpcConfig := miner.RPCConfig() log.Printf("Running %v ChainNotifier interface tests\n", len(ntfnTests)) var notifier chainntnfs.ChainNotifier for _, notifierDriver := range chainntnfs.RegisteredNotifiers() { notifierType := notifierDriver.NotifierType switch notifierType { case "btcd": notifier, err = notifierDriver.New(&rpcConfig) if err != nil { t.Fatalf("unable to create %v notifier: %v", notifierType, err) } } if err := notifier.Start(); err != nil { t.Fatalf("unable to start notifier %v: %v", notifierType, err) } for _, ntfnTest := range ntfnTests { ntfnTest(miner, notifier, t) } notifier.Stop() } }
func TestLightningWallet(t *testing.T) { // TODO(roasbeef): switch to testnetL later netParams := &chaincfg.SimNetParams // Initialize the harness around a btcd node which will serve as our // dedicated miner to generate blocks, cause re-orgs, etc. We'll set // up this node with a chain length of 125, so we have plentyyy of BTC // to play around with. miningNode, err := rpctest.New(netParams, nil, nil) defer miningNode.TearDown() if err != nil { t.Fatalf("unable to create mining node: %v", err) } if err := miningNode.SetUp(true, 25); err != nil { t.Fatalf("unable to set up mining node: %v", err) } // Funding via 5 outputs with 4BTC each. testDir, lnwallet, err := createTestWallet(miningNode, netParams) if err != nil { t.Fatalf("unable to create test ln wallet: %v", err) } defer os.RemoveAll(testDir) defer lnwallet.Shutdown() // The wallet should now have 20BTC available for spending. assertProperBalance(t, lnwallet, 1, 20) // Execute every test, clearing possibly mutated wallet state after // each step. for _, walletTest := range walletTests { walletTest(miningNode, lnwallet, t) if err := clearWalletState(lnwallet); err != nil && err != walletdb.ErrBucketNotFound { t.Fatalf("unable to clear wallet state: %v", err) } // TODO(roasbeef): possible reset mining node's chainstate to // initial level } }
func TestMain(m *testing.M) { var err error // In order to properly test scenarios on as if we were on mainnet, // ensure that non-standard transactions aren't accepted into the // mempool or relayed. btcdCfg := []string{"--rejectnonstd"} primaryHarness, err = rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg) if err != nil { fmt.Println("unable to create primary harness: ", err) os.Exit(1) } // Initialize the primary mining node with a chain of length 125, // providing 25 mature coinbases to allow spending from for testing // purposes. if err := primaryHarness.SetUp(true, 25); err != nil { fmt.Println("unable to setup test chain: ", err) // Even though the harness was not fully setup, it still needs // to be torn down to ensure all resources such as temp // directories are cleaned up. The error is intentionally // ignored since this is already an error path and nothing else // could be done about it anyways. _ = primaryHarness.TearDown() os.Exit(1) } exitCode := m.Run() // Clean up any active harnesses that are still currently running.This // includes removing all temporary directories, and shutting down any // created processes. if err := rpctest.TearDownAll(); err != nil { fmt.Println("unable to tear down all harnesses: ", err) os.Exit(1) } os.Exit(exitCode) }
// TestInterfaces tests all registered interfaces with a unified set of tests // which excersie each of the required methods found within the WalletController // interface. // // NOTE: In the future, when additional implementations of the WalletController // interface have been implemented, in order to ensure the new concrete // implementation is automatically tested, two steps must be undertaken. First, // one needs add a "non-captured" (_) import from the new sub-package. This // import should trigger an init() method within the package which registeres // the interface. Second, an additional case in the switch within the main loop // below needs to be added which properly initializes the interface. // // TODO(roasbeef): purge bobNode in favor of dual lnwallet's func TestLightningWallet(t *testing.T) { netParams := &chaincfg.SimNetParams // Initialize the harness around a btcd node which will serve as our // dedicated miner to generate blocks, cause re-orgs, etc. We'll set // up this node with a chain length of 125, so we have plentyyy of BTC // to play around with. miningNode, err := rpctest.New(netParams, nil, nil) defer miningNode.TearDown() if err != nil { t.Fatalf("unable to create mining node: %v", err) } if err := miningNode.SetUp(true, 25); err != nil { t.Fatalf("unable to set up mining node: %v", err) } rpcConfig := miningNode.RPCConfig() chainNotifier, err := btcdnotify.New(&rpcConfig) if err != nil { t.Fatalf("unable to create notifier: %v", err) } if err := chainNotifier.Start(); err != nil { t.Fatalf("unable to start notifier: %v", err) } var bio lnwallet.BlockChainIO var signer lnwallet.Signer var wc lnwallet.WalletController for _, walletDriver := range lnwallet.RegisteredWallets() { tempTestDir, err := ioutil.TempDir("", "lnwallet") if err != nil { t.Fatalf("unable to create temp directory: %v", err) } defer os.RemoveAll(tempTestDir) walletType := walletDriver.WalletType switch walletType { case "btcwallet": btcwalletConfig := &btcwallet.Config{ PrivatePass: privPass, HdSeed: testHdSeed[:], DataDir: tempTestDir, NetParams: netParams, RpcHost: rpcConfig.Host, RpcUser: rpcConfig.User, RpcPass: rpcConfig.Pass, CACert: rpcConfig.Certificates, } wc, err = walletDriver.New(btcwalletConfig) if err != nil { t.Fatalf("unable to create btcwallet: %v", err) } signer = wc.(*btcwallet.BtcWallet) bio = wc.(*btcwallet.BtcWallet) default: t.Fatalf("unknown wallet driver: %v", walletType) } // Funding via 20 outputs with 4BTC each. lnwallet, err := createTestWallet(tempTestDir, miningNode, netParams, chainNotifier, wc, signer, bio) if err != nil { t.Fatalf("unable to create test ln wallet: %v", err) } // The wallet should now have 80BTC available for spending. assertProperBalance(t, lnwallet, 1, 80) // Execute every test, clearing possibly mutated wallet state after // each step. for _, walletTest := range walletTests { walletTest(miningNode, lnwallet, t) // TODO(roasbeef): possible reset mining node's chainstate to // initial level, cleanly wipe buckets if err := clearWalletState(lnwallet); err != nil && err != bolt.ErrBucketNotFound { t.Fatalf("unable to wipe wallet state: %v", err) } } lnwallet.Shutdown() } }
// TestBIP0009Mining ensures blocks built via btcd's CPU miner follow the rules // set forth by BIP0009 by using the test dummy deployment. // // Overview: // - Generate block 1 // - Assert bit is NOT set (ThresholdDefined) // - Generate enough blocks to reach first state transition // - Assert bit is NOT set for block prior to state transition // - Assert bit is set for block at state transition (ThresholdStarted) // - Generate enough blocks to reach second state transition // - Assert bit is set for block at state transition (ThresholdLockedIn) // - Generate enough blocks to reach third state transition // - Assert bit is set for block prior to state transition (ThresholdLockedIn) // - Assert bit is NOT set for block at state transition (ThresholdActive) func TestBIP0009Mining(t *testing.T) { t.Parallel() // Initialize the primary mining node with only the genesis block. r, err := rpctest.New(&chaincfg.SimNetParams, nil, nil) if err != nil { t.Fatalf("unable to create primary harness: %v", err) } if err := r.SetUp(true, 0); err != nil { t.Fatalf("unable to setup test chain: %v", err) } defer r.TearDown() // Assert the chain only consists of the gensis block. assertChainHeight(r, t, 0) // *** ThresholdDefined *** // // Generate a block that extends the genesis block. It should not have // the test dummy bit set in the version since the first window is // in the defined threshold state. deployment := &r.ActiveNet.Deployments[chaincfg.DeploymentTestDummy] testDummyBitNum := deployment.BitNumber hashes, err := r.Node.Generate(1) if err != nil { t.Fatalf("unable to generate blocks: %v", err) } assertChainHeight(r, t, 1) assertVersionBit(r, t, hashes[0], testDummyBitNum, false) // *** ThresholdStarted *** // // Generate enough blocks to reach the first state transition. // // The second to last generated block should not have the test bit set // in the version. // // The last generated block should now have the test bit set in the // version since the btcd mining code will have recognized the test // dummy deployment as started. confirmationWindow := r.ActiveNet.MinerConfirmationWindow numNeeded := confirmationWindow - 1 hashes, err = r.Node.Generate(numNeeded) if err != nil { t.Fatalf("failed to generated %d blocks: %v", numNeeded, err) } assertChainHeight(r, t, confirmationWindow) assertVersionBit(r, t, hashes[len(hashes)-2], testDummyBitNum, false) assertVersionBit(r, t, hashes[len(hashes)-1], testDummyBitNum, true) // *** ThresholdLockedIn *** // // Generate enough blocks to reach the next state transition. // // The last generated block should still have the test bit set in the // version since the btcd mining code will have recognized the test // dummy deployment as locked in. hashes, err = r.Node.Generate(confirmationWindow) if err != nil { t.Fatalf("failed to generated %d blocks: %v", confirmationWindow, err) } assertChainHeight(r, t, confirmationWindow*2) assertVersionBit(r, t, hashes[len(hashes)-1], testDummyBitNum, true) // *** ThresholdActivated *** // // Generate enough blocks to reach the next state transition. // // The second to last generated block should still have the test bit set // in the version since it is still locked in. // // The last generated block should NOT have the test bit set in the // version since the btcd mining code will have recognized the test // dummy deployment as activated and thus there is no longer any need // to set the bit. hashes, err = r.Node.Generate(confirmationWindow) if err != nil { t.Fatalf("failed to generated %d blocks: %v", confirmationWindow, err) } assertChainHeight(r, t, confirmationWindow*3) assertVersionBit(r, t, hashes[len(hashes)-2], testDummyBitNum, true) assertVersionBit(r, t, hashes[len(hashes)-1], testDummyBitNum, false) }
// testBIP0009 ensures the BIP0009 soft fork mechanism follows the state // transition rules set forth by the BIP for the provided soft fork key. It // uses the regression test network to signal support and advance through the // various threshold states including failure to achieve locked in status. // // See TestBIP0009 for an overview of what is tested. // // NOTE: This only differs from the exported version in that it accepts the // specific soft fork deployment to test. func testBIP0009(t *testing.T, forkKey string, deploymentID uint32) { // Initialize the primary mining node with only the genesis block. r, err := rpctest.New(&chaincfg.RegressionNetParams, nil, nil) if err != nil { t.Fatalf("unable to create primary harness: %v", err) } if err := r.SetUp(false, 0); err != nil { t.Fatalf("unable to setup test chain: %v", err) } defer r.TearDown() // *** ThresholdDefined *** // // Assert the chain height is the expected value and the soft fork // status starts out as defined. assertChainHeight(r, t, 0) assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdDefined) // *** ThresholdDefined part 2 - 1 block prior to ThresholdStarted *** // // Generate enough blocks to reach the height just before the first // state transition without signalling support since the state should // move to started once the start time has been reached regardless of // support signalling. // // NOTE: This is two blocks before the confirmation window because the // getblockchaininfo RPC reports the status for the block AFTER the // current one. All of the heights below are thus offset by one to // compensate. // // Assert the chain height is the expected value and soft fork status is // still defined and did NOT move to started. confirmationWindow := r.ActiveNet.MinerConfirmationWindow for i := uint32(0); i < confirmationWindow-2; i++ { _, err := r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion, time.Time{}) if err != nil { t.Fatalf("failed to generated block %d: %v", i, err) } } assertChainHeight(r, t, confirmationWindow-2) assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdDefined) // *** ThresholdStarted *** // // Generate another block to reach the next window. // // Assert the chain height is the expected value and the soft fork // status is started. _, err = r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion, time.Time{}) if err != nil { t.Fatalf("failed to generated block: %v", err) } assertChainHeight(r, t, confirmationWindow-1) assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdStarted) // *** ThresholdStarted part 2 - Fail to achieve ThresholdLockedIn *** // // Generate enough blocks to reach the next window in such a way that // the number blocks with the version bit set to signal support is 1 // less than required to achieve locked in status. // // Assert the chain height is the expected value and the soft fork // status is still started and did NOT move to locked in. if deploymentID > uint32(len(r.ActiveNet.Deployments)) { t.Fatalf("deployment ID %d does not exist", deploymentID) } deployment := &r.ActiveNet.Deployments[deploymentID] activationThreshold := r.ActiveNet.RuleChangeActivationThreshold signalForkVersion := int32(1<<deployment.BitNumber) | vbTopBits for i := uint32(0); i < activationThreshold-1; i++ { _, err := r.GenerateAndSubmitBlock(nil, signalForkVersion, time.Time{}) if err != nil { t.Fatalf("failed to generated block %d: %v", i, err) } } for i := uint32(0); i < confirmationWindow-(activationThreshold-1); i++ { _, err := r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion, time.Time{}) if err != nil { t.Fatalf("failed to generated block %d: %v", i, err) } } assertChainHeight(r, t, (confirmationWindow*2)-1) assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdStarted) // *** ThresholdLockedIn *** // // Generate enough blocks to reach the next window in such a way that // the number blocks with the version bit set to signal support is // exactly the number required to achieve locked in status. // // Assert the chain height is the expected value and the soft fork // status moved to locked in. for i := uint32(0); i < activationThreshold; i++ { _, err := r.GenerateAndSubmitBlock(nil, signalForkVersion, time.Time{}) if err != nil { t.Fatalf("failed to generated block %d: %v", i, err) } } for i := uint32(0); i < confirmationWindow-activationThreshold; i++ { _, err := r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion, time.Time{}) if err != nil { t.Fatalf("failed to generated block %d: %v", i, err) } } assertChainHeight(r, t, (confirmationWindow*3)-1) assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdLockedIn) // *** ThresholdLockedIn part 2 -- 1 block prior to ThresholdActive *** // // Generate enough blocks to reach the height just before the next // window without continuing to signal support since it is already // locked in. // // Assert the chain height is the expected value and the soft fork // status is still locked in and did NOT move to active. for i := uint32(0); i < confirmationWindow-1; i++ { _, err := r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion, time.Time{}) if err != nil { t.Fatalf("failed to generated block %d: %v", i, err) } } assertChainHeight(r, t, (confirmationWindow*4)-2) assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdLockedIn) // *** ThresholdActive *** // // Generate another block to reach the next window without continuing to // signal support since it is already locked in. // // Assert the chain height is the expected value and the soft fork // status moved to active. _, err = r.GenerateAndSubmitBlock(nil, vbLegacyBlockVersion, time.Time{}) if err != nil { t.Fatalf("failed to generated block: %v", err) } assertChainHeight(r, t, (confirmationWindow*4)-1) assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdActive) }
// TestBIP0068AndBIP0112Activation tests for the proper adherence to the BIP // 112 and BIP 68 rule-set after the activation of the CSV-package soft-fork. // // Overview: // - Pre soft-fork: // - A transaction spending a CSV output validly should be rejected from the // mempool, but accepted in a valid generated block including the // transaction. // - Post soft-fork: // - See the cases exercised within the table driven tests towards the end // of this test. func TestBIP0068AndBIP0112Activation(t *testing.T) { t.Parallel() // We'd like the test proper evaluation and validation of the BIP 68 // (sequence locks) and BIP 112 rule-sets which add input-age based // relative lock times. btcdCfg := []string{"--rejectnonstd"} r, err := rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg) if err != nil { t.Fatal("unable to create primary harness: ", err) } if err := r.SetUp(true, 1); err != nil { t.Fatalf("unable to setup test chain: %v", err) } defer r.TearDown() assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdStarted) harnessAddr, err := r.NewAddress() if err != nil { t.Fatalf("unable to obtain harness address: %v", err) } harnessScript, err := txscript.PayToAddrScript(harnessAddr) if err != nil { t.Fatalf("unable to generate pkScript: %v", err) } const ( outputAmt = btcutil.SatoshiPerBitcoin relativeBlockLock = 10 ) sweepOutput := &wire.TxOut{ Value: outputAmt - 5000, PkScript: harnessScript, } // As the soft-fork hasn't yet activated _any_ transaction version // which uses the CSV opcode should be accepted. Since at this point, // CSV doesn't actually exist, it's just a NOP. for txVersion := int32(0); txVersion < 3; txVersion++ { // Create a trivially spendable output with a CSV lock-time of // 10 relative blocks. redeemScript, testUTXO, tx, err := createCSVOutput(r, t, outputAmt, relativeBlockLock, false) if err != nil { t.Fatalf("unable to create CSV encumbered output: %v", err) } // As the transaction is p2sh it should be accepted into the // mempool and found within the next generated block. if _, err := r.Node.SendRawTransaction(tx, true); err != nil { t.Fatalf("unable to broadcast tx: %v", err) } blocks, err := r.Node.Generate(1) if err != nil { t.Fatalf("unable to generate blocks: %v", err) } txid := tx.TxHash() assertTxInBlock(r, t, blocks[0], &txid) // Generate a custom transaction which spends the CSV output. sequenceNum := blockchain.LockTimeToSequence(false, 10) spendingTx, err := spendCSVOutput(redeemScript, testUTXO, sequenceNum, sweepOutput, txVersion) if err != nil { t.Fatalf("unable to spend csv output: %v", err) } // This transaction should be rejected from the mempool since // CSV validation is already mempool policy pre-fork. _, err = r.Node.SendRawTransaction(spendingTx, true) if err == nil { t.Fatalf("transaction should have been rejected, but was " + "instead accepted") } // However, this transaction should be accepted in a custom // generated block as CSV validation for scripts within blocks // shouldn't yet be active. txns := []*btcutil.Tx{btcutil.NewTx(spendingTx)} block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err != nil { t.Fatalf("unable to submit block: %v", err) } txid = spendingTx.TxHash() assertTxInBlock(r, t, block.Hash(), &txid) } // At this point, the block height should be 107: we started at height // 101, then generated 2 blocks in each loop iteration above. assertChainHeight(r, t, 107) // With the height at 107 we need 200 blocks to be mined after the // genesis target period, so we mine 192 blocks. This'll put us at // height 299. The getblockchaininfo call checks the state for the // block AFTER the current height. numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 8 if _, err := r.Node.Generate(numBlocks); err != nil { t.Fatalf("unable to generate blocks: %v", err) } assertChainHeight(r, t, 299) assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdActive) // Knowing the number of outputs needed for the tests below, create a // fresh output for use within each of the test-cases below. const relativeTimeLock = 512 const numTests = 8 type csvOutput struct { RedeemScript []byte Utxo *wire.OutPoint Timelock int32 } var spendableInputs [numTests]csvOutput // Create three outputs which have a block-based sequence locks, and // three outputs which use the above time based sequence lock. for i := 0; i < numTests; i++ { timeLock := relativeTimeLock isSeconds := true if i < 7 { timeLock = relativeBlockLock isSeconds = false } redeemScript, utxo, tx, err := createCSVOutput(r, t, outputAmt, int32(timeLock), isSeconds) if err != nil { t.Fatalf("unable to create CSV output: %v", err) } if _, err := r.Node.SendRawTransaction(tx, true); err != nil { t.Fatalf("unable to broadcast transaction: %v", err) } spendableInputs[i] = csvOutput{ RedeemScript: redeemScript, Utxo: utxo, Timelock: int32(timeLock), } } // Mine a single block including all the transactions generated above. if _, err := r.Node.Generate(1); err != nil { t.Fatalf("unable to generate block: %v", err) } // Now mine 10 additional blocks giving the inputs generated above a // age of 11. Space out each block 10 minutes after the previous block. prevBlockHash, err := r.Node.GetBestBlockHash() if err != nil { t.Fatalf("unable to get prior block hash: %v", err) } prevBlock, err := r.Node.GetBlock(prevBlockHash) if err != nil { t.Fatalf("unable to get block: %v", err) } for i := 0; i < relativeBlockLock; i++ { timeStamp := prevBlock.Header.Timestamp.Add(time.Minute * 10) b, err := r.GenerateAndSubmitBlock(nil, -1, timeStamp) if err != nil { t.Fatalf("unable to generate block: %v", err) } prevBlock = b.MsgBlock() } // A helper function to create fully signed transactions in-line during // the array initialization below. var inputIndex uint32 makeTxCase := func(sequenceNum uint32, txVersion int32) *wire.MsgTx { csvInput := spendableInputs[inputIndex] tx, err := spendCSVOutput(csvInput.RedeemScript, csvInput.Utxo, sequenceNum, sweepOutput, txVersion) if err != nil { t.Fatalf("unable to spend CSV output: %v", err) } inputIndex++ return tx } tests := [numTests]struct { tx *wire.MsgTx accept bool }{ // A valid transaction with a single input a sequence number // creating a 100 block relative time-lock. This transaction // should be rejected as its version number is 1, and only tx // of version > 2 will trigger the CSV behavior. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 100), 1), accept: false, }, // A transaction of version 2 spending a single input. The // input has a relative time-lock of 1 block, but the disable // bit it set. The transaction should be rejected as a result. { tx: makeTxCase( blockchain.LockTimeToSequence(false, 1)|wire.SequenceLockTimeDisabled, 2, ), accept: false, }, // A v2 transaction with a single input having a 9 block // relative time lock. The referenced input is 11 blocks old, // but the CSV output requires a 10 block relative lock-time. // Therefore, the transaction should be rejected. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 9), 2), accept: false, }, // A v2 transaction with a single input having a 10 block // relative time lock. The referenced input is 11 blocks old so // the transaction should be accepted. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 10), 2), accept: true, }, // A v2 transaction with a single input having a 11 block // relative time lock. The input referenced has an input age of // 11 and the CSV op-code requires 10 blocks to have passed, so // this transaction should be accepted. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 11), 2), accept: true, }, // A v2 transaction whose input has a 1000 blck relative time // lock. This should be rejected as the input's age is only 11 // blocks. { tx: makeTxCase(blockchain.LockTimeToSequence(false, 1000), 2), accept: false, }, // A v2 transaction with a single input having a 512,000 second // relative time-lock. This transaction should be rejected as 6 // days worth of blocks haven't yet been mined. The referenced // input doesn't have sufficient age. { tx: makeTxCase(blockchain.LockTimeToSequence(true, 512000), 2), accept: false, }, // A v2 transaction whose single input has a 512 second // relative time-lock. This transaction should be accepted as // finalized. { tx: makeTxCase(blockchain.LockTimeToSequence(true, 512), 2), accept: true, }, } for i, test := range tests { txid, err := r.Node.SendRawTransaction(test.tx, true) switch { // Test case passes, nothing further to report. case test.accept && err == nil: // Transaction should have been accepted but we have a non-nil // error. case test.accept && err != nil: t.Fatalf("test #%d, transaction should be accepted, "+ "but was rejected: %v", i, err) // Transaction should have been rejected, but it was accepted. case !test.accept && err == nil: t.Fatalf("test #%d, transaction should be rejected, "+ "but was accepted", i) // Transaction was rejected as wanted, nothing more to do. case !test.accept && err != nil: } // If the transaction should be rejected, manually mine a block // with the non-final transaction. It should be rejected. if !test.accept { txns := []*btcutil.Tx{btcutil.NewTx(test.tx)} _, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err == nil { t.Fatalf("test #%d, invalid block accepted", i) } continue } // Generate a block, the transaction should be included within // the newly mined block. blockHashes, err := r.Node.Generate(1) if err != nil { t.Fatalf("unable to mine block: %v", err) } assertTxInBlock(r, t, blockHashes[0], txid) } }
// TestBIP0113Activation tests for proper adherance of the BIP 113 rule // constraint which requires all transaction finality tests to use the MTP of // the last 11 blocks, rather than the timestamp of the block which includes // them. // // Overview: // - Pre soft-fork: // - Transactions with non-final lock-times from the PoV of MTP should be // rejected from the mempool. // - Transactions within non-final MTP based lock-times should be accepted // in valid blocks. // // - Post soft-fork: // - Transactions with non-final lock-times from the PoV of MTP should be // rejected from the mempool and when found within otherwise valid blocks. // - Transactions with final lock-times from the PoV of MTP should be // accepted to the mempool and mined in future block. func TestBIP0113Activation(t *testing.T) { t.Parallel() btcdCfg := []string{"--rejectnonstd"} r, err := rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg) if err != nil { t.Fatal("unable to create primary harness: ", err) } if err := r.SetUp(true, 1); err != nil { t.Fatalf("unable to setup test chain: %v", err) } defer r.TearDown() // Create a fresh output for usage within the test below. const outputValue = btcutil.SatoshiPerBitcoin outputKey, testOutput, testPkScript, err := makeTestOutput(r, t, outputValue) if err != nil { t.Fatalf("unable to create test output: %v", err) } // Fetch a fresh address from the harness, we'll use this address to // send funds back into the Harness. addr, err := r.NewAddress() if err != nil { t.Fatalf("unable to generate address: %v", err) } addrScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to generate addr script: %v", err) } // Now create a transaction with a lock time which is "final" according // to the latest block, but not according to the current median time // past. tx := wire.NewMsgTx(1) tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: *testOutput, }) tx.AddTxOut(&wire.TxOut{ PkScript: addrScript, Value: outputValue - 1000, }) // We set the lock-time of the transaction to just one minute after the // current MTP of the chain. chainInfo, err := r.Node.GetBlockChainInfo() if err != nil { t.Fatalf("unable to query for chain info: %v", err) } tx.LockTime = uint32(chainInfo.MedianTime) + 1 sigScript, err := txscript.SignatureScript(tx, 0, testPkScript, txscript.SigHashAll, outputKey, true) if err != nil { t.Fatalf("unable to generate sig: %v", err) } tx.TxIn[0].SignatureScript = sigScript // This transaction should be rejected from the mempool as using MTP // for transactions finality is now a policy rule. Additionally, the // exact error should be the rejection of a non-final transaction. _, err = r.Node.SendRawTransaction(tx, true) if err == nil { t.Fatalf("transaction accepted, but should be non-final") } else if !strings.Contains(err.Error(), "not finalized") { t.Fatalf("transaction should be rejected due to being "+ "non-final, instead: %v", err) } // However, since the block validation consensus rules haven't yet // activated, a block including the transaction should be accepted. txns := []*btcutil.Tx{btcutil.NewTx(tx)} block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err != nil { t.Fatalf("unable to submit block: %v", err) } txid := tx.TxHash() assertTxInBlock(r, t, block.Hash(), &txid) // At this point, the block height should be 103: we mined 101 blocks // to create a single mature output, then an additional block to create // a new output, and then mined a single block above to include our // transation. assertChainHeight(r, t, 103) // Next, mine enough blocks to ensure that the soft-fork becomes // activated. Assert that the block version of the second-to-last block // in the final range is active. // Next, mine ensure blocks to ensure that the soft-fork becomes // active. We're at height 103 and we need 200 blocks to be mined after // the genesis target period, so we mine 196 blocks. This'll put us at // height 299. The getblockchaininfo call checks the state for the // block AFTER the current height. numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 4 if _, err := r.Node.Generate(numBlocks); err != nil { t.Fatalf("unable to generate blocks: %v", err) } assertChainHeight(r, t, 299) assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdActive) // The timeLockDeltas slice represents a series of deviations from the // current MTP which will be used to test border conditions w.r.t // transaction finality. -1 indicates 1 second prior to the MTP, 0 // indicates the current MTP, and 1 indicates 1 second after the // current MTP. // // This time, all transactions which are final according to the MTP // *should* be accepted to both the mempool and within a valid block. // While transactions with lock-times *after* the current MTP should be // rejected. timeLockDeltas := []int64{-1, 0, 1} for _, timeLockDelta := range timeLockDeltas { chainInfo, err = r.Node.GetBlockChainInfo() if err != nil { t.Fatalf("unable to query for chain info: %v", err) } medianTimePast := chainInfo.MedianTime // Create another test output to be spent shortly below. outputKey, testOutput, testPkScript, err = makeTestOutput(r, t, outputValue) if err != nil { t.Fatalf("unable to create test output: %v", err) } // Create a new transaction with a lock-time past the current known // MTP. tx = wire.NewMsgTx(1) tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: *testOutput, }) tx.AddTxOut(&wire.TxOut{ PkScript: addrScript, Value: outputValue - 1000, }) tx.LockTime = uint32(medianTimePast + timeLockDelta) sigScript, err = txscript.SignatureScript(tx, 0, testPkScript, txscript.SigHashAll, outputKey, true) if err != nil { t.Fatalf("unable to generate sig: %v", err) } tx.TxIn[0].SignatureScript = sigScript // If the time-lock delta is greater than -1, then the // transaction should be rejected from the mempool and when // included within a block. A time-lock delta of -1 should be // accepted as it has a lock-time of one // second _before_ the current MTP. _, err = r.Node.SendRawTransaction(tx, true) if err == nil && timeLockDelta >= 0 { t.Fatal("transaction was accepted into the mempool " + "but should be rejected!") } else if err != nil && !strings.Contains(err.Error(), "not finalized") { t.Fatalf("transaction should be rejected from mempool "+ "due to being non-final, instead: %v", err) } txns = []*btcutil.Tx{btcutil.NewTx(tx)} _, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) if err == nil && timeLockDelta >= 0 { t.Fatal("block should be rejected due to non-final " + "txn, but was accepted") } else if err != nil && !strings.Contains(err.Error(), "unfinalized") { t.Fatalf("block should be rejected due to non-final "+ "tx, instead: %v", err) } } }