[NOD-909] Add tests for double spends (#694)

* [NOD-909] Add tests for double spends

* [NOD-909] Add prepareAndProcessBlock that gets parent hashes and transactions as argument

* [NOD-909] Use PrepareAndProcessBlockForTest where possible

* [NOD-909] Use more meaningful names

* [NOD-909] Change a comment

* [NOD-909] Fix comment

* [NOD-909] Fix comment
This commit is contained in:
Ori Newman 2020-04-13 12:28:59 +03:00 committed by GitHub
parent fe91b4c878
commit d015286f65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 221 additions and 34 deletions

View File

@ -172,28 +172,12 @@ func checkRuleError(gotErr, wantErr error) error {
return nil
}
func prepareAndProcessBlock(t *testing.T, dag *BlockDAG, parents ...*wire.MsgBlock) *wire.MsgBlock {
func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parents ...*wire.MsgBlock) *wire.MsgBlock {
parentHashes := make([]*daghash.Hash, len(parents))
for i, parent := range parents {
parentHashes[i] = parent.BlockHash()
}
daghash.Sort(parentHashes)
block, err := PrepareBlockForTest(dag, parentHashes, nil)
if err != nil {
t.Fatalf("error in PrepareBlockForTest: %s", err)
}
utilBlock := util.NewBlock(block)
isOrphan, isDelayed, err := dag.ProcessBlock(utilBlock, BFNoPoWCheck)
if err != nil {
t.Fatalf("unexpected error in ProcessBlock: %s", err)
}
if isDelayed {
t.Fatalf("block is too far in the future")
}
if isOrphan {
t.Fatalf("block was unexpectedly orphan")
}
return block
return PrepareAndProcessBlockForTest(t, dag, parentHashes, nil)
}
func nodeByMsgBlock(t *testing.T, dag *BlockDAG, block *wire.MsgBlock) *blockNode {

View File

@ -688,7 +688,7 @@ func TestConfirmations(t *testing.T) {
chainBlocks := make([]*wire.MsgBlock, 5)
chainBlocks[0] = dag.dagParams.GenesisBlock
for i := uint32(1); i < 5; i++ {
chainBlocks[i] = prepareAndProcessBlock(t, dag, chainBlocks[i-1])
chainBlocks[i] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[i-1])
}
// Make sure that each one of the chain blocks has the expected confirmations number
@ -707,8 +707,8 @@ func TestConfirmations(t *testing.T) {
branchingBlocks := make([]*wire.MsgBlock, 2)
// Add two branching blocks
branchingBlocks[0] = prepareAndProcessBlock(t, dag, chainBlocks[1])
branchingBlocks[1] = prepareAndProcessBlock(t, dag, branchingBlocks[0])
branchingBlocks[0] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[1])
branchingBlocks[1] = prepareAndProcessBlockByParentMsgBlocks(t, dag, branchingBlocks[0])
// Check that the genesis has a confirmations number == len(chainBlocks)
genesisConfirmations, err = dag.blockConfirmations(dag.genesis)
@ -738,7 +738,7 @@ func TestConfirmations(t *testing.T) {
// Generate 100 blocks to force the "main" chain to become red
branchingChainTip := branchingBlocks[1]
for i := uint32(0); i < 100; i++ {
nextBranchingChainTip := prepareAndProcessBlock(t, dag, branchingChainTip)
nextBranchingChainTip := prepareAndProcessBlockByParentMsgBlocks(t, dag, branchingChainTip)
branchingChainTip = nextBranchingChainTip
}
@ -797,7 +797,7 @@ func TestAcceptingBlock(t *testing.T) {
chainBlocks := make([]*wire.MsgBlock, numChainBlocks)
chainBlocks[0] = dag.dagParams.GenesisBlock
for i := uint32(1); i <= numChainBlocks-1; i++ {
chainBlocks[i] = prepareAndProcessBlock(t, dag, chainBlocks[i-1])
chainBlocks[i] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[i-1])
}
// Make sure that each chain block (including the genesis) is accepted by its child
@ -825,7 +825,7 @@ func TestAcceptingBlock(t *testing.T) {
// Generate a chain tip that will be in the anticone of the selected tip and
// in dag.virtual.blues.
branchingChainTip := prepareAndProcessBlock(t, dag, chainBlocks[len(chainBlocks)-3])
branchingChainTip := prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[len(chainBlocks)-3])
// Make sure that branchingChainTip is not in the selected parent chain
isBranchingChainTipInSelectedParentChain, err := dag.IsInSelectedParentChain(branchingChainTip.BlockHash())
@ -863,7 +863,7 @@ func TestAcceptingBlock(t *testing.T) {
intersectionBlock := chainBlocks[1]
sideChainTip := intersectionBlock
for i := 0; i < len(chainBlocks)-3; i++ {
sideChainTip = prepareAndProcessBlock(t, dag, sideChainTip)
sideChainTip = prepareAndProcessBlockByParentMsgBlocks(t, dag, sideChainTip)
}
// Make sure that the accepting block of the parent of the branching block didn't change
@ -879,7 +879,7 @@ func TestAcceptingBlock(t *testing.T) {
// Make sure that a block that is found in the red set of the selected tip
// doesn't have an accepting block
prepareAndProcessBlock(t, dag, sideChainTip, chainBlocks[len(chainBlocks)-1])
prepareAndProcessBlockByParentMsgBlocks(t, dag, sideChainTip, chainBlocks[len(chainBlocks)-1])
sideChainTipAcceptingBlock, err := acceptingBlockByMsgBlock(sideChainTip)
if err != nil {
@ -1117,3 +1117,132 @@ func TestIsDAGCurrentMaxDiff(t *testing.T) {
}
}
}
func TestDoubleSpends(t *testing.T) {
params := dagconfig.SimnetParams
params.BlockCoinbaseMaturity = 0
// Create a new database and dag instance to run tests against.
dag, teardownFunc, err := DAGSetup("TestDoubleSpends", true, Config{
DAGParams: &params,
})
if err != nil {
t.Fatalf("Failed to setup dag instance: %v", err)
}
defer teardownFunc()
fundingBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{params.GenesisHash}, nil)
cbTx := fundingBlock.Transactions[0]
signatureScript, err := txscript.PayToScriptHashSignatureScript(OpTrueScript, nil)
if err != nil {
t.Fatalf("Failed to build signature script: %s", err)
}
txIn := &wire.TxIn{
PreviousOutpoint: wire.Outpoint{TxID: *cbTx.TxID(), Index: 0},
SignatureScript: signatureScript,
Sequence: wire.MaxTxInSequenceNum,
}
txOut := &wire.TxOut{
ScriptPubKey: OpTrueScript,
Value: uint64(1),
}
tx1 := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
doubleSpendTxOut := &wire.TxOut{
ScriptPubKey: OpTrueScript,
Value: uint64(2),
}
doubleSpendTx1 := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{doubleSpendTxOut})
blockWithTx1 := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*wire.MsgTx{tx1})
// Check that a block will be rejected if it has a transaction that already exists in its past.
anotherBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{blockWithTx1.BlockHash()}, nil)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
// Manually add tx1.
anotherBlockWithTx1.Transactions = append(anotherBlockWithTx1.Transactions, tx1)
anotherBlockWithTx1UtilTxs := make([]*util.Tx, len(anotherBlockWithTx1.Transactions))
for i, tx := range anotherBlockWithTx1.Transactions {
anotherBlockWithTx1UtilTxs[i] = util.NewTx(tx)
}
anotherBlockWithTx1.Header.HashMerkleRoot = BuildHashMerkleTreeStore(anotherBlockWithTx1UtilTxs).Root()
isOrphan, isDelayed, err := dag.ProcessBlock(util.NewBlock(anotherBlockWithTx1), BFNoPoWCheck)
if err == nil {
t.Errorf("ProcessBlock expected an error")
} else {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok {
if ruleErr.ErrorCode != ErrOverwriteTx {
t.Errorf("ProcessBlock expected an %v error code but got %v", ErrOverwriteTx, ruleErr.ErrorCode)
}
} else {
t.Errorf("ProcessBlock expected a blockdag.RuleError but got %v", err)
}
}
if isDelayed {
t.Fatalf("ProcessBlock: anotherBlockWithTx1 " +
"is too far in the future")
}
if isOrphan {
t.Fatalf("ProcessBlock: anotherBlockWithTx1 got unexpectedly orphaned")
}
// Check that a block will be rejected if it has a transaction that double spends
// a transaction from its past.
blockWithDoubleSpendForTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{blockWithTx1.BlockHash()}, nil)
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
// Manually add a transaction that double spends the block past.
blockWithDoubleSpendForTx1.Transactions = append(blockWithDoubleSpendForTx1.Transactions, doubleSpendTx1)
blockWithDoubleSpendForTx1UtilTxs := make([]*util.Tx, len(blockWithDoubleSpendForTx1.Transactions))
for i, tx := range blockWithDoubleSpendForTx1.Transactions {
blockWithDoubleSpendForTx1UtilTxs[i] = util.NewTx(tx)
}
blockWithDoubleSpendForTx1.Header.HashMerkleRoot = BuildHashMerkleTreeStore(blockWithDoubleSpendForTx1UtilTxs).Root()
isOrphan, isDelayed, err = dag.ProcessBlock(util.NewBlock(blockWithDoubleSpendForTx1), BFNoPoWCheck)
if err == nil {
t.Errorf("ProcessBlock expected an error")
} else {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok {
if ruleErr.ErrorCode != ErrMissingTxOut {
t.Errorf("ProcessBlock expected an %v error code but got %v", ErrMissingTxOut, ruleErr.ErrorCode)
}
} else {
t.Errorf("ProcessBlock expected a blockdag.RuleError but got %v", err)
}
}
if isDelayed {
t.Fatalf("ProcessBlock: blockWithDoubleSpendForTx1 " +
"is too far in the future")
}
if isOrphan {
t.Fatalf("ProcessBlock: blockWithDoubleSpendForTx1 got unexpectedly orphaned")
}
blockInAnticoneOfBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*wire.MsgTx{doubleSpendTx1})
if err != nil {
t.Fatalf("PrepareBlockForTest: %v", err)
}
// Check that a block will not get rejected if it has a transaction that double spends
// a transaction from its anticone.
isOrphan, isDelayed, err = dag.ProcessBlock(util.NewBlock(blockInAnticoneOfBlockWithTx1), BFNoPoWCheck)
if err != nil {
t.Fatalf("ProcessBlock: %v", err)
}
if isDelayed {
t.Fatalf("ProcessBlock: blockInAnticoneOfBlockWithTx1 " +
"is too far in the future")
}
if isOrphan {
t.Fatalf("ProcessBlock: blockInAnticoneOfBlockWithTx1 got unexpectedly orphaned")
}
}

