2016-11-03 01:17:18 +01:00
|
|
|
// Copyright (c) 2016 The btcsuite developers
|
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// This file is ignored during the regular tests due to the following build tag.
|
|
|
|
// +build rpctest
|
|
|
|
|
|
|
|
package integration
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/blockchain"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
2016-10-26 18:55:49 +02:00
|
|
|
"github.com/btcsuite/btcd/integration/rpctest"
|
2016-11-03 01:17:18 +01:00
|
|
|
"github.com/btcsuite/btcd/txscript"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
csvKey = "csv"
|
|
|
|
)
|
|
|
|
|
|
|
|
// makeTestOutput creates an on-chain output paying to a freshly generated
|
|
|
|
// p2pkh output with the specified amount.
|
|
|
|
func makeTestOutput(r *rpctest.Harness, t *testing.T,
|
|
|
|
amt btcutil.Amount) (*btcec.PrivateKey, *wire.OutPoint, []byte, error) {
|
|
|
|
|
|
|
|
// Create a fresh key, then send some coins to an address spendable by
|
|
|
|
// that key.
|
|
|
|
key, err := btcec.NewPrivateKey(btcec.S256())
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Using the key created above, generate a pkScript which it's able to
|
|
|
|
// spend.
|
|
|
|
a, err := btcutil.NewAddressPubKey(key.PubKey().SerializeCompressed(), r.ActiveNet)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
selfAddrScript, err := txscript.PayToAddrScript(a.AddressPubKeyHash())
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
output := &wire.TxOut{PkScript: selfAddrScript, Value: 1e8}
|
|
|
|
|
|
|
|
// Next, create and broadcast a transaction paying to the output.
|
|
|
|
fundTx, err := r.CreateTransaction([]*wire.TxOut{output}, 10)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
txHash, err := r.Node.SendRawTransaction(fundTx, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// The transaction created above should be included within the next
|
|
|
|
// generated block.
|
|
|
|
blockHash, err := r.Node.Generate(1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
assertTxInBlock(r, t, blockHash[0], txHash)
|
|
|
|
|
|
|
|
// Locate the output index of the coins spendable by the key we
|
|
|
|
// generated above, this is needed in order to create a proper utxo for
|
|
|
|
// this output.
|
|
|
|
var outputIndex uint32
|
|
|
|
if bytes.Equal(fundTx.TxOut[0].PkScript, selfAddrScript) {
|
|
|
|
outputIndex = 0
|
|
|
|
} else {
|
|
|
|
outputIndex = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
utxo := &wire.OutPoint{
|
|
|
|
Hash: fundTx.TxHash(),
|
|
|
|
Index: outputIndex,
|
|
|
|
}
|
|
|
|
|
|
|
|
return key, utxo, selfAddrScript, nil
|
|
|
|
}
|
|
|
|
|
2017-10-29 20:56:03 +01:00
|
|
|
// TestBIP0113Activation tests for proper adherence of the BIP 113 rule
|
2016-11-03 01:17:18 +01:00
|
|
|
// 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
|
2017-10-29 20:56:03 +01:00
|
|
|
// transaction.
|
2016-11-03 01:17:18 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// spendCSVOutput spends an output previously created by the createCSVOutput
|
|
|
|
// function. The sigScript is a trivial push of OP_TRUE followed by the
|
|
|
|
// redeemScript to pass P2SH evaluation.
|
|
|
|
func spendCSVOutput(redeemScript []byte, csvUTXO *wire.OutPoint,
|
|
|
|
sequence uint32, targetOutput *wire.TxOut,
|
|
|
|
txVersion int32) (*wire.MsgTx, error) {
|
|
|
|
|
|
|
|
tx := wire.NewMsgTx(txVersion)
|
|
|
|
tx.AddTxIn(&wire.TxIn{
|
|
|
|
PreviousOutPoint: *csvUTXO,
|
|
|
|
Sequence: sequence,
|
|
|
|
})
|
|
|
|
tx.AddTxOut(targetOutput)
|
|
|
|
|
|
|
|
b := txscript.NewScriptBuilder().
|
|
|
|
AddOp(txscript.OP_TRUE).
|
|
|
|
AddData(redeemScript)
|
|
|
|
|
|
|
|
sigScript, err := b.Script()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tx.TxIn[0].SignatureScript = sigScript
|
|
|
|
|
|
|
|
return tx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// assertTxInBlock asserts a transaction with the specified txid is found
|
|
|
|
// within the block with the passed block hash.
|
|
|
|
func assertTxInBlock(r *rpctest.Harness, t *testing.T, blockHash *chainhash.Hash,
|
|
|
|
txid *chainhash.Hash) {
|
|
|
|
|
|
|
|
block, err := r.Node.GetBlock(blockHash)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to get block: %v", err)
|
|
|
|
}
|
|
|
|
if len(block.Transactions) < 2 {
|
|
|
|
t.Fatal("target transaction was not mined")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, txn := range block.Transactions {
|
|
|
|
txHash := txn.TxHash()
|
|
|
|
if txn.TxHash() == txHash {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, _, line, _ := runtime.Caller(1)
|
|
|
|
t.Fatalf("assertion failed at line %v: txid %v was not found in "+
|
|
|
|
"block %v", line, txid, blockHash)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|