mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-06 06:06:49 +00:00
[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:
parent
fe91b4c878
commit
d015286f65
@ -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 {
|
||||
|
@ -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: ¶ms,
|
||||
})
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -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{}
|
||||
|
@ -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++
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user