[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 <stas@daglabs.com>
Co-authored-by: Ori Newman <orinewman1@gmail.com>
This commit is contained in:
Svarog 2020-05-20 12:43:52 +03:00 committed by GitHub
parent b884ba128e
commit 0ca127853d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 309 additions and 369 deletions

View File

@ -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
}

View File

@ -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: &params,
})
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: &params,
})
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")
}
}

View File

@ -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",

View File

@ -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"},
},
},
},

View File

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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) {

View File

@ -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 <nil>")
}
//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 <nil>")
}
}
// collection returns a collection of all UTXOs in this set
func (fus *FullUTXOSet) collection() utxoCollection {
return fus.utxoCollection.clone()

View File

@ -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 {

View File

@ -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
}