mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-09-15 14:00:13 +00:00

* [NOD-1320] Flush UTXOs to disk. * [NOD-1320] Minor improvements and fixes. * FullUTXOSet: change size type from int64 to uint64. * Rename FullUTXOSet.size to FullUTXOSet.estimatedSize * Fill NewFullUTXOSetFromContext with db context on virtual block creation. * Typo fixes. * [NOD-1320] Stylystic fixes. * [NOD-1320] Update tests. Improvements and fixes. * Update blockdag/dag tests: prepare DB for tests. * Update blockdag/utxoset tests: prepare DB for tests. * Update blockdag/test_utils utils. * Update blockdag/common tests. * FullUTXOSet: remove embedded utxoCollection type. Rename utxoCollection to utxoCache. * Fix blockdag/block_utxo genesisPastUTXO func. * Minor fixes and improvements.
364 lines
11 KiB
Go
364 lines
11 KiB
Go
package blockdag
|
|
|
|
import (
|
|
"github.com/kaspanet/go-secp256k1"
|
|
"github.com/kaspanet/kaspad/util"
|
|
"github.com/kaspanet/kaspad/util/daghash"
|
|
"github.com/kaspanet/kaspad/util/mstime"
|
|
"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
|
|
Fee uint64
|
|
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()
|
|
}
|
|
|
|
type utxoVerificationOutput struct {
|
|
newBlockPastUTXO UTXOSet
|
|
txsAcceptanceData MultiBlockTxsAcceptanceData
|
|
newBlockMultiset *secp256k1.MultiSet
|
|
}
|
|
|
|
// verifyAndBuildUTXO verifies all transactions in the given block and builds its UTXO
|
|
// to save extra traversals it returns the transactions acceptance data
|
|
// for the new block and its multiset.
|
|
func (node *blockNode) verifyAndBuildUTXO(transactions []*util.Tx) (*utxoVerificationOutput, error) {
|
|
pastUTXO, selectedParentPastUTXO, txsAcceptanceData, err := node.dag.pastUTXO(node)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = node.validateAcceptedIDMerkleRoot(node.dag, txsAcceptanceData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = node.dag.checkConnectBlockToPastUTXO(node, pastUTXO, transactions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
multiset, err := node.calcMultiset(txsAcceptanceData, selectedParentPastUTXO)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = node.validateUTXOCommitment(multiset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &utxoVerificationOutput{
|
|
newBlockPastUTXO: pastUTXO,
|
|
txsAcceptanceData: txsAcceptanceData,
|
|
newBlockMultiset: 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.utxoCache {
|
|
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))
|
|
|
|
// We obtain the median time of the selected parent block (unless it's genesis block)
|
|
// in order to determine if transactions in the current block are final.
|
|
selectedParentMedianTime := node.selectedParentMedianTime()
|
|
accumulatedMass := uint64(0)
|
|
|
|
// 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 transactions {
|
|
var isAccepted bool
|
|
var txFee uint64
|
|
|
|
isAccepted, txFee, accumulatedMass, err =
|
|
node.maybeAcceptTx(tx, isSelectedParent, pastUTXO, accumulatedMass, selectedParentMedianTime)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
blockTxsAcceptanceData.TxAcceptanceData[j] = TxAcceptanceData{
|
|
Tx: tx,
|
|
Fee: txFee,
|
|
IsAccepted: isAccepted}
|
|
}
|
|
multiBlockTxsAcceptanceData[i] = blockTxsAcceptanceData
|
|
}
|
|
|
|
return pastUTXO, multiBlockTxsAcceptanceData, nil
|
|
}
|
|
|
|
func (node *blockNode) maybeAcceptTx(tx *util.Tx, isSelectedParent bool, pastUTXO UTXOSet,
|
|
accumulatedMassBefore uint64, selectedParentMedianTime mstime.Time) (
|
|
isAccepted bool, txFee uint64, accumulatedMassAfter uint64, err error) {
|
|
|
|
accumulatedMass := accumulatedMassBefore
|
|
|
|
// Coinbase transaction outputs are added to the UTXO-set only if they are in the selected parent chain.
|
|
if tx.IsCoinBase() {
|
|
if !isSelectedParent {
|
|
return false, 0, 0, nil
|
|
}
|
|
txMass := CalcTxMass(tx, nil)
|
|
accumulatedMass += txMass
|
|
|
|
_, err = pastUTXO.AddTx(tx.MsgTx(), node.blueScore)
|
|
if err != nil {
|
|
return false, 0, 0, err
|
|
}
|
|
|
|
return true, 0, accumulatedMass, nil
|
|
}
|
|
|
|
txFee, accumulatedMassAfter, err = node.dag.checkConnectTransactionToPastUTXO(
|
|
node, tx, pastUTXO, accumulatedMassBefore, selectedParentMedianTime)
|
|
if err != nil {
|
|
if !errors.As(err, &(RuleError{})) {
|
|
return false, 0, 0, err
|
|
}
|
|
|
|
isAccepted = false
|
|
} else {
|
|
isAccepted = true
|
|
accumulatedMass = accumulatedMassAfter
|
|
|
|
_, err = pastUTXO.AddTx(tx.MsgTx(), node.blueScore)
|
|
if err != nil {
|
|
return false, 0, 0, err
|
|
}
|
|
}
|
|
return isAccepted, txFee, accumulatedMass, 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
|
|
}
|
|
|
|
// updateValidTipsUTXO builds and applies new diff UTXOs for all the DAG's valid tips
|
|
func updateValidTipsUTXO(dag *BlockDAG, virtualUTXO UTXOSet) error {
|
|
for validTip := range dag.validTips {
|
|
validTipPastUTXO, err := dag.restorePastUTXO(validTip)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
diff, err := virtualUTXO.diffFrom(validTipPastUTXO)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = dag.utxoDiffStore.setBlockDiff(validTip, diff)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// updateParentsDiffs updates the diff of any parent whose DiffChild is this block
|
|
func (node *blockNode) updateParentsDiffs(dag *BlockDAG, newBlockPastUTXO UTXOSet) error {
|
|
for parent := range node.parents {
|
|
if node.dag.index.BlockNodeStatus(parent) == statusUTXOPendingVerification {
|
|
continue
|
|
}
|
|
|
|
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 := newBlockPastUTXO.diffFrom(parentPastUTXO)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = dag.utxoDiffStore.setBlockDiff(parent, diff)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (node *blockNode) updateDiffAndDiffChild(newBlockPastUTXO UTXOSet) error {
|
|
var diffChild *blockNode
|
|
for child := range node.children {
|
|
if node.dag.index.BlockNodeStatus(child) == statusValid {
|
|
diffChild = child
|
|
break
|
|
}
|
|
}
|
|
|
|
// If there's no diffChild, then virtual is the de-facto diffChild
|
|
var diffChildUTXOSet UTXOSet = node.dag.virtual.utxoSet
|
|
if diffChild != nil {
|
|
var err error
|
|
diffChildUTXOSet, err = node.dag.restorePastUTXO(diffChild)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
diffFromDiffChild, err := diffChildUTXOSet.diffFrom(newBlockPastUTXO)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = node.dag.utxoDiffStore.setBlockDiff(node, diffFromDiffChild)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if diffChild != nil {
|
|
err = node.dag.utxoDiffStore.setBlockDiffChild(node, diffChild)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|