View File

@ -293,14 +293,14 @@ func TestBlueAnticoneSizeErrors(t *testing.T) {
// Prepare a block chain with size K beginning with the genesis block
currentBlockA := dag.dagParams.GenesisBlock
for i := dagconfig.KType(0); i < dag.dagParams.K; i++ {
newBlock := prepareAndProcessBlock(t, dag, currentBlockA)
newBlock := prepareAndProcessBlockByParentMsgBlocks(t, dag, currentBlockA)
currentBlockA = newBlock
}
// Prepare another block chain with size K beginning with the genesis block
currentBlockB := dag.dagParams.GenesisBlock
for i := dagconfig.KType(0); i < dag.dagParams.K; i++ {
newBlock := prepareAndProcessBlock(t, dag, currentBlockB)
newBlock := prepareAndProcessBlockByParentMsgBlocks(t, dag, currentBlockB)
currentBlockB = newBlock
}
@ -332,11 +332,11 @@ func TestGHOSTDAGErrors(t *testing.T) {
defer teardownFunc()
// Add two child blocks to the genesis
block1 := prepareAndProcessBlock(t, dag, dag.dagParams.GenesisBlock)
block2 := prepareAndProcessBlock(t, dag, dag.dagParams.GenesisBlock)
block1 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
block2 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
// Add a child block to the previous two blocks
block3 := prepareAndProcessBlock(t, dag, block1, block2)
block3 := prepareAndProcessBlockByParentMsgBlocks(t, dag, block1, block2)
// Clear the reachability store
dag.reachabilityStore.loaded = map[daghash.Hash]*reachabilityData{}

View File

@ -15,6 +15,7 @@ import (
"sort"
"strings"
"sync"
"testing"
"github.com/kaspanet/kaspad/util/subnetworkid"
@ -279,6 +280,28 @@ func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactio
return block, nil
}
// PrepareAndProcessBlockForTest prepares a block that points to the given parent
// hashes and process it.
func PrepareAndProcessBlockForTest(t *testing.T, dag *BlockDAG, parentHashes []*daghash.Hash, transactions []*wire.MsgTx) *wire.MsgBlock {
daghash.Sort(parentHashes)
block, err := PrepareBlockForTest(dag, parentHashes, transactions)
if err != nil {
t.Fatalf("error in PrepareBlockForTest: %s", err)
}
utilBlock := util.NewBlock(block)
isOrphan, isDelayed, err := dag.ProcessBlock(utilBlock, BFNoPoWCheck)
if err != nil {
t.Fatalf("unexpected error in ProcessBlock: %s", err)
}
if isDelayed {
t.Fatalf("block is too far in the future")
}
if isOrphan {
t.Fatalf("block was unexpectedly orphan")
}
return block
}
// generateDeterministicExtraNonceForTest returns a unique deterministic extra nonce for coinbase data, in order to create unique coinbase transactions.
func generateDeterministicExtraNonceForTest() uint64 {
extraNonceForTest++

View File

@ -570,9 +570,9 @@ func TestValidateParents(t *testing.T) {
}
defer teardownFunc()
a := prepareAndProcessBlock(t, dag, dag.dagParams.GenesisBlock)
b := prepareAndProcessBlock(t, dag, a)
c := prepareAndProcessBlock(t, dag, dag.dagParams.GenesisBlock)
a := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
b := prepareAndProcessBlockByParentMsgBlocks(t, dag, a)
c := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
aNode := nodeByMsgBlock(t, dag, a)
bNode := nodeByMsgBlock(t, dag, b)

View File

@ -795,6 +795,57 @@ func TestDoubleSpends(t *testing.T) {
testPoolMembership(tc, tx2, false, true, false)
}
func TestDoubleSpendsFromDAG(t *testing.T) {
tc, spendableOuts, teardownFunc, err := newPoolHarness(t, &dagconfig.SimnetParams, 2, "TestDoubleSpendsFromDAG")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
defer teardownFunc()
harness := tc.harness
tx, err := harness.createTx(spendableOuts[0], uint64(txRelayFeeForTest), 1)
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
dag := harness.txPool.cfg.DAG
blockdag.PrepareAndProcessBlockForTest(t, dag, dag.TipHashes(), []*wire.MsgTx{tx.MsgTx()})
// Check that a transaction that double spends the DAG UTXO set is orphaned.
doubleSpendTx, err := harness.createTx(spendableOuts[0], uint64(txRelayFeeForTest), 2)
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
_, err = harness.txPool.ProcessTransaction(doubleSpendTx, true, 0)
if err != nil {
t.Fatalf("ProcessTransaction: %s", err)
}
testPoolMembership(tc, doubleSpendTx, true, false, false)
// If you send a transaction that some of its outputs exist in the DAG UTXO
// set, it won't be added to the orphan pool, and will completely get rejected
// from the mempool.
// This happens because transactions with the same ID as old transactions
// are not allowed as long as some of the old transaction outputs exist
// in the UTXO.
_, err = harness.txPool.ProcessTransaction(tx, true, 0)
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok {
var txRuleErr TxRuleError
if ok := errors.As(ruleErr.Err, &txRuleErr); ok {
if txRuleErr.RejectCode != wire.RejectDuplicate {
t.Errorf("ProcessTransaction expected an %v reject code but got %v", wire.RejectDuplicate, txRuleErr.RejectCode)
}
} else {
t.Errorf("ProcessTransaction expected a ruleErr.Err to be a TxRuleError but got %v", err)
}
} else {
t.Errorf("ProcessTransaction expected a RuleError but got %v", err)
}
testPoolMembership(tc, tx, false, false, false)
}
//TestFetchTransaction checks that FetchTransaction
//returns only transaction from the main pool and not from the orphan pool
func TestFetchTransaction(t *testing.T) {