From 0ca127853d3469f77300fe340b1e4dbc2c24cbc2 Mon Sep 17 00:00:00 2001 From: Svarog Date: Wed, 20 May 2020 12:43:52 +0300 Subject: [PATCH] [NOD-974] UTXO-Commitments shouldn't include the new block's transactions (#727) * [NOD-975] Don't include block transactions inside its UTXO commitment (#711) * [NOD-975] Don't include block transactions inside its UTXO commitment. * Revert "[NOD-975] Don't include block transactions inside its UTXO commitment." This reverts commit b1a2ae66 * [NOD-975] Implement a (currently failing) TestUTXOCommitment. * [NOD-975] Remove the block's own transactions from calcMultiset. * [NOD-975] Simplify calcMultiset. * [NOD-975] Add a comment on top of selectedParentMultiset. * [NOD-975] Use pastUTXO instead of selectedParentUTXO in calcMultiset. * [NOD-975] Use selected parent's pastUTXO instead of this block's pastUTXO in calcMultiset. * [NOD-975] Extract selectedParentPastUTXO to a separate function. * [NOD-975] Remove selectedParentUTXO from pastUTXO's return values. * [NOD-975] Add txs to TestUTXOCommitment. * [NOD-975] Remove debug code. * [NOD-975] In pastUTXOMultiSet, copy the multiset to avoid modifying the original. * [NOD-975] Add a test: TestPastUTXOMultiSet. * [NOD-975] Improve TestPastUTXOMultiSet. * [NOD-976] Implement tests for UTXO commitments (#715) * [NOD-975] Don't include block transactions inside its UTXO commitment. * Revert "[NOD-975] Don't include block transactions inside its UTXO commitment." This reverts commit b1a2ae66 * [NOD-975] Implement a (currently failing) TestUTXOCommitment. * [NOD-975] Remove the block's own transactions from calcMultiset. * [NOD-975] Simplify calcMultiset. * [NOD-975] Add a comment on top of selectedParentMultiset. * [NOD-975] Use pastUTXO instead of selectedParentUTXO in calcMultiset. * [NOD-975] Use selected parent's pastUTXO instead of this block's pastUTXO in calcMultiset. * [NOD-975] Extract selectedParentPastUTXO to a separate function. * [NOD-975] Remove selectedParentUTXO from pastUTXO's return values. * [NOD-975] Add txs to TestUTXOCommitment. * [NOD-976] Generate new blockDB blocks for tests. * [NOD-976] Fix TestBlueBlockWindow. * [NOD-976] Fix TestIsKnownBlock. * [NOD-976] Fix TestGHOSTDAG. * [NOD-976] Fix TestUTXOCommitment. * [NOD-976] Remove kaka. * [NOD-990] Save utxo diffs of past UTXO (#724) * [NOD-990] Save UTXO diffs of past UTXO * [NOD-990] Check for block double spends with its past instead of building its UTXO * [NOD-990] Call resetExtraNonceForTest in TestUTXOCommitment * [NOD-990] Remove redundant functions diffFromTx and diffFromAcceptedTx * [NOD-990] Rename i->j to avoid confusion * [NOD-990] Break long lines * [NOD-990] Rename ErrDoubleSpendsWithBlockTransaction -> ErrDoubleSpendInSameBlock * [NOD-990] Make ErrDoubleSpendInSameBlock more detailed * [NOD-990] Add testProcessBlockRuleError * [NOD-990] Fix comment * [NOD-990] Add test for duplicate transactions on the same block * [NOD-990] Use pkg/errors on panic * [NOD-990] Make cloneWithoutBase method * [NOD-990] Break long lines * [NOD-990] Fix comment * [NOD-990] Fix wrong variable names * [NOD-990] Fix comment * [NOD-974] Generate new test blocks. * [NOD-974] Fix TestIsKnownBlock and TestGHOSTDAG. * [NOD-974] Fix TestUTXOCommitment. * [NOD-974] Fix comments Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com> Co-authored-by: stasatdaglabs Co-authored-by: Ori Newman --- blockdag/dag.go | 224 ++++++++++------------------ blockdag/dag_test.go | 243 +++++++++++++++++++++++++------ blockdag/error.go | 6 + blockdag/ghostdag_test.go | 4 +- blockdag/mining.go | 10 +- blockdag/testdata/blk_0_to_4.dat | Bin 2055 -> 2055 bytes blockdag/testdata/blk_3A.dat | Bin 467 -> 467 bytes blockdag/testdata/blk_3B.dat | Bin 354 -> 354 bytes blockdag/testdata/blk_3C.dat | Bin 382 -> 382 bytes blockdag/testdata/blk_3D.dat | Bin 508 -> 508 bytes blockdag/utxoset.go | 93 +----------- blockdag/utxoset_test.go | 75 ---------- blockdag/validate.go | 21 ++- mining/test_utils.go | 2 +- 14 files changed, 309 insertions(+), 369 deletions(-) diff --git a/blockdag/dag.go b/blockdag/dag.go index 8c9cf6732..35c9d5a95 100644 --- a/blockdag/dag.go +++ b/blockdag/dag.go @@ -574,7 +574,8 @@ func (dag *BlockDAG) connectBlock(node *blockNode, return nil, err } - newBlockUTXO, txsAcceptanceData, newBlockFeeData, newBlockMultiSet, err := node.verifyAndBuildUTXO(dag, block.Transactions(), fastAdd) + newBlockPastUTXO, txsAcceptanceData, newBlockFeeData, newBlockMultiSet, err := + node.verifyAndBuildUTXO(dag, block.Transactions(), fastAdd) if err != nil { var ruleErr RuleError if ok := errors.As(err, &ruleErr); ok { @@ -589,7 +590,8 @@ func (dag *BlockDAG) connectBlock(node *blockNode, } // Apply all changes to the DAG. - virtualUTXODiff, chainUpdates, err := dag.applyDAGChanges(node, newBlockUTXO, newBlockMultiSet, selectedParentAnticone) + virtualUTXODiff, chainUpdates, err := + dag.applyDAGChanges(node, newBlockPastUTXO, newBlockMultiSet, selectedParentAnticone) if err != nil { // Since all validation logic has already ran, if applyDAGChanges errors out, // this means we have a problem in the internal structure of the DAG - a problem which is @@ -606,29 +608,43 @@ func (dag *BlockDAG) connectBlock(node *blockNode, return chainUpdates, nil } -// calcMultiset returns the multiset of the UTXO of the given block with the given transactions. -func (node *blockNode) calcMultiset(dag *BlockDAG, transactions []*util.Tx, acceptanceData MultiBlockTxsAcceptanceData, selectedParentUTXO, pastUTXO UTXOSet) (*secp256k1.MultiSet, error) { - ms, err := node.pastUTXOMultiSet(dag, acceptanceData, selectedParentUTXO) +// calcMultiset returns the multiset of the past UTXO of the given block. +func (node *blockNode) calcMultiset(dag *BlockDAG, acceptanceData MultiBlockTxsAcceptanceData, + selectedParentPastUTXO UTXOSet) (*secp256k1.MultiSet, error) { + + return node.pastUTXOMultiSet(dag, acceptanceData, selectedParentPastUTXO) +} + +func (node *blockNode) pastUTXOMultiSet(dag *BlockDAG, acceptanceData MultiBlockTxsAcceptanceData, + selectedParentPastUTXO UTXOSet) (*secp256k1.MultiSet, error) { + + ms, err := node.selectedParentMultiset(dag) if err != nil { return nil, err } - for _, tx := range transactions { - ms, err = addTxToMultiset(ms, tx.MsgTx(), pastUTXO, UnacceptedBlueScore) - if err != nil { - return nil, err + for _, blockAcceptanceData := range acceptanceData { + for _, txAcceptanceData := range blockAcceptanceData.TxAcceptanceData { + if !txAcceptanceData.IsAccepted { + continue + } + + tx := txAcceptanceData.Tx.MsgTx() + + var err error + ms, err = addTxToMultiset(ms, tx, selectedParentPastUTXO, node.blueScore) + if err != nil { + return nil, err + } } } - return ms, nil } -// acceptedSelectedParentMultiset takes the multiset of the selected -// parent, replaces all the selected parent outputs' blue score with -// the block blue score and returns the result. -func (node *blockNode) acceptedSelectedParentMultiset(dag *BlockDAG, - acceptanceData MultiBlockTxsAcceptanceData) (*secp256k1.MultiSet, error) { - +// selectedParentMultiset returns the multiset of the node's selected +// parent. If the node is the genesis blockNode then it does not have +// a selected parent, in which case return a new, empty multiset. +func (node *blockNode) selectedParentMultiset(dag *BlockDAG) (*secp256k1.MultiSet, error) { if node.isGenesis() { return secp256k1.NewMultiset(), nil } @@ -638,61 +654,6 @@ func (node *blockNode) acceptedSelectedParentMultiset(dag *BlockDAG, return nil, err } - selectedParentAcceptanceData, exists := acceptanceData.FindAcceptanceData(node.selectedParent.hash) - if !exists { - return nil, errors.Errorf("couldn't find selected parent acceptance data for block %s", node) - } - for _, txAcceptanceData := range selectedParentAcceptanceData.TxAcceptanceData { - tx := txAcceptanceData.Tx - msgTx := tx.MsgTx() - isCoinbase := tx.IsCoinBase() - for i, txOut := range msgTx.TxOut { - outpoint := *wire.NewOutpoint(tx.ID(), uint32(i)) - - unacceptedEntry := NewUTXOEntry(txOut, isCoinbase, UnacceptedBlueScore) - acceptedEntry := NewUTXOEntry(txOut, isCoinbase, node.blueScore) - - var err error - ms, err = removeUTXOFromMultiset(ms, unacceptedEntry, &outpoint) - if err != nil { - return nil, err - } - - ms, err = addUTXOToMultiset(ms, acceptedEntry, &outpoint) - if err != nil { - return nil, err - } - } - } - - return ms, nil -} - -func (node *blockNode) pastUTXOMultiSet(dag *BlockDAG, acceptanceData MultiBlockTxsAcceptanceData, selectedParentUTXO UTXOSet) (*secp256k1.MultiSet, error) { - ms, err := node.acceptedSelectedParentMultiset(dag, acceptanceData) - if err != nil { - return nil, err - } - - for _, blockAcceptanceData := range acceptanceData { - if blockAcceptanceData.BlockHash.IsEqual(node.selectedParent.hash) { - continue - } - - for _, txAcceptanceData := range blockAcceptanceData.TxAcceptanceData { - if !txAcceptanceData.IsAccepted { - continue - } - - tx := txAcceptanceData.Tx.MsgTx() - - var err error - ms, err = addTxToMultiset(ms, tx, selectedParentUTXO, node.blueScore) - if err != nil { - return nil, err - } - } - } return ms, nil } @@ -1011,7 +972,8 @@ func (dag *BlockDAG) TxsAcceptedByBlockHash(blockHash *daghash.Hash) (MultiBlock // It returns the diff in the virtual block's UTXO set. // // This function MUST be called with the DAG state lock held (for writes). -func (dag *BlockDAG) applyDAGChanges(node *blockNode, newBlockUTXO UTXOSet, newBlockMultiset *secp256k1.MultiSet, selectedParentAnticone []*blockNode) ( +func (dag *BlockDAG) applyDAGChanges(node *blockNode, newBlockPastUTXO UTXOSet, + newBlockMultiset *secp256k1.MultiSet, selectedParentAnticone []*blockNode) ( virtualUTXODiff *UTXODiff, chainUpdates *chainUpdates, err error) { // Add the block to the reachability structures @@ -1022,7 +984,7 @@ func (dag *BlockDAG) applyDAGChanges(node *blockNode, newBlockUTXO UTXOSet, newB dag.multisetStore.setMultiset(node, newBlockMultiset) - if err = node.updateParents(dag, newBlockUTXO); err != nil { + if err = node.updateParents(dag, newBlockPastUTXO); err != nil { return nil, nil, errors.Wrapf(err, "failed updating parents of %s", node) } @@ -1063,26 +1025,23 @@ func (dag *BlockDAG) meldVirtualUTXO(newVirtualUTXODiffSet *DiffUTXOSet) error { return newVirtualUTXODiffSet.meldToBase() } -// applyAndVerifyBlockTransactionsToPastUTXO applies a block's transactions to its -// given past UTXO, and verifies that there are no double spends with its past. -func applyAndVerifyBlockTransactionsToPastUTXO(pastUTXO UTXOSet, blockTransactions []*util.Tx) (UTXOSet, error) { - diff := NewUTXODiff() - +// checkDoubleSpendsWithBlockPast checks that each block transaction +// has a corresponding UTXO in the block pastUTXO. +func checkDoubleSpendsWithBlockPast(pastUTXO UTXOSet, blockTransactions []*util.Tx) error { for _, tx := range blockTransactions { - txDiff, err := pastUTXO.diffFromTx(tx.MsgTx(), UnacceptedBlueScore) - if errors.Is(err, errUTXOMissingTxOut) { - return nil, ruleError(ErrMissingTxOut, err.Error()) + if tx.IsCoinBase() { + continue } - if err != nil { - return nil, err - } - diff, err = diff.WithDiff(txDiff) - if err != nil { - return nil, err + + for _, txIn := range tx.MsgTx().TxIn { + if _, ok := pastUTXO.Get(txIn.PreviousOutpoint); !ok { + return ruleError(ErrMissingTxOut, fmt.Sprintf("missing transaction "+ + "output %s in the utxo set", txIn.PreviousOutpoint)) + } } } - return pastUTXO.WithDiff(diff) + return nil } // verifyAndBuildUTXO verifies all transactions in the given block and builds its UTXO @@ -1091,7 +1050,7 @@ func applyAndVerifyBlockTransactionsToPastUTXO(pastUTXO UTXOSet, blockTransactio func (node *blockNode) verifyAndBuildUTXO(dag *BlockDAG, transactions []*util.Tx, fastAdd bool) ( newBlockUTXO UTXOSet, txsAcceptanceData MultiBlockTxsAcceptanceData, newBlockFeeData compactFeeData, multiset *secp256k1.MultiSet, err error) { - pastUTXO, selectedParentUTXO, txsAcceptanceData, err := dag.pastUTXO(node) + pastUTXO, selectedParentPastUTXO, txsAcceptanceData, err := dag.pastUTXO(node) if err != nil { return nil, nil, nil, nil, err } @@ -1106,12 +1065,7 @@ func (node *blockNode) verifyAndBuildUTXO(dag *BlockDAG, transactions []*util.Tx return nil, nil, nil, nil, err } - utxo, err := applyAndVerifyBlockTransactionsToPastUTXO(pastUTXO, transactions) - if err != nil { - return nil, nil, nil, nil, err - } - - multiset, err = node.calcMultiset(dag, transactions, txsAcceptanceData, selectedParentUTXO, pastUTXO) + multiset, err = node.calcMultiset(dag, txsAcceptanceData, selectedParentPastUTXO) if err != nil { return nil, nil, nil, nil, err } @@ -1124,7 +1078,7 @@ func (node *blockNode) verifyAndBuildUTXO(dag *BlockDAG, transactions []*util.Tx return nil, nil, nil, nil, ruleError(ErrBadUTXOCommitment, str) } - return utxo, txsAcceptanceData, feeData, multiset, nil + return pastUTXO, txsAcceptanceData, feeData, multiset, nil } // TxAcceptanceData stores a transaction together with an indication @@ -1180,34 +1134,33 @@ func (node *blockNode) fetchBlueBlocks() ([]*util.Block, error) { return blueBlocks, nil } -// applyBlueBlocks adds all transactions in the blue blocks to the selectedParent's UTXO set +// applyBlueBlocks adds all transactions in the blue blocks to the selectedParent's past UTXO set // Purposefully ignoring failures - these are just unaccepted transactions // Writing down which transactions were accepted or not in txsAcceptanceData -func (node *blockNode) applyBlueBlocks(acceptedSelectedParentUTXO UTXOSet, selectedParentAcceptanceData []TxAcceptanceData, blueBlocks []*util.Block) ( +func (node *blockNode) applyBlueBlocks(selectedParentPastUTXO UTXOSet, blueBlocks []*util.Block) ( pastUTXO UTXOSet, multiBlockTxsAcceptanceData MultiBlockTxsAcceptanceData, err error) { - pastUTXO = acceptedSelectedParentUTXO - multiBlockTxsAcceptanceData = MultiBlockTxsAcceptanceData{BlockTxsAcceptanceData{ - BlockHash: *node.selectedParent.hash, - TxAcceptanceData: selectedParentAcceptanceData, - }} + pastUTXO = selectedParentPastUTXO.(*DiffUTXOSet).cloneWithoutBase() + multiBlockTxsAcceptanceData = make(MultiBlockTxsAcceptanceData, len(blueBlocks)) // Add blueBlocks to multiBlockTxsAcceptanceData in topological order. This // is so that anyone who iterates over it would process blocks (and transactions) // in their order of appearance in the DAG. - // We skip the selected parent, because we calculated its UTXO in acceptSelectedParentTransactions. - for i := 1; i < len(blueBlocks); i++ { + for i := 0; i < len(blueBlocks); i++ { blueBlock := blueBlocks[i] transactions := blueBlock.Transactions() blockTxsAcceptanceData := BlockTxsAcceptanceData{ BlockHash: *blueBlock.Hash(), TxAcceptanceData: make([]TxAcceptanceData, len(transactions)), } - for i, tx := range blueBlock.Transactions() { + isSelectedParent := i == 0 + + for j, tx := range blueBlock.Transactions() { var isAccepted bool + // Coinbase transaction outputs are added to the UTXO // only if they are in the selected parent chain. - if tx.IsCoinBase() { + if !isSelectedParent && tx.IsCoinBase() { isAccepted = false } else { isAccepted, err = pastUTXO.AddTx(tx.MsgTx(), node.blueScore) @@ -1215,9 +1168,9 @@ func (node *blockNode) applyBlueBlocks(acceptedSelectedParentUTXO UTXOSet, selec return nil, nil, err } } - blockTxsAcceptanceData.TxAcceptanceData[i] = TxAcceptanceData{Tx: tx, IsAccepted: isAccepted} + blockTxsAcceptanceData.TxAcceptanceData[j] = TxAcceptanceData{Tx: tx, IsAccepted: isAccepted} } - multiBlockTxsAcceptanceData = append(multiBlockTxsAcceptanceData, blockTxsAcceptanceData) + multiBlockTxsAcceptanceData[i] = blockTxsAcceptanceData } return pastUTXO, multiBlockTxsAcceptanceData, nil @@ -1248,7 +1201,7 @@ func (node *blockNode) updateParentsDiffs(dag *BlockDAG, newBlockUTXO UTXOSet) e return err } if diffChild == nil { - parentUTXO, err := dag.restoreUTXO(parent) + parentPastUTXO, err := dag.restorePastUTXO(parent) if err != nil { return err } @@ -1256,7 +1209,7 @@ func (node *blockNode) updateParentsDiffs(dag *BlockDAG, newBlockUTXO UTXOSet) e if err != nil { return err } - diff, err := newBlockUTXO.diffFrom(parentUTXO) + diff, err := newBlockUTXO.diffFrom(parentPastUTXO) if err != nil { return err } @@ -1274,12 +1227,13 @@ func (node *blockNode) updateParentsDiffs(dag *BlockDAG, newBlockUTXO UTXOSet) e // To save traversals over the blue blocks, it also returns the transaction acceptance data for // all blue blocks func (dag *BlockDAG) pastUTXO(node *blockNode) ( - pastUTXO, selectedParentUTXO UTXOSet, bluesTxsAcceptanceData MultiBlockTxsAcceptanceData, err error) { + pastUTXO, selectedParentPastUTXO UTXOSet, bluesTxsAcceptanceData MultiBlockTxsAcceptanceData, err error) { if node.isGenesis() { - return genesisPastUTXO(dag.virtual), NewFullUTXOSet(), MultiBlockTxsAcceptanceData{}, nil + return genesisPastUTXO(dag.virtual), nil, MultiBlockTxsAcceptanceData{}, nil } - selectedParentUTXO, err = dag.restoreUTXO(node.selectedParent) + + selectedParentPastUTXO, err = dag.restorePastUTXO(node.selectedParent) if err != nil { return nil, nil, nil, err } @@ -1289,46 +1243,16 @@ func (dag *BlockDAG) pastUTXO(node *blockNode) ( return nil, nil, nil, err } - selectedParent := blueBlocks[0] - acceptedSelectedParentUTXO, selectedParentAcceptanceData, err := node.acceptSelectedParentTransactions(selectedParent, selectedParentUTXO) + pastUTXO, bluesTxsAcceptanceData, err = node.applyBlueBlocks(selectedParentPastUTXO, blueBlocks) if err != nil { return nil, nil, nil, err } - pastUTXO, bluesTxsAcceptanceData, err = node.applyBlueBlocks(acceptedSelectedParentUTXO, selectedParentAcceptanceData, blueBlocks) - if err != nil { - return nil, nil, nil, err - } - - return pastUTXO, selectedParentUTXO, bluesTxsAcceptanceData, nil + return pastUTXO, selectedParentPastUTXO, bluesTxsAcceptanceData, nil } -func (node *blockNode) acceptSelectedParentTransactions(selectedParent *util.Block, selectedParentUTXO UTXOSet) (acceptedSelectedParentUTXO UTXOSet, txAcceptanceData []TxAcceptanceData, err error) { - diff := NewUTXODiff() - txAcceptanceData = make([]TxAcceptanceData, len(selectedParent.Transactions())) - for i, tx := range selectedParent.Transactions() { - txAcceptanceData[i] = TxAcceptanceData{ - Tx: tx, - IsAccepted: true, - } - acceptanceDiff, err := selectedParentUTXO.diffFromAcceptedTx(tx.MsgTx(), node.blueScore) - if err != nil { - return nil, nil, err - } - diff, err = diff.WithDiff(acceptanceDiff) - if err != nil { - return nil, nil, err - } - } - acceptedSelectedParentUTXO, err = selectedParentUTXO.WithDiff(diff) - if err != nil { - return nil, nil, err - } - return acceptedSelectedParentUTXO, txAcceptanceData, nil -} - -// restoreUTXO restores the UTXO of a given block from its diff -func (dag *BlockDAG) restoreUTXO(node *blockNode) (UTXOSet, error) { +// restorePastUTXO restores the UTXO of a given block from its diff +func (dag *BlockDAG) restorePastUTXO(node *blockNode) (UTXOSet, error) { stack := []*blockNode{} // Iterate over the chain of diff-childs from node till virtual and add them @@ -1369,11 +1293,11 @@ func (dag *BlockDAG) restoreUTXO(node *blockNode) (UTXOSet, error) { // updateTipsUTXO builds and applies new diff UTXOs for all the DAG's tips func updateTipsUTXO(dag *BlockDAG, virtualUTXO UTXOSet) error { for tip := range dag.virtual.parents { - tipUTXO, err := dag.restoreUTXO(tip) + tipPastUTXO, err := dag.restorePastUTXO(tip) if err != nil { return err } - diff, err := virtualUTXO.diffFrom(tipUTXO) + diff, err := virtualUTXO.diffFrom(tipPastUTXO) if err != nil { return err } diff --git a/blockdag/dag_test.go b/blockdag/dag_test.go index a0933b560..a1bbee2b0 100644 --- a/blockdag/dag_test.go +++ b/blockdag/dag_test.go @@ -6,11 +6,13 @@ package blockdag import ( "fmt" + "github.com/kaspanet/go-secp256k1" "github.com/kaspanet/kaspad/dbaccess" "github.com/pkg/errors" "math" "os" "path/filepath" + "reflect" "testing" "time" @@ -205,7 +207,7 @@ func TestIsKnownBlock(t *testing.T) { {hash: dagconfig.SimnetParams.GenesisHash.String(), want: true}, // Block 3b should be present (as a second child of Block 2). - {hash: "7f2bea5aa4122aed2a542447133e73da6b6f6190ec34c061be70d4576cdd7498", want: true}, + {hash: "48a752afbe36ad66357f751f8dee4f75665d24e18f644d83a3409b398405b46b", want: true}, // Block 100000 should be present (as an orphan). {hash: "65b20b048a074793ebfd1196e49341c8d194dabfc6b44a4fd0c607406e122baf", want: true}, @@ -1124,6 +1126,23 @@ func TestIsDAGCurrentMaxDiff(t *testing.T) { } } +func testProcessBlockRuleError(t *testing.T, dag *BlockDAG, block *wire.MsgBlock, expectedRuleErr error) { + isOrphan, isDelayed, err := dag.ProcessBlock(util.NewBlock(block), BFNoPoWCheck) + + err = checkRuleError(err, expectedRuleErr) + if err != nil { + t.Errorf("checkRuleError: %s", err) + } + + if isDelayed { + t.Fatalf("ProcessBlock: block " + + "is too far in the future") + } + if isOrphan { + t.Fatalf("ProcessBlock: block got unexpectedly orphaned") + } +} + func TestDoubleSpends(t *testing.T) { params := dagconfig.SimnetParams params.BlockCoinbaseMaturity = 0 @@ -1176,26 +1195,7 @@ func TestDoubleSpends(t *testing.T) { } 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") - } + testProcessBlockRuleError(t, dag, anotherBlockWithTx1, ruleError(ErrOverwriteTx, "")) // Check that a block will be rejected if it has a transaction that double spends // a transaction from its past. @@ -1212,26 +1212,7 @@ func TestDoubleSpends(t *testing.T) { } 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") - } + testProcessBlockRuleError(t, dag, blockWithDoubleSpendForTx1, ruleError(ErrMissingTxOut, "")) blockInAnticoneOfBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*wire.MsgTx{doubleSpendTx1}) if err != nil { @@ -1240,15 +1221,181 @@ func TestDoubleSpends(t *testing.T) { // 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) + testProcessBlockRuleError(t, dag, blockInAnticoneOfBlockWithTx1, nil) + + // Check that a block will be rejected if it has two transactions that spend the same UTXO. + blockWithDoubleSpendWithItself, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, nil) if err != nil { - t.Fatalf("ProcessBlock: %v", err) + t.Fatalf("PrepareBlockForTest: %v", err) } - if isDelayed { - t.Fatalf("ProcessBlock: blockInAnticoneOfBlockWithTx1 " + - "is too far in the future") + + // Manually add tx1 and doubleSpendTx1. + blockWithDoubleSpendWithItself.Transactions = append(blockWithDoubleSpendWithItself.Transactions, tx1, doubleSpendTx1) + blockWithDoubleSpendWithItselfUtilTxs := make([]*util.Tx, len(blockWithDoubleSpendWithItself.Transactions)) + for i, tx := range blockWithDoubleSpendWithItself.Transactions { + blockWithDoubleSpendWithItselfUtilTxs[i] = util.NewTx(tx) } - if isOrphan { - t.Fatalf("ProcessBlock: blockInAnticoneOfBlockWithTx1 got unexpectedly orphaned") + blockWithDoubleSpendWithItself.Header.HashMerkleRoot = BuildHashMerkleTreeStore(blockWithDoubleSpendWithItselfUtilTxs).Root() + + testProcessBlockRuleError(t, dag, blockWithDoubleSpendWithItself, ruleError(ErrDoubleSpendInSameBlock, "")) + + // Check that a block will be rejected if it has the same transaction twice. + blockWithDuplicateTransaction, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, nil) + if err != nil { + t.Fatalf("PrepareBlockForTest: %v", err) + } + + // Manually add tx1 twice. + blockWithDuplicateTransaction.Transactions = append(blockWithDuplicateTransaction.Transactions, tx1, tx1) + blockWithDuplicateTransactionUtilTxs := make([]*util.Tx, len(blockWithDuplicateTransaction.Transactions)) + for i, tx := range blockWithDuplicateTransaction.Transactions { + blockWithDuplicateTransactionUtilTxs[i] = util.NewTx(tx) + } + blockWithDuplicateTransaction.Header.HashMerkleRoot = BuildHashMerkleTreeStore(blockWithDuplicateTransactionUtilTxs).Root() + testProcessBlockRuleError(t, dag, blockWithDuplicateTransaction, ruleError(ErrDuplicateTx, "")) +} + +func TestUTXOCommitment(t *testing.T) { + // Create a new database and dag instance to run tests against. + params := dagconfig.DevnetParams + params.BlockCoinbaseMaturity = 0 + dag, teardownFunc, err := DAGSetup("TestUTXOCommitment", true, Config{ + DAGParams: ¶ms, + }) + if err != nil { + t.Fatalf("TestUTXOCommitment: Failed to setup dag instance: %v", err) + } + defer teardownFunc() + + resetExtraNonceForTest() + + createTx := func(txToSpend *wire.MsgTx) *wire.MsgTx { + scriptPubKey, err := txscript.PayToScriptHashScript(OpTrueScript) + if err != nil { + t.Fatalf("TestUTXOCommitment: failed to build script pub key: %s", err) + } + signatureScript, err := txscript.PayToScriptHashSignatureScript(OpTrueScript, nil) + if err != nil { + t.Fatalf("TestUTXOCommitment: failed to build signature script: %s", err) + } + txIn := &wire.TxIn{ + PreviousOutpoint: wire.Outpoint{TxID: *txToSpend.TxID(), Index: 0}, + SignatureScript: signatureScript, + Sequence: wire.MaxTxInSequenceNum, + } + txOut := &wire.TxOut{ + ScriptPubKey: scriptPubKey, + Value: uint64(1), + } + return wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}) + } + + // Build the following DAG: + // G <- A <- B <- D + // <- C <- + genesis := params.GenesisBlock + + // Block A: + blockA := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{genesis.BlockHash()}, nil) + + // Block B: + blockB := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockA.BlockHash()}, nil) + + // Block C: + txSpendBlockACoinbase := createTx(blockA.Transactions[0]) + blockCTxs := []*wire.MsgTx{txSpendBlockACoinbase} + blockC := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockA.BlockHash()}, blockCTxs) + + // Block D: + txSpendTxInBlockC := createTx(txSpendBlockACoinbase) + blockDTxs := []*wire.MsgTx{txSpendTxInBlockC} + blockD := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockB.BlockHash(), blockC.BlockHash()}, blockDTxs) + + // Get the pastUTXO of blockD + blockNodeD := dag.index.LookupNode(blockD.BlockHash()) + if blockNodeD == nil { + t.Fatalf("TestUTXOCommitment: blockNode for block D not found") + } + blockDPastUTXO, _, _, _ := dag.pastUTXO(blockNodeD) + blockDPastDiffUTXOSet := blockDPastUTXO.(*DiffUTXOSet) + + // Build a Multiset for block D + multiset := secp256k1.NewMultiset() + for outpoint, entry := range blockDPastDiffUTXOSet.base.utxoCollection { + var err error + multiset, err = addUTXOToMultiset(multiset, entry, &outpoint) + if err != nil { + t.Fatalf("TestUTXOCommitment: addUTXOToMultiset unexpectedly failed") + } + } + for outpoint, entry := range blockDPastDiffUTXOSet.UTXODiff.toAdd { + var err error + multiset, err = addUTXOToMultiset(multiset, entry, &outpoint) + if err != nil { + t.Fatalf("TestUTXOCommitment: addUTXOToMultiset unexpectedly failed") + } + } + for outpoint, entry := range blockDPastDiffUTXOSet.UTXODiff.toRemove { + var err error + multiset, err = removeUTXOFromMultiset(multiset, entry, &outpoint) + if err != nil { + t.Fatalf("TestUTXOCommitment: removeUTXOFromMultiset unexpectedly failed") + } + } + + // Turn the multiset into a UTXO commitment + utxoCommitment := daghash.Hash(*multiset.Finalize()) + + // Make sure that the two commitments are equal + if !utxoCommitment.IsEqual(blockNodeD.utxoCommitment) { + t.Fatalf("TestUTXOCommitment: calculated UTXO commitment and "+ + "actual UTXO commitment don't match. Want: %s, got: %s", + utxoCommitment, blockNodeD.utxoCommitment) + } +} + +func TestPastUTXOMultiSet(t *testing.T) { + // Create a new database and dag instance to run tests against. + params := dagconfig.SimnetParams + dag, teardownFunc, err := DAGSetup("TestPastUTXOMultiSet", true, Config{ + DAGParams: ¶ms, + }) + if err != nil { + t.Fatalf("TestPastUTXOMultiSet: Failed to setup dag instance: %v", err) + } + defer teardownFunc() + + // Build a short chain + genesis := params.GenesisBlock + blockA := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{genesis.BlockHash()}, nil) + blockB := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockA.BlockHash()}, nil) + blockC := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockB.BlockHash()}, nil) + + // Take blockC's selectedParentMultiset + blockNodeC := dag.index.LookupNode(blockC.BlockHash()) + if blockNodeC == nil { + t.Fatalf("TestPastUTXOMultiSet: blockNode for blockC not found") + } + blockCSelectedParentMultiset, err := blockNodeC.selectedParentMultiset(dag) + if err != nil { + t.Fatalf("TestPastUTXOMultiSet: selectedParentMultiset unexpectedly failed: %s", err) + } + + // Copy the multiset + blockCSelectedParentMultisetCopy := *blockCSelectedParentMultiset + blockCSelectedParentMultiset = &blockCSelectedParentMultisetCopy + + // Add a block on top of blockC + PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockC.BlockHash()}, nil) + + // Get blockC's selectedParentMultiset again + blockCSelectedParentMultiSetAfterAnotherBlock, err := blockNodeC.selectedParentMultiset(dag) + if err != nil { + t.Fatalf("TestPastUTXOMultiSet: selectedParentMultiset unexpectedly failed: %s", err) + } + + // Make sure that blockC's selectedParentMultiset had not changed + if !reflect.DeepEqual(blockCSelectedParentMultiset, blockCSelectedParentMultiSetAfterAnotherBlock) { + t.Fatalf("TestPastUTXOMultiSet: selectedParentMultiset appears to have changed") } } diff --git a/blockdag/error.go b/blockdag/error.go index 977a4e77a..cc86f7bb6 100644 --- a/blockdag/error.go +++ b/blockdag/error.go @@ -103,6 +103,11 @@ const ( // either does not exist or has already been spent. ErrMissingTxOut + // ErrDoubleSpendInSameBlock indicates a transaction + // that spends an output that was already spent by another + // transaction in the same block. + ErrDoubleSpendInSameBlock + // ErrUnfinalizedTx indicates a transaction has not been finalized. // A valid block may only contain finalized transactions. ErrUnfinalizedTx @@ -227,6 +232,7 @@ var errorCodeStrings = map[ErrorCode]string{ ErrDuplicateTxInputs: "ErrDuplicateTxInputs", ErrBadTxInput: "ErrBadTxInput", ErrMissingTxOut: "ErrMissingTxOut", + ErrDoubleSpendInSameBlock: "ErrDoubleSpendInSameBlock", ErrUnfinalizedTx: "ErrUnfinalizedTx", ErrDuplicateTx: "ErrDuplicateTx", ErrOverwriteTx: "ErrOverwriteTx", diff --git a/blockdag/ghostdag_test.go b/blockdag/ghostdag_test.go index 1770975a7..985ffabd0 100644 --- a/blockdag/ghostdag_test.go +++ b/blockdag/ghostdag_test.go @@ -33,7 +33,7 @@ func TestGHOSTDAG(t *testing.T) { }{ { k: 3, - expectedReds: []string{"F", "G", "H", "I", "O", "Q"}, + expectedReds: []string{"F", "G", "H", "I", "N", "O"}, dagData: []*testBlockData{ { parents: []string{"A"}, @@ -166,7 +166,7 @@ func TestGHOSTDAG(t *testing.T) { id: "T", expectedScore: 13, expectedSelectedParent: "S", - expectedBlues: []string{"S", "P", "N"}, + expectedBlues: []string{"S", "P", "Q"}, }, }, }, diff --git a/blockdag/mining.go b/blockdag/mining.go index 9b7ef095d..83fe7c1b6 100644 --- a/blockdag/mining.go +++ b/blockdag/mining.go @@ -38,7 +38,7 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*wire.MsgBlock, er msgBlock.AddTransaction(tx.MsgTx()) } - multiset, err := dag.NextBlockMultiset(transactions) + multiset, err := dag.NextBlockMultiset() if err != nil { return nil, err } @@ -57,16 +57,16 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*wire.MsgBlock, er } // NextBlockMultiset returns the multiset of an assumed next block -// built on top of the current tips, with the given transactions. +// built on top of the current tips. // // This function MUST be called with the DAG state lock held (for reads). -func (dag *BlockDAG) NextBlockMultiset(transactions []*util.Tx) (*secp256k1.MultiSet, error) { - pastUTXO, selectedParentUTXO, txsAcceptanceData, err := dag.pastUTXO(&dag.virtual.blockNode) +func (dag *BlockDAG) NextBlockMultiset() (*secp256k1.MultiSet, error) { + _, selectedParentPastUTXO, txsAcceptanceData, err := dag.pastUTXO(&dag.virtual.blockNode) if err != nil { return nil, err } - return dag.virtual.blockNode.calcMultiset(dag, transactions, txsAcceptanceData, selectedParentUTXO, pastUTXO) + return dag.virtual.blockNode.calcMultiset(dag, txsAcceptanceData, selectedParentPastUTXO) } // CoinbasePayloadExtraData returns coinbase payload extra data parameter diff --git a/blockdag/testdata/blk_0_to_4.dat b/blockdag/testdata/blk_0_to_4.dat index c539d6e734f0999821da7044b3ced818dcdea4f7..d7d516eba16f02ca09dadb513db0fd51c4ec50db 100644 GIT binary patch delta 610 zcmZn{XcyQplX0S<0P~)a+BY)NGcGrYVvQkpvlXbkJszy z_HzAScEr!*gT(INOmEgV1nS-?I)BLKwh?FVzpP|d-z6JTtv0t7PqrwY*Y2JYRNZ~_ zo0bGY3X{N$I?`I@#=89C3>xbl0WW$do|mYQE~D+7AXZnt^hl?iq%g+q4)lM-nym> znLQ>d%pZRC8-Fz7=91YHt`nPmAguTQ>B+m$8v!wQijC#E2t|iu+YkH=% zYA5PkRW`lsJ1Os_`&9n+Z3*r53Pg410>Z?9&zcl@M z?Ys%q>n`v&e?0nDC}R2a0C@}JQ&tL-Wtb%u2ss1j!vzYab>>@yvX!?mCFz}-(EY_^ XiRfF2hSm3*iqt-+Ondb*1LQjZiPsiZ delta 659 zcmZn{XcyQplTo*3qxmd-YK3VRr^@U@`PpBM~}(JbzeJb$X*tWV*rBx z|LYZ4CLd$G>TuZP%F+FwmYtxtiW(FKmH6DPU8%aD>vWPDpCT)XAF zwc@XC_r)T)AHtQkDZjmOW&eiPJl|{Fbtd0tQiyt#a;r)1Hvg_y&!jKL9Z>S#$~rlz z$a04un~aQd+?UKLa!A&KtOo%mMj*jR$ok2@*@7l7XFgspbr`FHK+91+y>zrluBl zX|I7$Y~$izvhTwUKK%E&d|>;_JAC|$&nlJGO^MuJq01^gCrakmnbCz`!8DSYvT&&!t(C6LPjVm-EIhsF@@aCa1ND ztxvRR)@ql7a|)%I0-GPiPtRTSaxR7bqlW%SSa$oqTYc)oGUEyA6pSCSMbJ&(}KJIZ0=R L^6OPXAm0H1Rd_H4 diff --git a/blockdag/testdata/blk_3A.dat b/blockdag/testdata/blk_3A.dat index 5b83736e1aac2756b13112dfd89b269ccc344158..0251c8fa23535176126a31a2aa35cdbed9f5e32d 100644 GIT binary patch delta 232 zcmcc2e3`jk_WK>%(~JxtAi$`j+spNT*%3dJ4-&h7Grd{g5U6{n==>p@+eVzd|FV)< zT|B*iyf9W|a(sBZuU6!7tGwTXio5q+Jx|B^WN6m>5xKQ_|C%(0;=k1i{Fhp|w&io5 zE$)hLuRHfP%(~JxtAiyYf(1rQC&%yceAJ^?KmD=!A{PBV_0(s1HzPn6to@LJ^ zxOc9^2jP3xRyL(G&eXew)U#g9p87pq^~vq8Z>CO|6LbI4UIU@n#>Ky6--jD~`0sQ1 z!1kAS`1lu}RVu5S61l%Z_hjJF#HsrPZ?2GBy84ID3)49}_}({5Xgz(~JNKlu<4VTB ziM>*)gdFi72pA^!F&a+%o2Bq1%vfZ4%C`JYl5Mb2N?dAHv?1-Pq2Z`Okncl2#2-Lk(bpDXdZ6nU!e_6?_ z73X3zo)k|oyu9(M&D7gfC)U*7n0+DPqc-Ou7H^fd*T%Ot?_ZO~Q2e(#f&Wqq*S37_ zv&CJ}?RDqghWzc=dGP<5z>H4My8dG4mzJ*Qc`QwZ7q8|Ew_BpuDk%Bm{#KhyX0k|evN&o-= delta 186 zcmaFF^oXfm_WK>%C`JYl5MY!#=)(Nn=ivPKkL&iAN^STl{&>L|fjs6p-(4m+&$8zd z)KEGy+tMM+eYnAg|2~%w zY=3!&kALx5rLwvyk^3ujTfIYn%=ztVH1}oV&vRFo@@u`~yWa0~>2AZ?%j*{HPrbQz OVy~0}AxBI+J{thN09&H~ diff --git a/blockdag/testdata/blk_3C.dat b/blockdag/testdata/blk_3C.dat index c9fc086c3e73c082d59c7738e95d8b791900992e..7c2387cd9335b52946211de6ca52901d3eb0c94e 100644 GIT binary patch delta 198 zcmV;%06G8u0{#Mj9rxTlb^!nY0000G0xT_!3je0WPBicsyZZv{uYgf4+;Y#tJliq} zjs9w51-NR13i22&*e$n&JO|k*y2?0Fk;_ARvw3-wl3(&}ob`Bm?mIk2CQy3kn>(S1es?z*dd_%abwzO!e_t AN&o-= delta 198 zcmV;%06G8u0{#Mj9rxTlb^!nY0000G0vf?Y1NTh9pI`B=zjYe0`4{7$%n)t^ocBbK zLz+Jd5yLdp#lP~W&l-VzPW?3GA^Uwya4|B_2-T62Meb-C8W2>SdFPVjm${jfwLtE% zSdO6pPf2!Yt^L#96qk#$W=h9!+Z;FEz*nL$@_aXl)lt?Cix<41bLC&y7e__3roNGp z9Fd>|k*y2?0g<{`Ammmv6qi~0;=xdoegkr?tJ6bn(J-z1SP*>&XH{EOxsx&hOddyF AjQ{`u diff --git a/blockdag/testdata/blk_3D.dat b/blockdag/testdata/blk_3D.dat index e857dd182bd6a2197d8b0df490629b262dddc527..f6bd64d5e2c51ea91f2b33215a42fe0c4a69c896 100644 GIT binary patch delta 249 zcmeyv{D(PL_WK>%FN_QzAi$`j+spNT*%3dJ4-&h7Grd{g5U6{n==>p@+eVzd|FV)< z6lZNV4C_~lQ0U%7UipUirL zh}AbPJvfw~yv#uIWN%kBJKNhoAk%AC0)VWcGyX j#AY7|>-~RvvJhin2q8rvM>8=7{)Yl$lrv10WOM}p9UF0| delta 249 zcmeyv{D(PL_WK>%FN_QzAiyYf(1rQC&%yceAJ^?KmD=!A{PBV_0(s1HzPn6to@LJ^ z7!zmZVEB*4({Q5B7L6Pawo@$L$8<`<#4gPU-ZJxZ#Cose1-E6)?;QwVWbmoRyyI%% zwQlh}3yYt|-w=0q-Lh=o#6p>g4J;FTQx%?s8H-Gh{PpNyz@$3nqP43pJLg?ASo=Fd jpq4#7G&*$WWFf}D5JHMTj%H#E{0{}hC})@~$><6IEJ<&( diff --git a/blockdag/utxoset.go b/blockdag/utxoset.go index eb5a134c2..bab9a400a 100644 --- a/blockdag/utxoset.go +++ b/blockdag/utxoset.go @@ -399,78 +399,11 @@ type UTXOSet interface { fmt.Stringer diffFrom(other UTXOSet) (*UTXODiff, error) WithDiff(utxoDiff *UTXODiff) (UTXOSet, error) - diffFromTx(tx *wire.MsgTx, acceptingBlueScore uint64) (*UTXODiff, error) - diffFromAcceptedTx(tx *wire.MsgTx, acceptingBlueScore uint64) (*UTXODiff, error) AddTx(tx *wire.MsgTx, blockBlueScore uint64) (ok bool, err error) clone() UTXOSet Get(outpoint wire.Outpoint) (*UTXOEntry, bool) } -var errUTXOMissingTxOut = errors.New("missing transaction output in the utxo set") - -// diffFromTx is a common implementation for diffFromTx, that works -// for both diff-based and full UTXO sets -// Returns a diff that is equivalent to provided transaction, -// or an error if provided transaction is not valid in the context of this UTXOSet -func diffFromTx(u UTXOSet, tx *wire.MsgTx, acceptingBlueScore uint64) (*UTXODiff, error) { - diff := NewUTXODiff() - isCoinbase := tx.IsCoinBase() - if !isCoinbase { - for _, txIn := range tx.TxIn { - if entry, ok := u.Get(txIn.PreviousOutpoint); ok { - err := diff.RemoveEntry(txIn.PreviousOutpoint, entry) - if err != nil { - return nil, err - } - } else { - return nil, errors.Wrapf(errUTXOMissingTxOut, "Transaction %s is invalid because it spends "+ - "outpoint %s that is not in utxo set", tx.TxID(), txIn.PreviousOutpoint) - } - } - } - for i, txOut := range tx.TxOut { - entry := NewUTXOEntry(txOut, isCoinbase, acceptingBlueScore) - outpoint := *wire.NewOutpoint(tx.TxID(), uint32(i)) - err := diff.AddEntry(outpoint, entry) - if err != nil { - return nil, err - } - } - return diff, nil -} - -// diffFromAcceptedTx is a common implementation for diffFromAcceptedTx, that works -// for both diff-based and full UTXO sets. -// Returns a diff that replaces an entry's blockBlueScore with the given acceptingBlueScore. -// Returns an error if the provided transaction's entry is not valid in the context -// of this UTXOSet. -func diffFromAcceptedTx(u UTXOSet, tx *wire.MsgTx, acceptingBlueScore uint64) (*UTXODiff, error) { - diff := NewUTXODiff() - isCoinbase := tx.IsCoinBase() - for i, txOut := range tx.TxOut { - // Fetch any unaccepted transaction - existingOutpoint := *wire.NewOutpoint(tx.TxID(), uint32(i)) - existingEntry, ok := u.Get(existingOutpoint) - if !ok { - return nil, errors.Errorf("cannot accept outpoint %s because it doesn't exist in the given UTXO", existingOutpoint) - } - - // Remove unaccepted entries - err := diff.RemoveEntry(existingOutpoint, existingEntry) - if err != nil { - return nil, err - } - - // Add new entries with their accepting blue score - newEntry := NewUTXOEntry(txOut, isCoinbase, acceptingBlueScore) - err = diff.AddEntry(existingOutpoint, newEntry) - if err != nil { - return nil, err - } - } - return diff, nil -} - // FullUTXOSet represents a full list of transaction outputs and their values type FullUTXOSet struct { utxoCollection @@ -544,12 +477,6 @@ func (fus *FullUTXOSet) AddTx(tx *wire.MsgTx, blueScore uint64) (isAccepted bool return true, nil } -// diffFromTx returns a diff that is equivalent to provided transaction, -// or an error if provided transaction is not valid in the context of this UTXOSet -func (fus *FullUTXOSet) diffFromTx(tx *wire.MsgTx, acceptingBlueScore uint64) (*UTXODiff, error) { - return diffFromTx(fus, tx, acceptingBlueScore) -} - func (fus *FullUTXOSet) containsInputs(tx *wire.MsgTx) bool { for _, txIn := range tx.TxIn { outpoint := *wire.NewOutpoint(&txIn.PreviousOutpoint.TxID, txIn.PreviousOutpoint.Index) @@ -561,10 +488,6 @@ func (fus *FullUTXOSet) containsInputs(tx *wire.MsgTx) bool { return true } -func (fus *FullUTXOSet) diffFromAcceptedTx(tx *wire.MsgTx, acceptingBlueScore uint64) (*UTXODiff, error) { - return diffFromAcceptedTx(fus, tx, acceptingBlueScore) -} - // clone returns a clone of this utxoSet func (fus *FullUTXOSet) clone() UTXOSet { return &FullUTXOSet{utxoCollection: fus.utxoCollection.clone()} @@ -690,16 +613,6 @@ func (dus *DiffUTXOSet) meldToBase() error { return nil } -// diffFromTx returns a diff that is equivalent to provided transaction, -// or an error if provided transaction is not valid in the context of this UTXOSet -func (dus *DiffUTXOSet) diffFromTx(tx *wire.MsgTx, acceptingBlueScore uint64) (*UTXODiff, error) { - return diffFromTx(dus, tx, acceptingBlueScore) -} - -func (dus *DiffUTXOSet) diffFromAcceptedTx(tx *wire.MsgTx, acceptingBlueScore uint64) (*UTXODiff, error) { - return diffFromAcceptedTx(dus, tx, acceptingBlueScore) -} - func (dus *DiffUTXOSet) String() string { return fmt.Sprintf("{Base: %s, To Add: %s, To Remove: %s}", dus.base, dus.UTXODiff.toAdd, dus.UTXODiff.toRemove) } @@ -709,6 +622,12 @@ func (dus *DiffUTXOSet) clone() UTXOSet { return NewDiffUTXOSet(dus.base.clone().(*FullUTXOSet), dus.UTXODiff.clone()) } +// cloneWithoutBase returns a *DiffUTXOSet with same +// base as this *DiffUTXOSet and a cloned diff. +func (dus *DiffUTXOSet) cloneWithoutBase() UTXOSet { + return NewDiffUTXOSet(dus.base, dus.UTXODiff.clone()) +} + // Get returns the UTXOEntry associated with provided outpoint in this UTXOSet. // Returns false in second output if this UTXOEntry was not found func (dus *DiffUTXOSet) Get(outpoint wire.Outpoint) (*UTXOEntry, bool) { diff --git a/blockdag/utxoset_test.go b/blockdag/utxoset_test.go index c2f2d4ad6..95c99daac 100644 --- a/blockdag/utxoset_test.go +++ b/blockdag/utxoset_test.go @@ -1110,81 +1110,6 @@ testLoop: } } -func TestDiffFromTx(t *testing.T) { - fus := &FullUTXOSet{ - utxoCollection: utxoCollection{}, - } - - txID0, _ := daghash.NewTxIDFromStr("0000000000000000000000000000000000000000000000000000000000000000") - txIn0 := &wire.TxIn{SignatureScript: []byte{}, PreviousOutpoint: wire.Outpoint{TxID: *txID0, Index: math.MaxUint32}, Sequence: 0} - txOut0 := &wire.TxOut{ScriptPubKey: []byte{0}, Value: 10} - cbTx := wire.NewSubnetworkMsgTx(1, []*wire.TxIn{txIn0}, []*wire.TxOut{txOut0}, subnetworkid.SubnetworkIDCoinbase, 0, nil) - if isAccepted, err := fus.AddTx(cbTx, 1); err != nil { - t.Fatalf("AddTx unexpectedly failed. Error: %s", err) - } else if !isAccepted { - t.Fatalf("AddTx unexpectedly didn't add tx %s", cbTx.TxID()) - } - acceptingBlueScore := uint64(2) - cbOutpoint := wire.Outpoint{TxID: *cbTx.TxID(), Index: 0} - txIns := []*wire.TxIn{{ - PreviousOutpoint: cbOutpoint, - SignatureScript: nil, - Sequence: wire.MaxTxInSequenceNum, - }} - txOuts := []*wire.TxOut{{ - ScriptPubKey: OpTrueScript, - Value: uint64(1), - }} - tx := wire.NewNativeMsgTx(wire.TxVersion, txIns, txOuts) - diff, err := fus.diffFromTx(tx, acceptingBlueScore) - if err != nil { - t.Errorf("diffFromTx: %v", err) - } - if !reflect.DeepEqual(diff.toAdd, utxoCollection{ - wire.Outpoint{TxID: *tx.TxID(), Index: 0}: NewUTXOEntry(tx.TxOut[0], false, 2), - }) { - t.Errorf("diff.toAdd doesn't have the expected values") - } - - if !reflect.DeepEqual(diff.toRemove, utxoCollection{ - wire.Outpoint{TxID: *cbTx.TxID(), Index: 0}: NewUTXOEntry(cbTx.TxOut[0], true, 1), - }) { - t.Errorf("diff.toRemove doesn't have the expected values") - } - - //Test that we get an error if we don't have the outpoint inside the utxo set - invalidTxIns := []*wire.TxIn{{ - PreviousOutpoint: wire.Outpoint{TxID: daghash.TxID{}, Index: 0}, - SignatureScript: nil, - Sequence: wire.MaxTxInSequenceNum, - }} - invalidTxOuts := []*wire.TxOut{{ - ScriptPubKey: OpTrueScript, - Value: uint64(1), - }} - invalidTx := wire.NewNativeMsgTx(wire.TxVersion, invalidTxIns, invalidTxOuts) - _, err = fus.diffFromTx(invalidTx, acceptingBlueScore) - if err == nil { - t.Errorf("diffFromTx: expected an error but got ") - } - - //Test that we get an error if the outpoint is inside diffUTXOSet's toRemove - diff2 := &UTXODiff{ - toAdd: utxoCollection{}, - toRemove: utxoCollection{}, - } - dus := NewDiffUTXOSet(fus, diff2) - if isAccepted, err := dus.AddTx(tx, 2); err != nil { - t.Fatalf("AddTx unexpectedly failed. Error: %s", err) - } else if !isAccepted { - t.Fatalf("AddTx unexpectedly didn't add tx %s", tx.TxID()) - } - _, err = dus.diffFromTx(tx, acceptingBlueScore) - if err == nil { - t.Errorf("diffFromTx: expected an error but got ") - } -} - // collection returns a collection of all UTXOs in this set func (fus *FullUTXOSet) collection() utxoCollection { return fus.utxoCollection.clone() diff --git a/blockdag/validate.go b/blockdag/validate.go index 7e1154485..a42dad058 100644 --- a/blockdag/validate.go +++ b/blockdag/validate.go @@ -545,6 +545,20 @@ func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) (t existingTxIDs[*id] = struct{}{} } + // Check for double spends with transactions on the same block. + usedOutpoints := make(map[wire.Outpoint]*daghash.TxID) + for _, tx := range transactions { + for _, txIn := range tx.MsgTx().TxIn { + if spendingTxID, exists := usedOutpoints[txIn.PreviousOutpoint]; exists { + str := fmt.Sprintf("transaction %s spends "+ + "outpoint %s that was already spent by "+ + "transaction %s in this block", tx.ID(), txIn.PreviousOutpoint, spendingTxID) + return 0, ruleError(ErrDoubleSpendInSameBlock, str) + } + usedOutpoints[txIn.PreviousOutpoint] = tx.ID() + } + } + return delay, nil } @@ -838,6 +852,11 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet, return nil, err } + err = checkDoubleSpendsWithBlockPast(pastUTXO, transactions) + if err != nil { + return nil, err + } + if err := validateBlockMass(pastUTXO, transactions); err != nil { return nil, err } @@ -913,7 +932,7 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet, // Now that the inexpensive checks are done and have passed, verify the // transactions are actually allowed to spend the coins by running the - // expensive ECDSA signature check scripts. Doing this last helps + // expensive SCHNORR signature check scripts. Doing this last helps // prevent CPU exhaustion attacks. err := checkBlockScripts(block, pastUTXO, transactions, scriptFlags, dag.sigCache) if err != nil { diff --git a/mining/test_utils.go b/mining/test_utils.go index 132c38587..bbca4d83c 100644 --- a/mining/test_utils.go +++ b/mining/test_utils.go @@ -104,7 +104,7 @@ func PrepareBlockForTest(dag *blockdag.BlockDAG, params *dagconfig.Params, paren } template.Block.Header.HashMerkleRoot = blockdag.BuildHashMerkleTreeStore(utilTxs).Root() - ms, err := dag.NextBlockMultiset(utilTxs) + ms, err := dag.NextBlockMultiset() if err != nil { return nil, err }