kaspad/blockdag/block_utxo.go
stasatdaglabs 0653e59e16
[NOD-1190] Refactor process.go (#858)
* [NOD-1190] Move non-processBlock stuff out of process.go.

* [NOD-1190] Move everything out of accept.go.

* [NOD-1190] Move all processBlock functions to process.go.

* [NOD-1190] Move orphan stuff to orphan.go.

* [NOD-1190] Remove thresholdstate stuff.

* [NOD-1190] Move isSynced to sync_rate.go.

* [NOD-1190] Move delayed block stuff to delayed_blocks.go.

* [NOD-1190] Rename orphans.go to orphaned_blocks.go.

* [NOD-1190] Move non-BlockDAG structs out of dag.go.

* [NOD-1190] Remove unused fields.

* [NOD-1190] Fixup BlockDAG.New a bit.

* [NOD-1190] Move sequence lock stuff to sequence_lock.go

* [NOD-1190] Move some multiset stuff out of dag.go.

* [NOD-1190] Move finality stuff out of dag.go.

* [NOD-1190] Move blocklocator stuff out of dag.go.

* [NOD-1190] Move confirmation stuff out of dag.go.

* [NOD-1190] Move utxo and selected parent chain stuff out of dag.go.

* [NOD-1190] Move BlockDAG lock functions to the beginning of dag.go.

* [NOD-1190] Move verifyAndBuildUTXO out of process.go.

* [NOD-1190] Extract handleProcessBlockError to a function.

* [NOD-1190] Remove daglock unlock in notifyBlockAccepted.

* [NOD-1190] Extract checkDuplicateBlock to a method.

* [NOD-1190] Fix merge errors.

* [NOD-1190] Remove unused parameter from CalcSequenceLock.

* [NOD-1190] Extract processBlock contents into functions.

* [NOD-1190] Fix parent delayed blocks not marking their children as delayed

* [NOD-1190] Fix TestProcessDelayedBlocks.

* [NOD-1190] Extract stuff in maybeAcceptBlock to separate functions.

* [NOD-1190] Rename handleProcessBlockError to handleConnectBlockError.

* [NOD-1190] Remove some comments.

* [NOD-1190] Use lowercase in error messages.

* [NOD-1190] Rename createNewBlockNode to createBlockNodeFromBlock.

* [NOD-1190] Rename orphaned_blocks.go to orpan_blocks.go.

* [NOD-1190] Extract validateUTXOCommitment to a separate function.

* [NOD-1190] Fix a bug in validateUTXOCommitment.

* [NOD-1190] Rename checkBlockTxsFinalized to checkBlockTransactionsFinalized.

* [NOD-1190] Add a comment over createBlockNodeFromBlock.

* [NOD-1190] Fold validateAllTxsFinalized into checkBlockTransactionsFinalized.

* [NOD-1190] Return parents from checkBlockParents.

* [NOD-1190] Remove the processBlock prefix from the functions that had it.

* [NOD-1190] Begin extracting functions out of checkTransactionSanity.

* [NOD-1190] Finish extracting functions out of checkTransactionSanity.

* [NOD-1190] Remove an unused parameter.

* [NOD-1190] Fix merge errors.

* [NOD-1190] Added an explanation as to why we change the nonce in TestProcessDelayedBlocks.

* [NOD-1190] Fix a comment.

* [NOD-1190] Fix a comment.

* [NOD-1190] Fix a typo.

* [NOD-1190] Replace checkBlockParents with handleLookupParentNodesError.
2020-08-13 13:33:43 +03:00

303 lines
9.5 KiB
Go

package blockdag
import (
"fmt"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
// TxAcceptanceData stores a transaction together with an indication
// if it was accepted or not by some block
type TxAcceptanceData struct {
Tx *util.Tx
IsAccepted bool
}
// BlockTxsAcceptanceData stores all transactions in a block with an indication
// if they were accepted or not by some other block
type BlockTxsAcceptanceData struct {
BlockHash daghash.Hash
TxAcceptanceData []TxAcceptanceData
}
// MultiBlockTxsAcceptanceData stores data about which transactions were accepted by a block
// It's a slice of the block's blues block IDs and their transaction acceptance data
type MultiBlockTxsAcceptanceData []BlockTxsAcceptanceData
// FindAcceptanceData finds the BlockTxsAcceptanceData that matches blockHash
func (data MultiBlockTxsAcceptanceData) FindAcceptanceData(blockHash *daghash.Hash) (*BlockTxsAcceptanceData, bool) {
for _, acceptanceData := range data {
if acceptanceData.BlockHash.IsEqual(blockHash) {
return &acceptanceData, true
}
}
return nil, false
}
// TxsAcceptedByVirtual retrieves transactions accepted by the current virtual block
//
// This function MUST be called with the DAG read-lock held
func (dag *BlockDAG) TxsAcceptedByVirtual() (MultiBlockTxsAcceptanceData, error) {
_, _, txsAcceptanceData, err := dag.pastUTXO(&dag.virtual.blockNode)
return txsAcceptanceData, err
}
// TxsAcceptedByBlockHash retrieves transactions accepted by the given block
//
// This function MUST be called with the DAG read-lock held
func (dag *BlockDAG) TxsAcceptedByBlockHash(blockHash *daghash.Hash) (MultiBlockTxsAcceptanceData, error) {
node, ok := dag.index.LookupNode(blockHash)
if !ok {
return nil, errors.Errorf("Couldn't find block %s", blockHash)
}
_, _, txsAcceptanceData, err := dag.pastUTXO(node)
return txsAcceptanceData, err
}
func (dag *BlockDAG) meldVirtualUTXO(newVirtualUTXODiffSet *DiffUTXOSet) error {
return newVirtualUTXODiffSet.meldToBase()
}
// 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 {
if tx.IsCoinBase() {
continue
}
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 nil
}
// verifyAndBuildUTXO verifies all transactions in the given block and builds its UTXO
// to save extra traversals it returns the transactions acceptance data, the compactFeeData
// for the new block and its multiset.
func (node *blockNode) verifyAndBuildUTXO(dag *BlockDAG, transactions []*util.Tx, fastAdd bool) (
newBlockUTXO UTXOSet, txsAcceptanceData MultiBlockTxsAcceptanceData, newBlockFeeData compactFeeData, multiset *secp256k1.MultiSet, err error) {
pastUTXO, selectedParentPastUTXO, txsAcceptanceData, err := dag.pastUTXO(node)
if err != nil {
return nil, nil, nil, nil, err
}
err = node.validateAcceptedIDMerkleRoot(dag, txsAcceptanceData)
if err != nil {
return nil, nil, nil, nil, err
}
feeData, err := dag.checkConnectToPastUTXO(node, pastUTXO, transactions, fastAdd)
if err != nil {
return nil, nil, nil, nil, err
}
multiset, err = node.calcMultiset(dag, txsAcceptanceData, selectedParentPastUTXO)
if err != nil {
return nil, nil, nil, nil, err
}
err = node.validateUTXOCommitment(multiset)
if err != nil {
return nil, nil, nil, nil, err
}
return pastUTXO, txsAcceptanceData, feeData, multiset, nil
}
func genesisPastUTXO(virtual *virtualBlock) UTXOSet {
// The genesis has no past UTXO, so we create an empty UTXO
// set by creating a diff UTXO set with the virtual UTXO
// set, and adding all of its entries in toRemove
diff := NewUTXODiff()
for outpoint, entry := range virtual.utxoSet.utxoCollection {
diff.toRemove[outpoint] = entry
}
genesisPastUTXO := UTXOSet(NewDiffUTXOSet(virtual.utxoSet, diff))
return genesisPastUTXO
}
// 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(selectedParentPastUTXO UTXOSet, blueBlocks []*util.Block) (
pastUTXO UTXOSet, multiBlockTxsAcceptanceData MultiBlockTxsAcceptanceData, err error) {
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.
for i := 0; i < len(blueBlocks); i++ {
blueBlock := blueBlocks[i]
transactions := blueBlock.Transactions()
blockTxsAcceptanceData := BlockTxsAcceptanceData{
BlockHash: *blueBlock.Hash(),
TxAcceptanceData: make([]TxAcceptanceData, len(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 !isSelectedParent && tx.IsCoinBase() {
isAccepted = false
} else {
isAccepted, err = pastUTXO.AddTx(tx.MsgTx(), node.blueScore)
if err != nil {
return nil, nil, err
}
}
blockTxsAcceptanceData.TxAcceptanceData[j] = TxAcceptanceData{Tx: tx, IsAccepted: isAccepted}
}
multiBlockTxsAcceptanceData[i] = blockTxsAcceptanceData
}
return pastUTXO, multiBlockTxsAcceptanceData, nil
}
// pastUTXO returns the UTXO of a given block's past
// 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, selectedParentPastUTXO UTXOSet, bluesTxsAcceptanceData MultiBlockTxsAcceptanceData, err error) {
if node.isGenesis() {
return genesisPastUTXO(dag.virtual), nil, MultiBlockTxsAcceptanceData{}, nil
}
selectedParentPastUTXO, err = dag.restorePastUTXO(node.selectedParent)
if err != nil {
return nil, nil, nil, err
}
blueBlocks, err := dag.fetchBlueBlocks(node)
if err != nil {
return nil, nil, nil, err
}
pastUTXO, bluesTxsAcceptanceData, err = node.applyBlueBlocks(selectedParentPastUTXO, blueBlocks)
if err != nil {
return nil, nil, nil, err
}
return pastUTXO, selectedParentPastUTXO, bluesTxsAcceptanceData, nil
}
// 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
// all into a stack
for current := node; current != nil; {
stack = append(stack, current)
var err error
current, err = dag.utxoDiffStore.diffChildByNode(current)
if err != nil {
return nil, err
}
}
// Start with the top item in the stack, going over it top-to-bottom,
// applying the UTXO-diff one-by-one.
topNode, stack := stack[len(stack)-1], stack[:len(stack)-1] // pop the top item in the stack
topNodeDiff, err := dag.utxoDiffStore.diffByNode(topNode)
if err != nil {
return nil, err
}
accumulatedDiff := topNodeDiff.clone()
for i := len(stack) - 1; i >= 0; i-- {
diff, err := dag.utxoDiffStore.diffByNode(stack[i])
if err != nil {
return nil, err
}
// Use withDiffInPlace, otherwise copying the diffs again and again create a polynomial overhead
err = accumulatedDiff.withDiffInPlace(diff)
if err != nil {
return nil, err
}
}
return NewDiffUTXOSet(dag.virtual.utxoSet, accumulatedDiff), nil
}
// 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 {
tipPastUTXO, err := dag.restorePastUTXO(tip)
if err != nil {
return err
}
diff, err := virtualUTXO.diffFrom(tipPastUTXO)
if err != nil {
return err
}
err = dag.utxoDiffStore.setBlockDiff(tip, diff)
if err != nil {
return err
}
}
return nil
}
// updateParents adds this block to the children sets of its parents
// and updates the diff of any parent whose DiffChild is this block
func (node *blockNode) updateParents(dag *BlockDAG, newBlockUTXO UTXOSet) error {
node.updateParentsChildren()
return node.updateParentsDiffs(dag, newBlockUTXO)
}
// updateParentsDiffs updates the diff of any parent whose DiffChild is this block
func (node *blockNode) updateParentsDiffs(dag *BlockDAG, newBlockUTXO UTXOSet) error {
virtualDiffFromNewBlock, err := dag.virtual.utxoSet.diffFrom(newBlockUTXO)
if err != nil {
return err
}
err = dag.utxoDiffStore.setBlockDiff(node, virtualDiffFromNewBlock)
if err != nil {
return err
}
for parent := range node.parents {
diffChild, err := dag.utxoDiffStore.diffChildByNode(parent)
if err != nil {
return err
}
if diffChild == nil {
parentPastUTXO, err := dag.restorePastUTXO(parent)
if err != nil {
return err
}
err = dag.utxoDiffStore.setBlockDiffChild(parent, node)
if err != nil {
return err
}
diff, err := newBlockUTXO.diffFrom(parentPastUTXO)
if err != nil {
return err
}
err = dag.utxoDiffStore.setBlockDiff(parent, diff)
if err != nil {
return err
}
}
}
return nil
}