// createCSVOutput creates an output paying to a trivially redeemable CSV // pkScript with the specified time-lock. func createCSVOutput(r *rpctest.Harness, t *testing.T, numSatoshis btcutil.Amount, timeLock int32, isSeconds bool) ([]byte, *wire.OutPoint, *wire.MsgTx, error) { // Convert the time-lock to the proper sequence lock based according to // if the lock is seconds or time based. sequenceLock := blockchain.LockTimeToSequence(isSeconds, uint32(timeLock)) // Our CSV script is simply: <sequenceLock> OP_CSV OP_DROP b := txscript.NewScriptBuilder(). AddInt64(int64(sequenceLock)). AddOp(txscript.OP_CHECKSEQUENCEVERIFY). AddOp(txscript.OP_DROP) csvScript, err := b.Script() if err != nil { return nil, nil, nil, err } // Using the script generated above, create a P2SH output which will be // accepted into the mempool. p2shAddr, err := btcutil.NewAddressScriptHash(csvScript, r.ActiveNet) if err != nil { return nil, nil, nil, err } p2shScript, err := txscript.PayToAddrScript(p2shAddr) if err != nil { return nil, nil, nil, err } output := &wire.TxOut{ PkScript: p2shScript, Value: int64(numSatoshis), } // Finally create a valid transaction which creates the output crafted // above. tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10) if err != nil { return nil, nil, nil, err } var outputIndex uint32 if !bytes.Equal(tx.TxOut[0].PkScript, p2shScript) { outputIndex = 1 } utxo := &wire.OutPoint{ Hash: tx.TxHash(), Index: outputIndex, } return csvScript, utxo, tx, nil }
// TestCalcSequenceLock tests the LockTimeToSequence function, and the // CalcSequenceLock method of a Chain instance. The tests exercise several // combinations of inputs to the CalcSequenceLock function in order to ensure // the returned SequenceLocks are correct for each test instance. func TestCalcSequenceLock(t *testing.T) { netParams := &chaincfg.SimNetParams // Create a new database and chain instance to run tests against. chain, teardownFunc, err := chainSetup("calcseqlock", netParams) if err != nil { t.Errorf("Failed to setup chain instance: %v", err) return } defer teardownFunc() // Since we're not dealing with the real block chain, disable // checkpoints and set the coinbase maturity to 1. chain.DisableCheckpoints(true) chain.TstSetCoinbaseMaturity(1) // Create a test mining address to use for the blocks we'll generate // shortly below. k := bytes.Repeat([]byte{1}, 32) _, miningPub := btcec.PrivKeyFromBytes(btcec.S256(), k) miningAddr, err := btcutil.NewAddressPubKey(miningPub.SerializeCompressed(), netParams) if err != nil { t.Fatalf("unable to generate mining addr: %v", err) } // We'll keep track of the previous block for back pointers in blocks // we generated, and also the generated blocks along with the MTP from // their PoV to aide with our relative time lock calculations. var prevBlock *btcutil.Block var blocksWithMTP []struct { block *btcutil.Block mtp time.Time } // We need to activate CSV in order to test the processing logic, so // manually craft the block version that's used to signal the soft-fork // activation. csvBit := netParams.Deployments[chaincfg.DeploymentCSV].BitNumber blockVersion := int32(0x20000000 | (uint32(1) << csvBit)) // Generate enough blocks to activate CSV, collecting each of the // blocks into a slice for later use. numBlocksToActivate := (netParams.MinerConfirmationWindow * 3) for i := uint32(0); i < numBlocksToActivate; i++ { block, err := rpctest.CreateBlock(prevBlock, nil, blockVersion, time.Time{}, miningAddr, netParams) if err != nil { t.Fatalf("unable to generate block: %v", err) } mtp := chain.BestSnapshot().MedianTime _, isOrphan, err := chain.ProcessBlock(block, blockchain.BFNone) if err != nil { t.Fatalf("ProcessBlock fail on block %v: %v\n", i, err) } if isOrphan { t.Fatalf("ProcessBlock incorrectly returned block %v "+ "is an orphan\n", i) } blocksWithMTP = append(blocksWithMTP, struct { block *btcutil.Block mtp time.Time }{ block: block, mtp: mtp, }) prevBlock = block } // Create a utxo view with all the utxos within the blocks created // above. utxoView := blockchain.NewUtxoViewpoint() for blockHeight, blockWithMTP := range blocksWithMTP { for _, tx := range blockWithMTP.block.Transactions() { utxoView.AddTxOuts(tx, int32(blockHeight)) } } utxoView.SetBestHash(blocksWithMTP[len(blocksWithMTP)-1].block.Hash()) // The median time calculated from the PoV of the best block in our // test chain. For unconfirmed inputs, this value will be used since // the MTP will be calculated from the PoV of the yet-to-be-mined // block. nextMedianTime := int64(1401292712) // We'll refer to this utxo within each input in the transactions // created below. This utxo has an age of 4 blocks and was mined within // block 297 targetTx := blocksWithMTP[len(blocksWithMTP)-4].block.Transactions()[0] utxo := wire.OutPoint{ Hash: *targetTx.Hash(), Index: 0, } // Obtain the median time past from the PoV of the input created above. // The MTP for the input is the MTP from the PoV of the block *prior* // to the one that included it. medianTime := blocksWithMTP[len(blocksWithMTP)-5].mtp.Unix() // Add an additional transaction which will serve as our unconfirmed // output. var fakeScript []byte unConfTx := &wire.MsgTx{ TxOut: []*wire.TxOut{{ PkScript: fakeScript, Value: 5, }}, } unConfUtxo := wire.OutPoint{ Hash: unConfTx.TxHash(), Index: 0, } // Adding a utxo with a height of 0x7fffffff indicates that the output // is currently unmined. utxoView.AddTxOuts(btcutil.NewTx(unConfTx), 0x7fffffff) tests := []struct { tx *btcutil.Tx view *blockchain.UtxoViewpoint want *blockchain.SequenceLock mempool bool }{ // A transaction of version one should disable sequence locks // as the new sequence number semantics only apply to // transactions version 2 or higher. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: -1, }, }, // A transaction with a single input, that a max int sequence // number. This sequence number has the high bit set, so // sequence locks should be disabled. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: wire.MaxTxInSequenceNum, }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: -1, }, }, // A transaction with a single input whose lock time is // expressed in seconds. However, the specified lock time is // below the required floor for time based lock times since // they have time granularity of 512 seconds. As a result, the // seconds lock-time should be just before the median time of // the targeted block. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime - 1, BlockHeight: -1, }, }, // A transaction with a single input whose lock time is // expressed in seconds. The number of seconds should be 1023 // seconds after the median past time of the last block in the // chain. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 1024), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + 1023, BlockHeight: -1, }, }, // A transaction with multiple inputs. The first input has a // sequence lock in blocks with a value of 4. The last input // has a sequence number with a value of 5, but has the disable // bit set. So the first lock should be selected as it's the // target lock as its the furthest in the future lock that // isn't disabled. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2560), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 5) | wire.SequenceLockTimeDisabled, }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 4), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1, BlockHeight: 299, }, }, // Transaction has a single input spending the genesis block // transaction. The input's sequence number is encodes a // relative lock-time in blocks (3 blocks). The sequence lock // should have a value of -1 for seconds, but a block height of // 298 meaning it can be included at height 299. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: 298, }, }, // A transaction with two inputs with lock times expressed in // seconds. The selected sequence lock value for seconds should // be the time further in the future. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 5120), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2560), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1, BlockHeight: -1, }, }, // A transaction with two inputs with lock times expressed in // seconds. The selected sequence lock value for blocks should // be the height further in the future. The converted absolute // block height should be 302, meaning it can be included in // block 303. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 1), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 7), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: 302, }, }, // A transaction with multiple inputs. Two inputs are time // based, and the other two are input maturity based. The lock // lying further into the future for both inputs should be // chosen. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 2560), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(true, 6656), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 3), }, { PreviousOutPoint: utxo, Sequence: blockchain.LockTimeToSequence(false, 9), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1, BlockHeight: 304, }, }, // A transaction with a single unconfirmed input. As the input // is confirmed, the height of the input should be interpreted // as the height of the *next* block. The current block height // is 300, so the lock time should be calculated using height // 301 as a base. A 2 block relative lock means the transaction // can be included after block 302, so in 303. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: unConfUtxo, Sequence: blockchain.LockTimeToSequence(false, 2), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: -1, BlockHeight: 302, }, }, // A transaction with a single unconfirmed input. The input has // a time based lock, so the lock time should be based off the // MTP of the *next* block. { tx: btcutil.NewTx(&wire.MsgTx{ Version: 2, TxIn: []*wire.TxIn{{ PreviousOutPoint: unConfUtxo, Sequence: blockchain.LockTimeToSequence(true, 1024), }}, }), view: utxoView, want: &blockchain.SequenceLock{ Seconds: nextMedianTime + 1023, BlockHeight: -1, }, }, } t.Logf("Running %v SequenceLock tests", len(tests)) for i, test := range tests { seqLock, err := chain.CalcSequenceLock(test.tx, test.view, test.mempool) if err != nil { t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err) } if seqLock.Seconds != test.want.Seconds { t.Fatalf("test #%d got %v seconds want %v seconds", i, seqLock.Seconds, test.want.Seconds) } if seqLock.BlockHeight != test.want.BlockHeight { t.Fatalf("test #%d got height of %v want height of %v ", i, seqLock.BlockHeight, test.want.BlockHeight) } } }
// 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) } }