mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-04 13:16:43 +00:00
[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:
parent
b884ba128e
commit
0ca127853d
224
blockdag/dag.go
224
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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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
|
||||
|
BIN
blockdag/testdata/blk_0_to_4.dat
vendored
BIN
blockdag/testdata/blk_0_to_4.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3A.dat
vendored
BIN
blockdag/testdata/blk_3A.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3B.dat
vendored
BIN
blockdag/testdata/blk_3B.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3C.dat
vendored
BIN
blockdag/testdata/blk_3C.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3D.dat
vendored
BIN
blockdag/testdata/blk_3D.dat
vendored
Binary file not shown.
@ -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) {
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user