[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.
This commit is contained in:
stasatdaglabs 2020-08-13 13:33:43 +03:00 committed by GitHub
parent 32463ce906
commit 0653e59e16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 2451 additions and 3272 deletions

View File

@ -195,11 +195,10 @@ func setupMempool(cfg *config.Config, dag *blockdag.BlockDAG, sigCache *txscript
MaxTxVersion: 1, MaxTxVersion: 1,
}, },
CalcSequenceLockNoLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) { CalcSequenceLockNoLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
return dag.CalcSequenceLockNoLock(tx, utxoSet, true) return dag.CalcSequenceLockNoLock(tx, utxoSet)
}, },
IsDeploymentActive: dag.IsDeploymentActive, SigCache: sigCache,
SigCache: sigCache, DAG: dag,
DAG: dag,
} }
return mempool.New(&mempoolConfig) return mempool.New(&mempoolConfig)

View File

@ -1,154 +0,0 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
func (dag *BlockDAG) addNodeToIndexWithInvalidAncestor(block *util.Block) error {
blockHeader := &block.MsgBlock().Header
newNode, _ := dag.newBlockNode(blockHeader, newBlockSet())
newNode.status = statusInvalidAncestor
dag.index.AddNode(newNode)
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
err = dag.index.flushToDB(dbTx)
if err != nil {
return err
}
return dbTx.Commit()
}
// maybeAcceptBlock potentially accepts a block into the block DAG. It
// performs several validation checks which depend on its position within
// the block DAG before adding it. The block is expected to have already
// gone through ProcessBlock before calling this function with it.
//
// The flags are also passed to checkBlockContext and connectToDAG. See
// their documentation for how the flags modify their behavior.
//
// This function MUST be called with the dagLock held (for writes).
func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) error {
parents, err := lookupParentNodes(block, dag)
if err != nil {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrInvalidAncestorBlock {
err := dag.addNodeToIndexWithInvalidAncestor(block)
if err != nil {
return err
}
}
return err
}
// The block must pass all of the validation rules which depend on the
// position of the block within the block DAG.
err = dag.checkBlockContext(block, parents, flags)
if err != nil {
return err
}
// Create a new block node for the block and add it to the node index.
newNode, selectedParentAnticone := dag.newBlockNode(&block.MsgBlock().Header, parents)
newNode.status = statusDataStored
dag.index.AddNode(newNode)
// Insert the block into the database if it's not already there. Even
// though it is possible the block will ultimately fail to connect, it
// has already passed all proof-of-work and validity tests which means
// it would be prohibitively expensive for an attacker to fill up the
// disk with a bunch of blocks that fail to connect. This is necessary
// since it allows block download to be decoupled from the much more
// expensive connection logic. It also has some other nice properties
// such as making blocks that never become part of the DAG or
// blocks that fail to connect available for further analysis.
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
blockExists, err := dbaccess.HasBlock(dbTx, block.Hash())
if err != nil {
return err
}
if !blockExists {
err := storeBlock(dbTx, block)
if err != nil {
return err
}
}
err = dag.index.flushToDB(dbTx)
if err != nil {
return err
}
err = dbTx.Commit()
if err != nil {
return err
}
// Make sure that all the block's transactions are finalized
fastAdd := flags&BFFastAdd == BFFastAdd
bluestParent := parents.bluest()
if !fastAdd {
if err := dag.validateAllTxsFinalized(block, newNode, bluestParent); err != nil {
return err
}
}
// Connect the passed block to the DAG. This also handles validation of the
// transaction scripts.
chainUpdates, err := dag.addBlock(newNode, block, selectedParentAnticone, flags)
if err != nil {
return err
}
// Notify the caller that the new block was accepted into the block
// DAG. The caller would typically want to react by relaying the
// inventory to other peers.
dag.dagLock.Unlock()
dag.sendNotification(NTBlockAdded, &BlockAddedNotificationData{
Block: block,
WasUnorphaned: flags&BFWasUnorphaned != 0,
})
if len(chainUpdates.addedChainBlockHashes) > 0 {
dag.sendNotification(NTChainChanged, &ChainChangedNotificationData{
RemovedChainBlockHashes: chainUpdates.removedChainBlockHashes,
AddedChainBlockHashes: chainUpdates.addedChainBlockHashes,
})
}
dag.dagLock.Lock()
return nil
}
func lookupParentNodes(block *util.Block, dag *BlockDAG) (blockSet, error) {
header := block.MsgBlock().Header
parentHashes := header.ParentHashes
nodes := newBlockSet()
for _, parentHash := range parentHashes {
node, ok := dag.index.LookupNode(parentHash)
if !ok {
str := fmt.Sprintf("parent block %s is unknown", parentHash)
return nil, ruleError(ErrParentBlockUnknown, str)
} else if dag.index.NodeStatus(node).KnownInvalid() {
str := fmt.Sprintf("parent block %s is known to be invalid", parentHash)
return nil, ruleError(ErrInvalidAncestorBlock, str)
}
nodes.add(node)
}
return nodes, nil
}

View File

@ -1,107 +0,0 @@
package blockdag
import (
"errors"
"path/filepath"
"testing"
"github.com/kaspanet/kaspad/dagconfig"
)
func TestMaybeAcceptBlockErrors(t *testing.T) {
// Create a new database and DAG instance to run tests against.
dag, teardownFunc, err := DAGSetup("TestMaybeAcceptBlockErrors", true, Config{
DAGParams: &dagconfig.SimnetParams,
})
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
dag.TestSetCoinbaseMaturity(0)
// Test rejecting the block if its parents are missing
orphanBlockFile := "blk_3B.dat"
loadedBlocks, err := LoadBlocks(filepath.Join("testdata/", orphanBlockFile))
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: "+
"Error loading file '%s': %s\n", orphanBlockFile, err)
}
block := loadedBlocks[0]
err = dag.maybeAcceptBlock(block, BFNone)
if err == nil {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
"Expected: %s, got: <nil>", ErrParentBlockUnknown)
}
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); !ok {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
"Expected RuleError but got %s", err)
} else if ruleErr.ErrorCode != ErrParentBlockUnknown {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
"Unexpected error code. Want: %s, got: %s", ErrParentBlockUnknown, ruleErr.ErrorCode)
}
// Test rejecting the block if its parents are invalid
blocksFile := "blk_0_to_4.dat"
blocks, err := LoadBlocks(filepath.Join("testdata/", blocksFile))
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: "+
"Error loading file '%s': %s\n", blocksFile, err)
}
// Add a valid block and mark it as invalid
block1 := blocks[1]
isOrphan, isDelayed, err := dag.ProcessBlock(block1, BFNone)
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: Valid block unexpectedly returned an error: %s", err)
}
if isDelayed {
t.Fatalf("TestMaybeAcceptBlockErrors: block 1 is too far in the future")
}
if isOrphan {
t.Fatalf("TestMaybeAcceptBlockErrors: incorrectly returned block 1 is an orphan")
}
blockNode1, ok := dag.index.LookupNode(block1.Hash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", block1.Hash())
}
dag.index.SetStatusFlags(blockNode1, statusValidateFailed)
block2 := blocks[2]
err = dag.maybeAcceptBlock(block2, BFNone)
if err == nil {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
"Expected: %s, got: <nil>", ErrInvalidAncestorBlock)
}
if ok := errors.As(err, &ruleErr); !ok {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
"Expected RuleError but got %s", err)
} else if ruleErr.ErrorCode != ErrInvalidAncestorBlock {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
"Unexpected error. Want: %s, got: %s", ErrInvalidAncestorBlock, ruleErr.ErrorCode)
}
// Set block1's status back to valid for next tests
dag.index.UnsetStatusFlags(blockNode1, statusValidateFailed)
// Test rejecting the block due to bad context
originalBits := block2.MsgBlock().Header.Bits
block2.MsgBlock().Header.Bits = 0
err = dag.maybeAcceptBlock(block2, BFNone)
if err == nil {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
"Expected: %s, got: <nil>", ErrUnexpectedDifficulty)
}
if ok := errors.As(err, &ruleErr); !ok {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
"Expected RuleError but got %s", err)
} else if ruleErr.ErrorCode != ErrUnexpectedDifficulty {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
"Unexpected error. Want: %s, got: %s", ErrUnexpectedDifficulty, ruleErr.ErrorCode)
}
// Set block2's bits back to valid for next tests
block2.MsgBlock().Header.Bits = originalBits
}

View File

@ -0,0 +1,44 @@
package blockdag
// BehaviorFlags is a bitmask defining tweaks to the normal behavior when
// performing DAG processing and consensus rules checks.
type BehaviorFlags uint32
const (
// BFFastAdd may be set to indicate that several checks can be avoided
// for the block since it is already known to fit into the DAG due to
// already proving it correct links into the DAG.
BFFastAdd BehaviorFlags = 1 << iota
// BFNoPoWCheck may be set to indicate the proof of work check which
// ensures a block hashes to a value less than the required target will
// not be performed.
BFNoPoWCheck
// BFWasUnorphaned may be set to indicate that a block was just now
// unorphaned
BFWasUnorphaned
// BFAfterDelay may be set to indicate that a block had timestamp too far
// in the future, just finished the delay
BFAfterDelay
// BFWasStored is set to indicate that the block was previously stored
// in the block index but was never fully processed
BFWasStored
// BFDisallowDelay is set to indicate that a delayed block should be rejected.
// This is used for the case where a block is submitted through RPC.
BFDisallowDelay
// BFDisallowOrphans is set to indicate that an orphan block should be rejected.
// This is used for the case where a block is submitted through RPC.
BFDisallowOrphans
// BFNone is a convenience value to specifically indicate no flags.
BFNone BehaviorFlags = 0
)
func isBehaviorFlagRaised(flags BehaviorFlags, flag BehaviorFlags) bool {
return flags&flag == flag
}

302
blockdag/block_utxo.go Normal file
View File

@ -0,0 +1,302 @@
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
}

View File

@ -5,7 +5,9 @@
package blockdag package blockdag
import ( import (
"fmt"
"github.com/kaspanet/kaspad/dbaccess" "github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util"
"sync" "sync"
"github.com/kaspanet/kaspad/dagconfig" "github.com/kaspanet/kaspad/dagconfig"
@ -134,3 +136,42 @@ func (bi *blockIndex) flushToDB(dbContext *dbaccess.TxContext) error {
func (bi *blockIndex) clearDirtyEntries() { func (bi *blockIndex) clearDirtyEntries() {
bi.dirty = make(map[*blockNode]struct{}) bi.dirty = make(map[*blockNode]struct{})
} }
func (dag *BlockDAG) addNodeToIndexWithInvalidAncestor(block *util.Block) error {
blockHeader := &block.MsgBlock().Header
newNode, _ := dag.newBlockNode(blockHeader, newBlockSet())
newNode.status = statusInvalidAncestor
dag.index.AddNode(newNode)
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
err = dag.index.flushToDB(dbTx)
if err != nil {
return err
}
return dbTx.Commit()
}
func lookupParentNodes(block *util.Block, dag *BlockDAG) (blockSet, error) {
header := block.MsgBlock().Header
parentHashes := header.ParentHashes
nodes := newBlockSet()
for _, parentHash := range parentHashes {
node, ok := dag.index.LookupNode(parentHash)
if !ok {
str := fmt.Sprintf("parent block %s is unknown", parentHash)
return nil, ruleError(ErrParentBlockUnknown, str)
} else if dag.index.NodeStatus(node).KnownInvalid() {
str := fmt.Sprintf("parent block %s is known to be invalid", parentHash)
return nil, ruleError(ErrInvalidAncestorBlock, str)
}
nodes.add(node)
}
return nodes, nil
}

View File

@ -1,6 +1,7 @@
package blockdag package blockdag
import ( import (
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/util/daghash" "github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -107,3 +108,136 @@ func (dag *BlockDAG) FindNextLocatorBoundaries(locator BlockLocator) (highHash,
} }
return locator[nextBlockLocatorIndex], lowNode.hash return locator[nextBlockLocatorIndex], lowNode.hash
} }
// antiPastHashesBetween returns the hashes of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to the provided
// max number of block hashes.
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) antiPastHashesBetween(lowHash, highHash *daghash.Hash, maxHashes uint64) ([]*daghash.Hash, error) {
nodes, err := dag.antiPastBetween(lowHash, highHash, maxHashes)
if err != nil {
return nil, err
}
hashes := make([]*daghash.Hash, len(nodes))
for i, node := range nodes {
hashes[i] = node.hash
}
return hashes, nil
}
// antiPastBetween returns the blockNodes between the lowHash's antiPast
// and highHash's antiPast, or up to the provided max number of blocks.
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) antiPastBetween(lowHash, highHash *daghash.Hash, maxEntries uint64) ([]*blockNode, error) {
lowNode, ok := dag.index.LookupNode(lowHash)
if !ok {
return nil, errors.Errorf("Couldn't find low hash %s", lowHash)
}
highNode, ok := dag.index.LookupNode(highHash)
if !ok {
return nil, errors.Errorf("Couldn't find high hash %s", highHash)
}
if lowNode.blueScore >= highNode.blueScore {
return nil, errors.Errorf("Low hash blueScore >= high hash blueScore (%d >= %d)",
lowNode.blueScore, highNode.blueScore)
}
// In order to get no more then maxEntries blocks from the
// future of the lowNode (including itself), we iterate the
// selected parent chain of the highNode and stop once we reach
// highNode.blueScore-lowNode.blueScore+1 <= maxEntries. That
// stop point becomes the new highNode.
// Using blueScore as an approximation is considered to be
// fairly accurate because we presume that most DAG blocks are
// blue.
for highNode.blueScore-lowNode.blueScore+1 > maxEntries {
highNode = highNode.selectedParent
}
// Collect every node in highNode's past (including itself) but
// NOT in the lowNode's past (excluding itself) into an up-heap
// (a heap sorted by blueScore from lowest to greatest).
visited := newBlockSet()
candidateNodes := newUpHeap()
queue := newDownHeap()
queue.Push(highNode)
for queue.Len() > 0 {
current := queue.pop()
if visited.contains(current) {
continue
}
visited.add(current)
isCurrentAncestorOfLowNode, err := dag.isInPast(current, lowNode)
if err != nil {
return nil, err
}
if isCurrentAncestorOfLowNode {
continue
}
candidateNodes.Push(current)
for parent := range current.parents {
queue.Push(parent)
}
}
// Pop candidateNodes into a slice. Since candidateNodes is
// an up-heap, it's guaranteed to be ordered from low to high
nodesLen := int(maxEntries)
if candidateNodes.Len() < nodesLen {
nodesLen = candidateNodes.Len()
}
nodes := make([]*blockNode, nodesLen)
for i := 0; i < nodesLen; i++ {
nodes[i] = candidateNodes.pop()
}
return nodes, nil
}
// AntiPastHashesBetween returns the hashes of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to the provided
// max number of block hashes.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) AntiPastHashesBetween(lowHash, highHash *daghash.Hash, maxHashes uint64) ([]*daghash.Hash, error) {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
hashes, err := dag.antiPastHashesBetween(lowHash, highHash, maxHashes)
if err != nil {
return nil, err
}
return hashes, nil
}
// antiPastHeadersBetween returns the headers of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to the provided
// max number of block headers.
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) antiPastHeadersBetween(lowHash, highHash *daghash.Hash, maxHeaders uint64) ([]*domainmessage.BlockHeader, error) {
nodes, err := dag.antiPastBetween(lowHash, highHash, maxHeaders)
if err != nil {
return nil, err
}
headers := make([]*domainmessage.BlockHeader, len(nodes))
for i, node := range nodes {
headers[i] = node.Header()
}
return headers, nil
}
// AntiPastHeadersBetween returns the headers of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to
// domainmessage.MaxBlockHeadersPerMsg block headers.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) AntiPastHeadersBetween(lowHash, highHash *daghash.Hash, maxHeaders uint64) ([]*domainmessage.BlockHeader, error) {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
headers, err := dag.antiPastHeadersBetween(lowHash, highHash, maxHeaders)
if err != nil {
return nil, err
}
return headers, nil
}

View File

@ -197,3 +197,24 @@ func coinbaseOutputForBlueBlock(dag *BlockDAG, blueBlock *blockNode,
return txOut, nil return txOut, nil
} }
// NextBlockCoinbaseTransaction prepares the coinbase transaction for the next mined block
//
// This function CAN'T be called with the DAG lock held.
func (dag *BlockDAG) NextBlockCoinbaseTransaction(scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
return dag.NextBlockCoinbaseTransactionNoLock(scriptPubKey, extraData)
}
// NextBlockCoinbaseTransactionNoLock prepares the coinbase transaction for the next mined block
//
// This function MUST be called with the DAG read-lock held
func (dag *BlockDAG) NextBlockCoinbaseTransactionNoLock(scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
txsAcceptanceData, err := dag.TxsAcceptedByVirtual()
if err != nil {
return nil, err
}
return dag.virtual.blockNode.expectedCoinbaseTransaction(dag, txsAcceptanceData, scriptPubKey, extraData)
}

View File

@ -103,8 +103,6 @@ func newTestDAG(params *dagconfig.Params) *BlockDAG {
TimestampDeviationTolerance: params.TimestampDeviationTolerance, TimestampDeviationTolerance: params.TimestampDeviationTolerance,
powMaxBits: util.BigToCompact(params.PowMax), powMaxBits: util.BigToCompact(params.PowMax),
index: index, index: index,
warningCaches: newThresholdCaches(vbNumBits),
deploymentCaches: newThresholdCaches(dagconfig.DefinedDeployments),
} }
// Create a genesis block node and block index index populated with it // Create a genesis block node and block index index populated with it

54
blockdag/config.go Normal file
View File

@ -0,0 +1,54 @@
package blockdag
import (
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
// Config is a descriptor which specifies the blockDAG instance configuration.
type Config struct {
// Interrupt specifies a channel the caller can close to signal that
// long running operations, such as catching up indexes or performing
// database migrations, should be interrupted.
//
// This field can be nil if the caller does not desire the behavior.
Interrupt <-chan struct{}
// DAGParams identifies which DAG parameters the DAG is associated
// with.
//
// This field is required.
DAGParams *dagconfig.Params
// TimeSource defines the time source to use for things such as
// block processing and determining whether or not the DAG is current.
TimeSource TimeSource
// SigCache defines a signature cache to use when when validating
// signatures. This is typically most useful when individual
// transactions are already being validated prior to their inclusion in
// a block such as what is usually done via a transaction memory pool.
//
// This field can be nil if the caller is not interested in using a
// signature cache.
SigCache *txscript.SigCache
// IndexManager defines an index manager to use when initializing the
// DAG and connecting blocks.
//
// This field can be nil if the caller does not wish to make use of an
// index manager.
IndexManager IndexManager
// SubnetworkID identifies which subnetwork the DAG is associated
// with.
//
// This field is required.
SubnetworkID *subnetworkid.SubnetworkID
// DatabaseContext is the context in which all database queries related to
// this DAG are going to run.
DatabaseContext *dbaccess.DatabaseContext
}

137
blockdag/confirmations.go Normal file
View File

@ -0,0 +1,137 @@
package blockdag
import (
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
// BlockConfirmationsByHash returns the confirmations number for a block with the
// given hash. See blockConfirmations for further details.
//
// This function is safe for concurrent access
func (dag *BlockDAG) BlockConfirmationsByHash(hash *daghash.Hash) (uint64, error) {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
return dag.BlockConfirmationsByHashNoLock(hash)
}
// BlockConfirmationsByHashNoLock is lock free version of BlockConfirmationsByHash
//
// This function is unsafe for concurrent access.
func (dag *BlockDAG) BlockConfirmationsByHashNoLock(hash *daghash.Hash) (uint64, error) {
if hash.IsEqual(&daghash.ZeroHash) {
return 0, nil
}
node, ok := dag.index.LookupNode(hash)
if !ok {
return 0, errors.Errorf("block %s is unknown", hash)
}
return dag.blockConfirmations(node)
}
// UTXOConfirmations returns the confirmations for the given outpoint, if it exists
// in the DAG's UTXO set.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) UTXOConfirmations(outpoint *domainmessage.Outpoint) (uint64, bool) {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
utxoEntry, ok := dag.GetUTXOEntry(*outpoint)
if !ok {
return 0, false
}
confirmations := dag.SelectedTipBlueScore() - utxoEntry.BlockBlueScore() + 1
return confirmations, true
}
// blockConfirmations returns the current confirmations number of the given node
// The confirmations number is defined as follows:
// * If the node is in the selected tip red set -> 0
// * If the node is the selected tip -> 1
// * Otherwise -> selectedTip.blueScore - acceptingBlock.blueScore + 2
func (dag *BlockDAG) blockConfirmations(node *blockNode) (uint64, error) {
acceptingBlock, err := dag.acceptingBlock(node)
if err != nil {
return 0, err
}
// if acceptingBlock is nil, the node is red
if acceptingBlock == nil {
return 0, nil
}
return dag.selectedTip().blueScore - acceptingBlock.blueScore + 1, nil
}
// acceptingBlock finds the node in the selected-parent chain that had accepted
// the given node
func (dag *BlockDAG) acceptingBlock(node *blockNode) (*blockNode, error) {
// Return an error if the node is the virtual block
if node == &dag.virtual.blockNode {
return nil, errors.New("cannot get acceptingBlock for virtual")
}
// If the node is a chain-block itself, the accepting block is its chain-child
isNodeInSelectedParentChain, err := dag.IsInSelectedParentChain(node.hash)
if err != nil {
return nil, err
}
if isNodeInSelectedParentChain {
if len(node.children) == 0 {
// If the node is the selected tip, it doesn't have an accepting block
return nil, nil
}
for child := range node.children {
isChildInSelectedParentChain, err := dag.IsInSelectedParentChain(child.hash)
if err != nil {
return nil, err
}
if isChildInSelectedParentChain {
return child, nil
}
}
return nil, errors.Errorf("chain block %s does not have a chain child", node.hash)
}
// Find the only chain block that may contain the node in its blues
candidateAcceptingBlock := dag.oldestChainBlockWithBlueScoreGreaterThan(node.blueScore)
// if no candidate is found, it means that the node has same or more
// blue score than the selected tip and is found in its anticone, so
// it doesn't have an accepting block
if candidateAcceptingBlock == nil {
return nil, nil
}
// candidateAcceptingBlock is the accepting block only if it actually contains
// the node in its blues
for _, blue := range candidateAcceptingBlock.blues {
if blue == node {
return candidateAcceptingBlock, nil
}
}
// Otherwise, the node is red or in the selected tip anticone, and
// doesn't have an accepting block
return nil, nil
}
// oldestChainBlockWithBlueScoreGreaterThan finds the oldest chain block with a blue score
// greater than blueScore. If no such block exists, this method returns nil
func (dag *BlockDAG) oldestChainBlockWithBlueScoreGreaterThan(blueScore uint64) *blockNode {
chainBlockIndex, ok := util.SearchSlice(len(dag.virtual.selectedParentChainSlice), func(i int) bool {
selectedPathNode := dag.virtual.selectedParentChainSlice[i]
return selectedPathNode.blueScore > blueScore
})
if !ok {
return nil
}
return dag.virtual.selectedParentChainSlice[chainBlockIndex]
}

File diff suppressed because it is too large Load Diff

View File

@ -305,7 +305,6 @@ func TestCalcSequenceLock(t *testing.T) {
name string name string
tx *domainmessage.MsgTx tx *domainmessage.MsgTx
utxoSet UTXOSet utxoSet UTXOSet
mempool bool
want *SequenceLock want *SequenceLock
}{ }{
// A transaction with a single input with max sequence number. // A transaction with a single input with max sequence number.
@ -462,7 +461,6 @@ func TestCalcSequenceLock(t *testing.T) {
name: "single input, unconfirmed, lock-time in blocks", name: "single input, unconfirmed, lock-time in blocks",
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil), tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil),
utxoSet: utxoSet, utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{ want: &SequenceLock{
Milliseconds: -1, Milliseconds: -1,
BlockBlueScore: int64(nextBlockBlueScore) + 1, BlockBlueScore: int64(nextBlockBlueScore) + 1,
@ -475,7 +473,6 @@ func TestCalcSequenceLock(t *testing.T) {
name: "single input, unconfirmed, lock-time in milliseoncds", name: "single input, unconfirmed, lock-time in milliseoncds",
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil), tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil),
utxoSet: utxoSet, utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{ want: &SequenceLock{
Milliseconds: nextMedianTime + 1048575, Milliseconds: nextMedianTime + 1048575,
BlockBlueScore: -1, BlockBlueScore: -1,
@ -486,7 +483,7 @@ func TestCalcSequenceLock(t *testing.T) {
t.Logf("Running %v SequenceLock tests", len(tests)) t.Logf("Running %v SequenceLock tests", len(tests))
for _, test := range tests { for _, test := range tests {
utilTx := util.NewTx(test.tx) utilTx := util.NewTx(test.tx)
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet, test.mempool) seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet)
if err != nil { if err != nil {
t.Fatalf("test '%s', unable to calc sequence lock: %v", test.name, err) t.Fatalf("test '%s', unable to calc sequence lock: %v", test.name, err)
} }
@ -1124,21 +1121,6 @@ func TestDAGIndexFailedStatus(t *testing.T) {
} }
} }
func TestIsDAGCurrentMaxDiff(t *testing.T) {
netParams := []*dagconfig.Params{
&dagconfig.MainnetParams,
&dagconfig.TestnetParams,
&dagconfig.DevnetParams,
&dagconfig.RegressionNetParams,
&dagconfig.SimnetParams,
}
for _, params := range netParams {
if params.FinalityDuration < isDAGCurrentMaxDiff*params.TargetTimePerBlock {
t.Errorf("in %s, a DAG can be considered current even if it's below the finality point", params.Name)
}
}
}
func testProcessBlockRuleError(t *testing.T, dag *BlockDAG, block *domainmessage.MsgBlock, expectedRuleErr error) { func testProcessBlockRuleError(t *testing.T, dag *BlockDAG, block *domainmessage.MsgBlock, expectedRuleErr error) {
isOrphan, isDelayed, err := dag.ProcessBlock(util.NewBlock(block), BFNoPoWCheck) isOrphan, isDelayed, err := dag.ProcessBlock(util.NewBlock(block), BFNoPoWCheck)

View File

@ -662,3 +662,16 @@ func (dag *BlockDAG) BlockHashesFrom(lowHash *daghash.Hash, limit int) ([]*dagha
return blockHashes, nil return blockHashes, nil
} }
func (dag *BlockDAG) fetchBlueBlocks(node *blockNode) ([]*util.Block, error) {
blueBlocks := make([]*util.Block, len(node.blues))
for i, blueBlockNode := range node.blues {
blueBlock, err := dag.fetchBlockByHash(blueBlockNode.hash)
if err != nil {
return nil, err
}
blueBlocks[i] = blueBlock
}
return blueBlocks, nil
}

View File

@ -0,0 +1,95 @@
package blockdag
import (
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"time"
)
// delayedBlock represents a block which has a delayed timestamp and will be processed at processTime
type delayedBlock struct {
block *util.Block
processTime mstime.Time
}
func (dag *BlockDAG) isKnownDelayedBlock(hash *daghash.Hash) bool {
_, exists := dag.delayedBlocks[*hash]
return exists
}
func (dag *BlockDAG) addDelayedBlock(block *util.Block, delay time.Duration) error {
processTime := dag.Now().Add(delay)
log.Debugf("Adding block to delayed blocks queue (block hash: %s, process time: %s)", block.Hash().String(), processTime)
delayedBlock := &delayedBlock{
block: block,
processTime: processTime,
}
dag.delayedBlocks[*block.Hash()] = delayedBlock
dag.delayedBlocksQueue.Push(delayedBlock)
return dag.processDelayedBlocks()
}
// processDelayedBlocks loops over all delayed blocks and processes blocks which are due.
// This method is invoked after processing a block (ProcessBlock method).
func (dag *BlockDAG) processDelayedBlocks() error {
// Check if the delayed block with the earliest process time should be processed
for dag.delayedBlocksQueue.Len() > 0 {
earliestDelayedBlockProcessTime := dag.peekDelayedBlock().processTime
if earliestDelayedBlockProcessTime.After(dag.Now()) {
break
}
delayedBlock := dag.popDelayedBlock()
_, _, err := dag.processBlockNoLock(delayedBlock.block, BFAfterDelay)
if err != nil {
log.Errorf("Error while processing delayed block (block %s): %s", delayedBlock.block.Hash().String(), err)
// Rule errors should not be propagated as they refer only to the delayed block,
// while this function runs in the context of another block
if !errors.As(err, &RuleError{}) {
return err
}
}
log.Debugf("Processed delayed block (block %s)", delayedBlock.block.Hash().String())
}
return nil
}
// popDelayedBlock removes the topmost (delayed block with earliest process time) of the queue and returns it.
func (dag *BlockDAG) popDelayedBlock() *delayedBlock {
delayedBlock := dag.delayedBlocksQueue.pop()
delete(dag.delayedBlocks, *delayedBlock.block.Hash())
return delayedBlock
}
func (dag *BlockDAG) peekDelayedBlock() *delayedBlock {
return dag.delayedBlocksQueue.peek()
}
// maxDelayOfParents returns the maximum delay of the given block hashes.
// Note that delay could be 0, but isDelayed will return true. This is the case where the parent process time is due.
func (dag *BlockDAG) maxDelayOfParents(parentHashes []*daghash.Hash) (delay time.Duration, isDelayed bool) {
for _, parentHash := range parentHashes {
if delayedParent, exists := dag.delayedBlocks[*parentHash]; exists {
isDelayed = true
parentDelay := delayedParent.processTime.Sub(dag.Now())
if parentDelay > delay {
delay = parentDelay
}
}
}
return delay, isDelayed
}
func (dag *BlockDAG) shouldBlockBeDelayed(block *util.Block) (delay time.Duration, isDelayed bool) {
header := &block.MsgBlock().Header
maxTimestamp := dag.Now().Add(time.Duration(dag.TimestampDeviationTolerance) * dag.Params.TargetTimePerBlock)
if header.Timestamp.After(maxTimestamp) {
return header.Timestamp.Sub(maxTimestamp), true
}
return 0, false
}

View File

@ -0,0 +1,32 @@
package blockdag
import (
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/util"
"testing"
"time"
)
func TestShouldBlockBeDelayed(t *testing.T) {
// Create a new database and dag instance to run tests against.
dag, teardownFunc, err := DAGSetup("TestShouldBlockBeDelayed", true, Config{
DAGParams: &dagconfig.SimnetParams,
})
if err != nil {
t.Errorf("Failed to setup dag instance: %v", err)
return
}
defer teardownFunc()
blockInTheFuture := Block100000
expectedDelay := 10 * time.Second
deviationTolerance := time.Duration(dag.TimestampDeviationTolerance) * dag.Params.TargetTimePerBlock
blockInTheFuture.Header.Timestamp = dag.Now().Add(deviationTolerance + expectedDelay)
delay, isDelayed := dag.shouldBlockBeDelayed(util.NewBlock(&blockInTheFuture))
if !isDelayed {
t.Errorf("TestShouldBlockBeDelayed: block unexpectedly not delayed")
}
if delay != expectedDelay {
t.Errorf("TestShouldBlockBeDelayed: expected %s delay but got %s", expectedDelay, delay)
}
}

113
blockdag/finality.go Normal file
View File

@ -0,0 +1,113 @@
package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/util/daghash"
)
// LastFinalityPointHash returns the hash of the last finality point
func (dag *BlockDAG) LastFinalityPointHash() *daghash.Hash {
if dag.lastFinalityPoint == nil {
return nil
}
return dag.lastFinalityPoint.hash
}
// FinalityInterval is the interval that determines the finality window of the DAG.
func (dag *BlockDAG) FinalityInterval() uint64 {
return uint64(dag.Params.FinalityDuration / dag.Params.TargetTimePerBlock)
}
// checkFinalityViolation checks the new block does not violate the finality rules
// specifically - the new block selectedParent chain should contain the old finality point.
func (dag *BlockDAG) checkFinalityViolation(newNode *blockNode) error {
// the genesis block can not violate finality rules
if newNode.isGenesis() {
return nil
}
// Because newNode doesn't have reachability data we
// need to check if the last finality point is in the
// selected parent chain of newNode.selectedParent, so
// we explicitly check if newNode.selectedParent is
// the finality point.
if dag.lastFinalityPoint == newNode.selectedParent {
return nil
}
isInSelectedChain, err := dag.isInSelectedParentChainOf(dag.lastFinalityPoint, newNode.selectedParent)
if err != nil {
return err
}
if !isInSelectedChain {
return ruleError(ErrFinality, "the last finality point is not in the selected parent chain of this block")
}
return nil
}
// updateFinalityPoint updates the dag's last finality point if necessary.
func (dag *BlockDAG) updateFinalityPoint() {
selectedTip := dag.selectedTip()
// if the selected tip is the genesis block - it should be the new finality point
if selectedTip.isGenesis() {
dag.lastFinalityPoint = selectedTip
return
}
// We are looking for a new finality point only if the new block's finality score is higher
// by 2 than the existing finality point's
if selectedTip.finalityScore(dag) < dag.lastFinalityPoint.finalityScore(dag)+2 {
return
}
var currentNode *blockNode
for currentNode = selectedTip.selectedParent; ; currentNode = currentNode.selectedParent {
// We look for the first node in the selected parent chain that has a higher finality score than the last finality point.
if currentNode.selectedParent.finalityScore(dag) == dag.lastFinalityPoint.finalityScore(dag) {
break
}
}
dag.lastFinalityPoint = currentNode
spawn("dag.finalizeNodesBelowFinalityPoint", func() {
dag.finalizeNodesBelowFinalityPoint(true)
})
}
func (dag *BlockDAG) finalizeNodesBelowFinalityPoint(deleteDiffData bool) {
queue := make([]*blockNode, 0, len(dag.lastFinalityPoint.parents))
for parent := range dag.lastFinalityPoint.parents {
queue = append(queue, parent)
}
var nodesToDelete []*blockNode
if deleteDiffData {
nodesToDelete = make([]*blockNode, 0, dag.FinalityInterval())
}
for len(queue) > 0 {
var current *blockNode
current, queue = queue[0], queue[1:]
if !current.isFinalized {
current.isFinalized = true
if deleteDiffData {
nodesToDelete = append(nodesToDelete, current)
}
for parent := range current.parents {
queue = append(queue, parent)
}
}
}
if deleteDiffData {
err := dag.utxoDiffStore.removeBlocksDiffData(dag.databaseContext, nodesToDelete)
if err != nil {
panic(fmt.Sprintf("Error removing diff data from utxoDiffStore: %s", err))
}
}
}
// IsKnownFinalizedBlock returns whether the block is below the finality point.
// IsKnownFinalizedBlock might be false-negative because node finality status is
// updated in a separate goroutine. To get a definite answer if a block
// is finalized or not, use dag.checkFinalityViolation.
func (dag *BlockDAG) IsKnownFinalizedBlock(blockHash *daghash.Hash) bool {
node, ok := dag.index.LookupNode(blockHash)
return ok && node.isFinalized
}

18
blockdag/index_manager.go Normal file
View File

@ -0,0 +1,18 @@
package blockdag
import (
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/util/daghash"
)
// IndexManager provides a generic interface that is called when blocks are
// connected to the DAG for the purpose of supporting optional indexes.
type IndexManager interface {
// Init is invoked during DAG initialize in order to allow the index
// manager to initialize itself and any indexes it is managing.
Init(*BlockDAG, *dbaccess.DatabaseContext) error
// ConnectBlock is invoked when a new block has been connected to the
// DAG.
ConnectBlock(dbContext *dbaccess.TxContext, blockHash *daghash.Hash, acceptedTxsData MultiBlockTxsAcceptanceData) error
}

View File

@ -5,7 +5,9 @@
package blockdag package blockdag
import ( import (
"fmt"
"math" "math"
"sort"
"github.com/kaspanet/kaspad/util" "github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash" "github.com/kaspanet/kaspad/util/daghash"
@ -132,3 +134,49 @@ func buildMerkleTreeStore(hashes []*daghash.Hash) MerkleTree {
return merkles return merkles
} }
func calculateAcceptedIDMerkleRoot(multiBlockTxsAcceptanceData MultiBlockTxsAcceptanceData) *daghash.Hash {
var acceptedTxs []*util.Tx
for _, blockTxsAcceptanceData := range multiBlockTxsAcceptanceData {
for _, txAcceptance := range blockTxsAcceptanceData.TxAcceptanceData {
if !txAcceptance.IsAccepted {
continue
}
acceptedTxs = append(acceptedTxs, txAcceptance.Tx)
}
}
sort.Slice(acceptedTxs, func(i, j int) bool {
return daghash.LessTxID(acceptedTxs[i].ID(), acceptedTxs[j].ID())
})
acceptedIDMerkleTree := BuildIDMerkleTreeStore(acceptedTxs)
return acceptedIDMerkleTree.Root()
}
func (node *blockNode) validateAcceptedIDMerkleRoot(dag *BlockDAG, txsAcceptanceData MultiBlockTxsAcceptanceData) error {
if node.isGenesis() {
return nil
}
calculatedAccepetedIDMerkleRoot := calculateAcceptedIDMerkleRoot(txsAcceptanceData)
header := node.Header()
if !header.AcceptedIDMerkleRoot.IsEqual(calculatedAccepetedIDMerkleRoot) {
str := fmt.Sprintf("block accepted ID merkle root is invalid - block "+
"header indicates %s, but calculated value is %s",
header.AcceptedIDMerkleRoot, calculatedAccepetedIDMerkleRoot)
return ruleError(ErrBadMerkleRoot, str)
}
return nil
}
// NextAcceptedIDMerkleRootNoLock prepares the acceptedIDMerkleRoot for the next mined block
//
// This function MUST be called with the DAG read-lock held
func (dag *BlockDAG) NextAcceptedIDMerkleRootNoLock() (*daghash.Hash, error) {
txsAcceptanceData, err := dag.TxsAcceptedByVirtual()
if err != nil {
return nil, err
}
return calculateAcceptedIDMerkleRoot(txsAcceptanceData), nil
}

View File

@ -3,6 +3,7 @@ package blockdag
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"math"
"time" "time"
"github.com/kaspanet/go-secp256k1" "github.com/kaspanet/go-secp256k1"
@ -13,6 +14,9 @@ import (
"github.com/kaspanet/kaspad/util/mstime" "github.com/kaspanet/kaspad/util/mstime"
) )
// The current block version
const blockVersion = 0x10000000
// BlockForMining returns a block with the given transactions // BlockForMining returns a block with the given transactions
// that points to the current DAG tips, that is valid from // that points to the current DAG tips, that is valid from
// all aspects except proof of work. // all aspects except proof of work.
@ -22,13 +26,6 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*domainmessage.Msg
blockTimestamp := dag.NextBlockTime() blockTimestamp := dag.NextBlockTime()
requiredDifficulty := dag.NextRequiredDifficulty(blockTimestamp) requiredDifficulty := dag.NextRequiredDifficulty(blockTimestamp)
// Calculate the next expected block version based on the state of the
// rule change deployments.
nextBlockVersion, err := dag.CalcNextBlockVersion()
if err != nil {
return nil, err
}
// Create a new block ready to be solved. // Create a new block ready to be solved.
hashMerkleTree := BuildHashMerkleTreeStore(transactions) hashMerkleTree := BuildHashMerkleTreeStore(transactions)
acceptedIDMerkleRoot, err := dag.NextAcceptedIDMerkleRootNoLock() acceptedIDMerkleRoot, err := dag.NextAcceptedIDMerkleRootNoLock()
@ -46,7 +43,7 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*domainmessage.Msg
} }
msgBlock.Header = domainmessage.BlockHeader{ msgBlock.Header = domainmessage.BlockHeader{
Version: nextBlockVersion, Version: blockVersion,
ParentHashes: dag.TipHashes(), ParentHashes: dag.TipHashes(),
HashMerkleRoot: hashMerkleTree.Root(), HashMerkleRoot: hashMerkleTree.Root(),
AcceptedIDMerkleRoot: acceptedIDMerkleRoot, AcceptedIDMerkleRoot: acceptedIDMerkleRoot,
@ -127,3 +124,15 @@ func (dag *BlockDAG) NextBlockTime() mstime.Time {
return newTimestamp return newTimestamp
} }
// CurrentBits returns the bits of the tip with the lowest bits, which also means it has highest difficulty.
func (dag *BlockDAG) CurrentBits() uint32 {
tips := dag.virtual.tips()
minBits := uint32(math.MaxUint32)
for tip := range tips {
if minBits > tip.Header().Bits {
minBits = tip.Header().Bits
}
}
return minBits
}

84
blockdag/multiset.go Normal file
View File

@ -0,0 +1,84 @@
package blockdag
import (
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/domainmessage"
"github.com/pkg/errors"
)
// 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 _, 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
}
// 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
}
ms, err := dag.multisetStore.multisetByBlockNode(node.selectedParent)
if err != nil {
return nil, err
}
return ms, nil
}
func addTxToMultiset(ms *secp256k1.MultiSet, tx *domainmessage.MsgTx, pastUTXO UTXOSet, blockBlueScore uint64) (*secp256k1.MultiSet, error) {
for _, txIn := range tx.TxIn {
entry, ok := pastUTXO.Get(txIn.PreviousOutpoint)
if !ok {
return nil, errors.Errorf("Couldn't find entry for outpoint %s", txIn.PreviousOutpoint)
}
var err error
ms, err = removeUTXOFromMultiset(ms, entry, &txIn.PreviousOutpoint)
if err != nil {
return nil, err
}
}
isCoinbase := tx.IsCoinBase()
for i, txOut := range tx.TxOut {
outpoint := *domainmessage.NewOutpoint(tx.TxID(), uint32(i))
entry := NewUTXOEntry(txOut, isCoinbase, blockBlueScore)
var err error
ms, err = addUTXOToMultiset(ms, entry, &outpoint)
if err != nil {
return nil, err
}
}
return ms, nil
}

235
blockdag/orphan_blocks.go Normal file
View File

@ -0,0 +1,235 @@
package blockdag
import (
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"time"
)
// maxOrphanBlocks is the maximum number of orphan blocks that can be
// queued.
const maxOrphanBlocks = 100
// orphanBlock represents a block that we don't yet have the parent for. It
// is a normal block plus an expiration time to prevent caching the orphan
// forever.
type orphanBlock struct {
block *util.Block
expiration mstime.Time
}
// IsKnownOrphan returns whether the passed hash is currently a known orphan.
// Keep in mind that only a limited number of orphans are held onto for a
// limited amount of time, so this function must not be used as an absolute
// way to test if a block is an orphan block. A full block (as opposed to just
// its hash) must be passed to ProcessBlock for that purpose. However, calling
// ProcessBlock with an orphan that already exists results in an error, so this
// function provides a mechanism for a caller to intelligently detect *recent*
// duplicate orphans and react accordingly.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) IsKnownOrphan(hash *daghash.Hash) bool {
// Protect concurrent access. Using a read lock only so multiple
// readers can query without blocking each other.
dag.orphanLock.RLock()
defer dag.orphanLock.RUnlock()
_, exists := dag.orphans[*hash]
return exists
}
// GetOrphanMissingAncestorHashes returns all of the missing parents in the orphan's sub-DAG
//
// This function is safe for concurrent access.
func (dag *BlockDAG) GetOrphanMissingAncestorHashes(orphanHash *daghash.Hash) []*daghash.Hash {
// Protect concurrent access. Using a read lock only so multiple
// readers can query without blocking each other.
dag.orphanLock.RLock()
defer dag.orphanLock.RUnlock()
missingAncestorsHashes := make([]*daghash.Hash, 0)
visited := make(map[daghash.Hash]bool)
queue := []*daghash.Hash{orphanHash}
for len(queue) > 0 {
var current *daghash.Hash
current, queue = queue[0], queue[1:]
if !visited[*current] {
visited[*current] = true
orphan, orphanExists := dag.orphans[*current]
if orphanExists {
queue = append(queue, orphan.block.MsgBlock().Header.ParentHashes...)
} else {
if !dag.IsInDAG(current) && current != orphanHash {
missingAncestorsHashes = append(missingAncestorsHashes, current)
}
}
}
}
return missingAncestorsHashes
}
// removeOrphanBlock removes the passed orphan block from the orphan pool and
// previous orphan index.
func (dag *BlockDAG) removeOrphanBlock(orphan *orphanBlock) {
// Protect concurrent access.
dag.orphanLock.Lock()
defer dag.orphanLock.Unlock()
// Remove the orphan block from the orphan pool.
orphanHash := orphan.block.Hash()
delete(dag.orphans, *orphanHash)
// Remove the reference from the previous orphan index too.
for _, parentHash := range orphan.block.MsgBlock().Header.ParentHashes {
// An indexing for loop is intentionally used over a range here as range
// does not reevaluate the slice on each iteration nor does it adjust the
// index for the modified slice.
orphans := dag.prevOrphans[*parentHash]
for i := 0; i < len(orphans); i++ {
hash := orphans[i].block.Hash()
if hash.IsEqual(orphanHash) {
orphans = append(orphans[:i], orphans[i+1:]...)
i--
}
}
// Remove the map entry altogether if there are no longer any orphans
// which depend on the parent hash.
if len(orphans) == 0 {
delete(dag.prevOrphans, *parentHash)
continue
}
dag.prevOrphans[*parentHash] = orphans
}
}
// addOrphanBlock adds the passed block (which is already determined to be
// an orphan prior calling this function) to the orphan pool. It lazily cleans
// up any expired blocks so a separate cleanup poller doesn't need to be run.
// It also imposes a maximum limit on the number of outstanding orphan
// blocks and will remove the oldest received orphan block if the limit is
// exceeded.
func (dag *BlockDAG) addOrphanBlock(block *util.Block) {
log.Infof("Adding orphan block %s", block.Hash())
// Remove expired orphan blocks.
for _, oBlock := range dag.orphans {
if mstime.Now().After(oBlock.expiration) {
dag.removeOrphanBlock(oBlock)
continue
}
// Update the newest orphan block pointer so it can be discarded
// in case the orphan pool fills up.
if dag.newestOrphan == nil || oBlock.block.Timestamp().After(dag.newestOrphan.block.Timestamp()) {
dag.newestOrphan = oBlock
}
}
// Limit orphan blocks to prevent memory exhaustion.
if len(dag.orphans)+1 > maxOrphanBlocks {
// If the new orphan is newer than the newest orphan on the orphan
// pool, don't add it.
if block.Timestamp().After(dag.newestOrphan.block.Timestamp()) {
return
}
// Remove the newest orphan to make room for the added one.
dag.removeOrphanBlock(dag.newestOrphan)
dag.newestOrphan = nil
}
// Protect concurrent access. This is intentionally done here instead
// of near the top since removeOrphanBlock does its own locking and
// the range iterator is not invalidated by removing map entries.
dag.orphanLock.Lock()
defer dag.orphanLock.Unlock()
// Insert the block into the orphan map with an expiration time
// 1 hour from now.
expiration := mstime.Now().Add(time.Hour)
oBlock := &orphanBlock{
block: block,
expiration: expiration,
}
dag.orphans[*block.Hash()] = oBlock
// Add to parent hash lookup index for faster dependency lookups.
for _, parentHash := range block.MsgBlock().Header.ParentHashes {
dag.prevOrphans[*parentHash] = append(dag.prevOrphans[*parentHash], oBlock)
}
}
// processOrphans determines if there are any orphans which depend on the passed
// block hash (they are no longer orphans if true) and potentially accepts them.
// It repeats the process for the newly accepted blocks (to detect further
// orphans which may no longer be orphans) until there are no more.
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to maybeAcceptBlock.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) processOrphans(hash *daghash.Hash, flags BehaviorFlags) error {
// Start with processing at least the passed hash. Leave a little room
// for additional orphan blocks that need to be processed without
// needing to grow the array in the common case.
processHashes := make([]*daghash.Hash, 0, 10)
processHashes = append(processHashes, hash)
for len(processHashes) > 0 {
// Pop the first hash to process from the slice.
processHash := processHashes[0]
processHashes[0] = nil // Prevent GC leak.
processHashes = processHashes[1:]
// Look up all orphans that are parented by the block we just
// accepted. An indexing for loop is
// intentionally used over a range here as range does not
// reevaluate the slice on each iteration nor does it adjust the
// index for the modified slice.
for i := 0; i < len(dag.prevOrphans[*processHash]); i++ {
orphan := dag.prevOrphans[*processHash][i]
if orphan == nil {
log.Warnf("Found a nil entry at index %d in the "+
"orphan dependency list for block %s", i,
processHash)
continue
}
// Skip this orphan if one or more of its parents are
// still missing.
_, err := lookupParentNodes(orphan.block, dag)
if err != nil {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrParentBlockUnknown {
continue
}
return err
}
// Remove the orphan from the orphan pool.
orphanHash := orphan.block.Hash()
dag.removeOrphanBlock(orphan)
i--
// Potentially accept the block into the block DAG.
err = dag.maybeAcceptBlock(orphan.block, flags|BFWasUnorphaned)
if err != nil {
// Since we don't want to reject the original block because of
// a bad unorphaned child, only return an error if it's not a RuleError.
if !errors.As(err, &RuleError{}) {
return err
}
log.Warnf("Verification failed for orphan block %s: %s", orphanHash, err)
}
// Add this block to the list of blocks to process so
// any orphan blocks that depend on this block are
// handled too.
processHashes = append(processHashes, orphanHash)
}
}
return nil
}

View File

@ -1,140 +1,20 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockdag package blockdag
import ( import (
"fmt" "fmt"
"time" "github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/util" "github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash" "github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
"time"
) )
// BehaviorFlags is a bitmask defining tweaks to the normal behavior when // chainUpdates represents the updates made to the selected parent chain after
// performing DAG processing and consensus rules checks. // a block had been added to the DAG.
type BehaviorFlags uint32 type chainUpdates struct {
removedChainBlockHashes []*daghash.Hash
const ( addedChainBlockHashes []*daghash.Hash
// BFFastAdd may be set to indicate that several checks can be avoided
// for the block since it is already known to fit into the DAG due to
// already proving it correct links into the DAG.
BFFastAdd BehaviorFlags = 1 << iota
// BFNoPoWCheck may be set to indicate the proof of work check which
// ensures a block hashes to a value less than the required target will
// not be performed.
BFNoPoWCheck
// BFWasUnorphaned may be set to indicate that a block was just now
// unorphaned
BFWasUnorphaned
// BFAfterDelay may be set to indicate that a block had timestamp too far
// in the future, just finished the delay
BFAfterDelay
// BFIsSync may be set to indicate that the block was sent as part of the
// netsync process
BFIsSync
// BFWasStored is set to indicate that the block was previously stored
// in the block index but was never fully processed
BFWasStored
// BFDisallowDelay is set to indicate that a delayed block should be rejected.
// This is used for the case where a block is submitted through RPC.
BFDisallowDelay
// BFDisallowOrphans is set to indicate that an orphan block should be rejected.
// This is used for the case where a block is submitted through RPC.
BFDisallowOrphans
// BFNone is a convenience value to specifically indicate no flags.
BFNone BehaviorFlags = 0
)
// IsInDAG determines whether a block with the given hash exists in
// the DAG.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) IsInDAG(hash *daghash.Hash) bool {
return dag.index.HaveBlock(hash)
}
// processOrphans determines if there are any orphans which depend on the passed
// block hash (they are no longer orphans if true) and potentially accepts them.
// It repeats the process for the newly accepted blocks (to detect further
// orphans which may no longer be orphans) until there are no more.
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to maybeAcceptBlock.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) processOrphans(hash *daghash.Hash, flags BehaviorFlags) error {
// Start with processing at least the passed hash. Leave a little room
// for additional orphan blocks that need to be processed without
// needing to grow the array in the common case.
processHashes := make([]*daghash.Hash, 0, 10)
processHashes = append(processHashes, hash)
for len(processHashes) > 0 {
// Pop the first hash to process from the slice.
processHash := processHashes[0]
processHashes[0] = nil // Prevent GC leak.
processHashes = processHashes[1:]
// Look up all orphans that are parented by the block we just
// accepted. An indexing for loop is
// intentionally used over a range here as range does not
// reevaluate the slice on each iteration nor does it adjust the
// index for the modified slice.
for i := 0; i < len(dag.prevOrphans[*processHash]); i++ {
orphan := dag.prevOrphans[*processHash][i]
if orphan == nil {
log.Warnf("Found a nil entry at index %d in the "+
"orphan dependency list for block %s", i,
processHash)
continue
}
// Skip this orphan if one or more of its parents are
// still missing.
_, err := lookupParentNodes(orphan.block, dag)
if err != nil {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrParentBlockUnknown {
continue
}
return err
}
// Remove the orphan from the orphan pool.
orphanHash := orphan.block.Hash()
dag.removeOrphanBlock(orphan)
i--
// Potentially accept the block into the block DAG.
err = dag.maybeAcceptBlock(orphan.block, flags|BFWasUnorphaned)
if err != nil {
// Since we don't want to reject the original block because of
// a bad unorphaned child, only return an error if it's not a RuleError.
if !errors.As(err, &RuleError{}) {
return err
}
log.Warnf("Verification failed for orphan block %s: %s", orphanHash, err)
}
// Add this block to the list of blocks to process so
// any orphan blocks that depend on this block are
// handled too.
processHashes = append(processHashes, orphanHash)
}
}
return nil
} }
// ProcessBlock is the main workhorse for handling insertion of new blocks into // ProcessBlock is the main workhorse for handling insertion of new blocks into
@ -142,9 +22,6 @@ func (dag *BlockDAG) processOrphans(hash *daghash.Hash, flags BehaviorFlags) err
// blocks, ensuring blocks follow all rules, orphan handling, and insertion into // blocks, ensuring blocks follow all rules, orphan handling, and insertion into
// the block DAG. // the block DAG.
// //
// When no errors occurred during processing, the first return value indicates
// whether or not the block is an orphan.
//
// This function is safe for concurrent access. // This function is safe for concurrent access.
func (dag *BlockDAG) ProcessBlock(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) { func (dag *BlockDAG) ProcessBlock(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
dag.dagLock.Lock() dag.dagLock.Lock()
@ -153,60 +30,82 @@ func (dag *BlockDAG) ProcessBlock(block *util.Block, flags BehaviorFlags) (isOrp
} }
func (dag *BlockDAG) processBlockNoLock(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) { func (dag *BlockDAG) processBlockNoLock(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
isAfterDelay := flags&BFAfterDelay == BFAfterDelay
wasBlockStored := flags&BFWasStored == BFWasStored
disallowDelay := flags&BFDisallowDelay == BFDisallowDelay
disallowOrphans := flags&BFDisallowOrphans == BFDisallowOrphans
blockHash := block.Hash() blockHash := block.Hash()
log.Tracef("Processing block %s", blockHash) log.Tracef("Processing block %s", blockHash)
// The block must not already exist in the DAG. err = dag.checkDuplicateBlock(blockHash, flags)
if dag.IsInDAG(blockHash) && !wasBlockStored { if err != nil {
str := fmt.Sprintf("already have block %s", blockHash) return false, false, err
return false, false, ruleError(ErrDuplicateBlock, str)
} }
// The block must not already exist as an orphan. err = dag.checkBlockSanity(block, flags)
if _, exists := dag.orphans[*blockHash]; exists { if err != nil {
str := fmt.Sprintf("already have block (orphan) %s", blockHash) return false, false, err
return false, false, ruleError(ErrDuplicateBlock, str)
} }
if dag.isKnownDelayedBlock(blockHash) { isOrphan, isDelayed, err = dag.checkDelayedAndOrphanBlocks(block, flags)
str := fmt.Sprintf("already have block (delayed) %s", blockHash) if isOrphan || isDelayed || err != nil {
return false, false, ruleError(ErrDuplicateBlock, str) return isOrphan, isDelayed, err
} }
if !isAfterDelay { err = dag.maybeAcceptBlock(block, flags)
// Perform preliminary sanity checks on the block and its transactions. if err != nil {
delay, err := dag.checkBlockSanity(block, flags) return false, false, err
}
err = dag.processOrphansAndDelayedBlocks(blockHash, flags)
if err != nil {
return false, false, err
}
log.Debugf("Accepted block %s", blockHash)
return false, false, nil
}
func (dag *BlockDAG) checkDelayedAndOrphanBlocks(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
if !isBehaviorFlagRaised(flags, BFAfterDelay) {
isDelayed, err := dag.checkBlockDelay(block, flags)
if err != nil { if err != nil {
return false, false, err return false, false, err
} }
if isDelayed {
if delay != 0 && disallowDelay {
str := fmt.Sprintf("Cannot process blocks beyond the allowed time offset while the BFDisallowDelay flag is raised %s", blockHash)
return false, true, ruleError(ErrDelayedBlockIsNotAllowed, str)
}
if delay != 0 {
err = dag.addDelayedBlock(block, delay)
if err != nil {
return false, false, err
}
return false, true, nil return false, true, nil
} }
} }
return dag.checkMissingParents(block, flags)
}
func (dag *BlockDAG) checkBlockDelay(block *util.Block, flags BehaviorFlags) (isDelayed bool, err error) {
delay, isDelayed := dag.shouldBlockBeDelayed(block)
if isDelayed && isBehaviorFlagRaised(flags, BFDisallowDelay) {
str := fmt.Sprintf("cannot process blocks beyond the "+
"allowed time offset while the BFDisallowDelay flag is "+
"raised %s", block.Hash())
return false, ruleError(ErrDelayedBlockIsNotAllowed, str)
}
if isDelayed {
err := dag.addDelayedBlock(block, delay)
if err != nil {
return false, err
}
return true, nil
}
return false, nil
}
func (dag *BlockDAG) checkMissingParents(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
var missingParents []*daghash.Hash var missingParents []*daghash.Hash
for _, parentHash := range block.MsgBlock().Header.ParentHashes { for _, parentHash := range block.MsgBlock().Header.ParentHashes {
if !dag.IsInDAG(parentHash) { if !dag.IsInDAG(parentHash) {
missingParents = append(missingParents, parentHash) missingParents = append(missingParents, parentHash)
} }
} }
if len(missingParents) > 0 && disallowOrphans {
str := fmt.Sprintf("Cannot process orphan blocks while the BFDisallowOrphans flag is raised %s", blockHash) if len(missingParents) > 0 && isBehaviorFlagRaised(flags, BFDisallowOrphans) {
str := fmt.Sprintf("cannot process orphan blocks while the "+
"BFDisallowOrphans flag is raised %s", block.Hash())
return false, false, ruleError(ErrOrphanBlockIsNotAllowed, str) return false, false, ruleError(ErrOrphanBlockIsNotAllowed, str)
} }
@ -219,69 +118,341 @@ func (dag *BlockDAG) processBlockNoLock(block *util.Block, flags BehaviorFlags)
if err != nil { if err != nil {
return false, false, err return false, false, err
} }
return false, true, err return false, true, nil
} }
// Handle orphan blocks. // Handle orphan blocks.
if len(missingParents) > 0 { if len(missingParents) > 0 {
// Some orphans during netsync are a normal part of the process, since the anticone
// of the chain-split is never explicitly requested.
// Therefore, if we are during netsync - don't report orphans to default logs.
//
// The number K*2 was chosen since in peace times anticone is limited to K blocks,
// while some red block can make it a bit bigger, but much more than that indicates
// there might be some problem with the netsync process.
if flags&BFIsSync == BFIsSync && dagconfig.KType(len(dag.orphans)) < dag.Params.K*2 {
log.Debugf("Adding orphan block %s. This is normal part of netsync process", blockHash)
} else {
log.Infof("Adding orphan block %s", blockHash)
}
dag.addOrphanBlock(block) dag.addOrphanBlock(block)
return true, false, nil return true, false, nil
} }
// The block has passed all context independent checks and appears sane
// enough to potentially accept it into the block DAG.
err = dag.maybeAcceptBlock(block, flags)
if err != nil {
return false, false, err
}
// Accept any orphan blocks that depend on this block (they are
// no longer orphans) and repeat for those accepted blocks until
// there are no more.
err = dag.processOrphans(blockHash, flags)
if err != nil {
return false, false, err
}
if !isAfterDelay {
err = dag.processDelayedBlocks()
if err != nil {
return false, false, err
}
}
dag.addBlockProcessingTimestamp()
log.Debugf("Accepted block %s", blockHash)
return false, false, nil return false, false, nil
} }
// maxDelayOfParents returns the maximum delay of the given block hashes. func (dag *BlockDAG) processOrphansAndDelayedBlocks(blockHash *daghash.Hash, flags BehaviorFlags) error {
// Note that delay could be 0, but isDelayed will return true. This is the case where the parent process time is due. err := dag.processOrphans(blockHash, flags)
func (dag *BlockDAG) maxDelayOfParents(parentHashes []*daghash.Hash) (delay time.Duration, isDelayed bool) { if err != nil {
for _, parentHash := range parentHashes { return err
if delayedParent, exists := dag.delayedBlocks[*parentHash]; exists { }
isDelayed = true
parentDelay := delayedParent.processTime.Sub(dag.Now()) if !isBehaviorFlagRaised(flags, BFAfterDelay) {
if parentDelay > delay { err = dag.processDelayedBlocks()
delay = parentDelay if err != nil {
} return err
}
}
return nil
}
// maybeAcceptBlock potentially accepts a block into the block DAG. It
// performs several validation checks which depend on its position within
// the block DAG before adding it. The block is expected to have already
// gone through ProcessBlock before calling this function with it.
//
// The flags are also passed to checkBlockContext and connectBlock. See
// their documentation for how the flags modify their behavior.
//
// This function MUST be called with the dagLock held (for writes).
func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) error {
err := dag.checkBlockContext(block, flags)
if err != nil {
return err
}
newNode, selectedParentAnticone, err := dag.createBlockNodeFromBlock(block)
if err != nil {
return err
}
chainUpdates, err := dag.connectBlock(newNode, block, selectedParentAnticone, flags)
if err != nil {
return dag.handleConnectBlockError(err, newNode)
}
dag.notifyBlockAccepted(block, chainUpdates, flags)
return nil
}
// createBlockNodeFromBlock generates a new block node for the given block
// and stores it in the block index with statusDataStored.
func (dag *BlockDAG) createBlockNodeFromBlock(block *util.Block) (
newNode *blockNode, selectedParentAnticone []*blockNode, err error) {
// Create a new block node for the block and add it to the node index.
parents, err := lookupParentNodes(block, dag)
if err != nil {
return nil, nil, err
}
newNode, selectedParentAnticone = dag.newBlockNode(&block.MsgBlock().Header, parents)
newNode.status = statusDataStored
dag.index.AddNode(newNode)
// Insert the block into the database if it's not already there. Even
// though it is possible the block will ultimately fail to connect, it
// has already passed all proof-of-work and validity tests which means
// it would be prohibitively expensive for an attacker to fill up the
// disk with a bunch of blocks that fail to connect. This is necessary
// since it allows block download to be decoupled from the much more
// expensive connection logic. It also has some other nice properties
// such as making blocks that never become part of the DAG or
// blocks that fail to connect available for further analysis.
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
return nil, nil, err
}
defer dbTx.RollbackUnlessClosed()
blockExists, err := dbaccess.HasBlock(dbTx, block.Hash())
if err != nil {
return nil, nil, err
}
if !blockExists {
err := storeBlock(dbTx, block)
if err != nil {
return nil, nil, err
}
}
err = dag.index.flushToDB(dbTx)
if err != nil {
return nil, nil, err
}
err = dbTx.Commit()
if err != nil {
return nil, nil, err
}
return newNode, selectedParentAnticone, nil
}
// connectBlock handles connecting the passed node/block to the DAG.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) connectBlock(node *blockNode,
block *util.Block, selectedParentAnticone []*blockNode, flags BehaviorFlags) (*chainUpdates, error) {
err := dag.checkBlockTransactionsFinalized(block, node, flags)
if err != nil {
return nil, err
}
if err = dag.checkFinalityViolation(node); err != nil {
return nil, err
}
if err := dag.validateGasLimit(block); err != nil {
return nil, err
}
newBlockPastUTXO, txsAcceptanceData, newBlockFeeData, newBlockMultiSet, err :=
node.verifyAndBuildUTXO(dag, block.Transactions(), isBehaviorFlagRaised(flags, BFFastAdd))
if err != nil {
return nil, errors.Wrapf(err, "error verifying UTXO for %s", node)
}
err = node.validateCoinbaseTransaction(dag, block, txsAcceptanceData)
if err != nil {
return nil, err
}
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
// irrecoverable, and it would be a bad idea to attempt adding any more blocks to the DAG.
// Therefore - in such cases we panic.
panic(err)
}
err = dag.saveChangesFromBlock(block, virtualUTXODiff, txsAcceptanceData, newBlockFeeData)
if err != nil {
return nil, err
}
dag.addBlockProcessingTimestamp()
dag.blockCount++
return chainUpdates, nil
}
// applyDAGChanges does the following:
// 1. Connects each of the new block's parents to the block.
// 2. Adds the new block to the DAG's tips.
// 3. Updates the DAG's full UTXO set.
// 4. Updates each of the tips' utxoDiff.
// 5. Applies the new virtual's blue score to all the unaccepted UTXOs
// 6. Adds the block to the reachability structures
// 7. Adds the multiset of the block to the multiset store.
// 8. Updates the finality point of the DAG (if required).
//
// 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, newBlockPastUTXO UTXOSet,
newBlockMultiset *secp256k1.MultiSet, selectedParentAnticone []*blockNode) (
virtualUTXODiff *UTXODiff, chainUpdates *chainUpdates, err error) {
// Add the block to the reachability tree
err = dag.reachabilityTree.addBlock(node, selectedParentAnticone)
if err != nil {
return nil, nil, errors.Wrap(err, "failed adding block to the reachability tree")
}
dag.multisetStore.setMultiset(node, newBlockMultiset)
if err = node.updateParents(dag, newBlockPastUTXO); err != nil {
return nil, nil, errors.Wrapf(err, "failed updating parents of %s", node)
}
// Update the virtual block's parents (the DAG tips) to include the new block.
chainUpdates = dag.virtual.AddTip(node)
// Build a UTXO set for the new virtual block
newVirtualUTXO, _, _, err := dag.pastUTXO(&dag.virtual.blockNode)
if err != nil {
return nil, nil, errors.Wrap(err, "could not restore past UTXO for virtual")
}
// Apply new utxoDiffs to all the tips
err = updateTipsUTXO(dag, newVirtualUTXO)
if err != nil {
return nil, nil, errors.Wrap(err, "failed updating the tips' UTXO")
}
// It is now safe to meld the UTXO set to base.
diffSet := newVirtualUTXO.(*DiffUTXOSet)
virtualUTXODiff = diffSet.UTXODiff
err = dag.meldVirtualUTXO(diffSet)
if err != nil {
return nil, nil, errors.Wrap(err, "failed melding the virtual UTXO")
}
dag.index.SetStatusFlags(node, statusValid)
// And now we can update the finality point of the DAG (if required)
dag.updateFinalityPoint()
return virtualUTXODiff, chainUpdates, nil
}
func (dag *BlockDAG) saveChangesFromBlock(block *util.Block, virtualUTXODiff *UTXODiff,
txsAcceptanceData MultiBlockTxsAcceptanceData, feeData compactFeeData) error {
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
err = dag.index.flushToDB(dbTx)
if err != nil {
return err
}
err = dag.utxoDiffStore.flushToDB(dbTx)
if err != nil {
return err
}
err = dag.reachabilityTree.storeState(dbTx)
if err != nil {
return err
}
err = dag.multisetStore.flushToDB(dbTx)
if err != nil {
return err
}
// Update DAG state.
state := &dagState{
TipHashes: dag.TipHashes(),
LastFinalityPoint: dag.lastFinalityPoint.hash,
LocalSubnetworkID: dag.subnetworkID,
}
err = saveDAGState(dbTx, state)
if err != nil {
return err
}
// Update the UTXO set using the diffSet that was melded into the
// full UTXO set.
err = updateUTXOSet(dbTx, virtualUTXODiff)
if err != nil {
return err
}
// Scan all accepted transactions and register any subnetwork registry
// transaction. If any subnetwork registry transaction is not well-formed,
// fail the entire block.
err = registerSubnetworks(dbTx, block.Transactions())
if err != nil {
return err
}
// Allow the index manager to call each of the currently active
// optional indexes with the block being connected so they can
// update themselves accordingly.
if dag.indexManager != nil {
err := dag.indexManager.ConnectBlock(dbTx, block.Hash(), txsAcceptanceData)
if err != nil {
return err
} }
} }
return delay, isDelayed // Apply the fee data into the database
err = dbaccess.StoreFeeData(dbTx, block.Hash(), feeData)
if err != nil {
return err
}
err = dbTx.Commit()
if err != nil {
return err
}
dag.index.clearDirtyEntries()
dag.utxoDiffStore.clearDirtyEntries()
dag.utxoDiffStore.clearOldEntries()
dag.reachabilityTree.store.clearDirtyEntries()
dag.multisetStore.clearNewEntries()
return nil
}
func (dag *BlockDAG) handleConnectBlockError(err error, newNode *blockNode) error {
if errors.As(err, &RuleError{}) {
dag.index.SetStatusFlags(newNode, statusValidateFailed)
dbTx, err := dag.databaseContext.NewTx()
if err != nil {
return err
}
defer dbTx.RollbackUnlessClosed()
err = dag.index.flushToDB(dbTx)
if err != nil {
return err
}
err = dbTx.Commit()
if err != nil {
return err
}
}
return err
}
// notifyBlockAccepted notifies the caller that the new block was
// accepted into the block DAG. The caller would typically want to
// react by relaying the inventory to other peers.
func (dag *BlockDAG) notifyBlockAccepted(block *util.Block, chainUpdates *chainUpdates, flags BehaviorFlags) {
dag.sendNotification(NTBlockAdded, &BlockAddedNotificationData{
Block: block,
WasUnorphaned: flags&BFWasUnorphaned != 0,
})
if len(chainUpdates.addedChainBlockHashes) > 0 {
dag.sendNotification(NTChainChanged, &ChainChangedNotificationData{
RemovedChainBlockHashes: chainUpdates.removedChainBlockHashes,
AddedChainBlockHashes: chainUpdates.addedChainBlockHashes,
})
}
} }

View File

@ -1,6 +1,7 @@
package blockdag package blockdag
import ( import (
"github.com/pkg/errors"
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
@ -104,6 +105,9 @@ func TestProcessDelayedBlocks(t *testing.T) {
blockDelay := time.Duration(dag1.Params.TimestampDeviationTolerance)*dag1.Params.TargetTimePerBlock + 5*time.Second blockDelay := time.Duration(dag1.Params.TimestampDeviationTolerance)*dag1.Params.TargetTimePerBlock + 5*time.Second
delayedBlock.Header.Timestamp = initialTime.Add(blockDelay) delayedBlock.Header.Timestamp = initialTime.Add(blockDelay)
// We change the nonce here because processDelayedBlocks always runs without BFNoPoWCheck.
delayedBlock.Header.Nonce = 2
isOrphan, isDelayed, err := dag1.ProcessBlock(util.NewBlock(delayedBlock), BFNoPoWCheck) isOrphan, isDelayed, err := dag1.ProcessBlock(util.NewBlock(delayedBlock), BFNoPoWCheck)
if err != nil { if err != nil {
t.Fatalf("ProcessBlock returned unexpected error: %s\n", err) t.Fatalf("ProcessBlock returned unexpected error: %s\n", err)
@ -236,3 +240,101 @@ func TestProcessDelayedBlocks(t *testing.T) {
t.Errorf("delayedBlockChild shouldn't be added to the DAG because its parent has been added to the DAG") t.Errorf("delayedBlockChild shouldn't be added to the DAG because its parent has been added to the DAG")
} }
} }
func TestMaybeAcceptBlockErrors(t *testing.T) {
// Create a new database and DAG instance to run tests against.
dag, teardownFunc, err := DAGSetup("TestMaybeAcceptBlockErrors", true, Config{
DAGParams: &dagconfig.SimnetParams,
})
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
dag.TestSetCoinbaseMaturity(0)
// Test rejecting the block if its parents are missing
orphanBlockFile := "blk_3B.dat"
loadedBlocks, err := LoadBlocks(filepath.Join("testdata/", orphanBlockFile))
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: "+
"Error loading file '%s': %s\n", orphanBlockFile, err)
}
block := loadedBlocks[0]
err = dag.maybeAcceptBlock(block, BFNone)
if err == nil {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
"Expected: %s, got: <nil>", ErrParentBlockUnknown)
}
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); !ok {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
"Expected RuleError but got %s", err)
} else if ruleErr.ErrorCode != ErrParentBlockUnknown {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
"Unexpected error code. Want: %s, got: %s", ErrParentBlockUnknown, ruleErr.ErrorCode)
}
// Test rejecting the block if its parents are invalid
blocksFile := "blk_0_to_4.dat"
blocks, err := LoadBlocks(filepath.Join("testdata/", blocksFile))
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: "+
"Error loading file '%s': %s\n", blocksFile, err)
}
// Add a valid block and mark it as invalid
block1 := blocks[1]
isOrphan, isDelayed, err := dag.ProcessBlock(block1, BFNone)
if err != nil {
t.Fatalf("TestMaybeAcceptBlockErrors: Valid block unexpectedly returned an error: %s", err)
}
if isDelayed {
t.Fatalf("TestMaybeAcceptBlockErrors: block 1 is too far in the future")
}
if isOrphan {
t.Fatalf("TestMaybeAcceptBlockErrors: incorrectly returned block 1 is an orphan")
}
blockNode1, ok := dag.index.LookupNode(block1.Hash())
if !ok {
t.Fatalf("block %s does not exist in the DAG", block1.Hash())
}
dag.index.SetStatusFlags(blockNode1, statusValidateFailed)
block2 := blocks[2]
err = dag.maybeAcceptBlock(block2, BFNone)
if err == nil {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
"Expected: %s, got: <nil>", ErrInvalidAncestorBlock)
}
if ok := errors.As(err, &ruleErr); !ok {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
"Expected RuleError but got %s", err)
} else if ruleErr.ErrorCode != ErrInvalidAncestorBlock {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
"Unexpected error. Want: %s, got: %s", ErrInvalidAncestorBlock, ruleErr.ErrorCode)
}
// Set block1's status back to valid for next tests
dag.index.UnsetStatusFlags(blockNode1, statusValidateFailed)
// Test rejecting the block due to bad context
originalBits := block2.MsgBlock().Header.Bits
block2.MsgBlock().Header.Bits = 0
err = dag.maybeAcceptBlock(block2, BFNone)
if err == nil {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
"Expected: %s, got: <nil>", ErrUnexpectedDifficulty)
}
if ok := errors.As(err, &ruleErr); !ok {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
"Expected RuleError but got %s", err)
} else if ruleErr.ErrorCode != ErrUnexpectedDifficulty {
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
"Unexpected error. Want: %s, got: %s", ErrUnexpectedDifficulty, ruleErr.ErrorCode)
}
// Set block2's bits back to valid for next tests
block2.MsgBlock().Header.Bits = originalBits
}

View File

@ -0,0 +1,87 @@
package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
// SelectedParentChain returns the selected parent chain starting from blockHash (exclusive)
// up to the virtual (exclusive). If blockHash is nil then the genesis block is used. If
// blockHash is not within the select parent chain, go down its own selected parent chain,
// while collecting each block hash in removedChainHashes, until reaching a block within
// the main selected parent chain.
//
// This method MUST be called with the DAG lock held
func (dag *BlockDAG) SelectedParentChain(blockHash *daghash.Hash) ([]*daghash.Hash, []*daghash.Hash, error) {
if blockHash == nil {
blockHash = dag.genesis.hash
}
if !dag.IsInDAG(blockHash) {
return nil, nil, errors.Errorf("blockHash %s does not exist in the DAG", blockHash)
}
// If blockHash is not in the selected parent chain, go down its selected parent chain
// until we find a block that is in the main selected parent chain.
var removedChainHashes []*daghash.Hash
isBlockInSelectedParentChain, err := dag.IsInSelectedParentChain(blockHash)
if err != nil {
return nil, nil, err
}
for !isBlockInSelectedParentChain {
removedChainHashes = append(removedChainHashes, blockHash)
node, ok := dag.index.LookupNode(blockHash)
if !ok {
return nil, nil, errors.Errorf("block %s does not exist in the DAG", blockHash)
}
blockHash = node.selectedParent.hash
isBlockInSelectedParentChain, err = dag.IsInSelectedParentChain(blockHash)
if err != nil {
return nil, nil, err
}
}
// Find the index of the blockHash in the selectedParentChainSlice
blockHashIndex := len(dag.virtual.selectedParentChainSlice) - 1
for blockHashIndex >= 0 {
node := dag.virtual.selectedParentChainSlice[blockHashIndex]
if node.hash.IsEqual(blockHash) {
break
}
blockHashIndex--
}
// Copy all the addedChainHashes starting from blockHashIndex (exclusive)
addedChainHashes := make([]*daghash.Hash, len(dag.virtual.selectedParentChainSlice)-blockHashIndex-1)
for i, node := range dag.virtual.selectedParentChainSlice[blockHashIndex+1:] {
addedChainHashes[i] = node.hash
}
return removedChainHashes, addedChainHashes, nil
}
// IsInSelectedParentChain returns whether or not a block hash is found in the selected
// parent chain. Note that this method returns an error if the given blockHash does not
// exist within the block index.
//
// This method MUST be called with the DAG lock held
func (dag *BlockDAG) IsInSelectedParentChain(blockHash *daghash.Hash) (bool, error) {
blockNode, ok := dag.index.LookupNode(blockHash)
if !ok {
str := fmt.Sprintf("block %s is not in the DAG", blockHash)
return false, ErrNotInDAG(str)
}
return dag.virtual.selectedParentChainSet.contains(blockNode), nil
}
// isInSelectedParentChainOf returns whether `node` is in the selected parent chain of `other`.
func (dag *BlockDAG) isInSelectedParentChainOf(node *blockNode, other *blockNode) (bool, error) {
// By definition, a node is not in the selected parent chain of itself.
if node == other {
return false, nil
}
return dag.reachabilityTree.isReachabilityTreeAncestorOf(node, other)
}

143
blockdag/sequence_lock.go Normal file
View File

@ -0,0 +1,143 @@
package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/domainmessage"
"github.com/kaspanet/kaspad/util"
)
// SequenceLock represents the converted relative lock-time in seconds, and
// absolute block-blue-score for a transaction input's relative lock-times.
// According to SequenceLock, after the referenced input has been confirmed
// within a block, a transaction spending that input can be included into a
// block either after 'seconds' (according to past median time), or once the
// 'BlockBlueScore' has been reached.
type SequenceLock struct {
Milliseconds int64
BlockBlueScore int64
}
// CalcSequenceLock computes a relative lock-time SequenceLock for the passed
// transaction using the passed UTXOSet to obtain the past median time
// for blocks in which the referenced inputs of the transactions were included
// within. The generated SequenceLock lock can be used in conjunction with a
// block height, and adjusted median block time to determine if all the inputs
// referenced within a transaction have reached sufficient maturity allowing
// the candidate transaction to be included in a block.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) CalcSequenceLock(tx *util.Tx, utxoSet UTXOSet) (*SequenceLock, error) {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
return dag.calcSequenceLock(dag.selectedTip(), utxoSet, tx)
}
// CalcSequenceLockNoLock is lock free version of CalcSequenceLockWithLock
// This function is unsafe for concurrent access.
func (dag *BlockDAG) CalcSequenceLockNoLock(tx *util.Tx, utxoSet UTXOSet) (*SequenceLock, error) {
return dag.calcSequenceLock(dag.selectedTip(), utxoSet, tx)
}
// calcSequenceLock computes the relative lock-times for the passed
// transaction. See the exported version, CalcSequenceLock for further details.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) calcSequenceLock(node *blockNode, utxoSet UTXOSet, tx *util.Tx) (*SequenceLock, error) {
// A value of -1 for each relative lock type represents a relative time
// lock value that will allow a transaction to be included in a block
// at any given height or time.
sequenceLock := &SequenceLock{Milliseconds: -1, BlockBlueScore: -1}
// Sequence locks don't apply to coinbase transactions Therefore, we
// return sequence lock values of -1 indicating that this transaction
// can be included within a block at any given height or time.
if tx.IsCoinBase() {
return sequenceLock, nil
}
mTx := tx.MsgTx()
for txInIndex, txIn := range mTx.TxIn {
entry, ok := utxoSet.Get(txIn.PreviousOutpoint)
if !ok {
str := fmt.Sprintf("output %s referenced from "+
"transaction %s input %d either does not exist or "+
"has already been spent", txIn.PreviousOutpoint,
tx.ID(), txInIndex)
return sequenceLock, ruleError(ErrMissingTxOut, str)
}
// If the input blue score is set to the mempool blue score, then we
// assume the transaction makes it into the next block when
// evaluating its sequence blocks.
inputBlueScore := entry.BlockBlueScore()
if entry.IsUnaccepted() {
inputBlueScore = dag.virtual.blueScore
}
// Given a sequence number, we apply the relative time lock
// mask in order to obtain the time lock delta required before
// this input can be spent.
sequenceNum := txIn.Sequence
relativeLock := int64(sequenceNum & domainmessage.SequenceLockTimeMask)
switch {
// Relative time locks are disabled for this input, so we can
// skip any further calculation.
case sequenceNum&domainmessage.SequenceLockTimeDisabled == domainmessage.SequenceLockTimeDisabled:
continue
case sequenceNum&domainmessage.SequenceLockTimeIsSeconds == domainmessage.SequenceLockTimeIsSeconds:
// This input requires a relative time lock expressed
// in seconds before it can be spent. Therefore, we
// need to query for the block prior to the one in
// which this input was accepted within so we can
// compute the past median time for the block prior to
// the one which accepted this referenced output.
blockNode := node
for blockNode.selectedParent.blueScore > inputBlueScore {
blockNode = blockNode.selectedParent
}
medianTime := blockNode.PastMedianTime(dag)
// Time based relative time-locks have a time granularity of
// domainmessage.SequenceLockTimeGranularity, so we shift left by this
// amount to convert to the proper relative time-lock. We also
// subtract one from the relative lock to maintain the original
// lockTime semantics.
timeLockMilliseconds := (relativeLock << domainmessage.SequenceLockTimeGranularity) - 1
timeLock := medianTime.UnixMilliseconds() + timeLockMilliseconds
if timeLock > sequenceLock.Milliseconds {
sequenceLock.Milliseconds = timeLock
}
default:
// The relative lock-time for this input is expressed
// in blocks so we calculate the relative offset from
// the input's blue score as its converted absolute
// lock-time. We subtract one from the relative lock in
// order to maintain the original lockTime semantics.
blockBlueScore := int64(inputBlueScore) + relativeLock - 1
if blockBlueScore > sequenceLock.BlockBlueScore {
sequenceLock.BlockBlueScore = blockBlueScore
}
}
}
return sequenceLock, nil
}
// LockTimeToSequence converts the passed relative locktime to a sequence
// number.
func LockTimeToSequence(isMilliseconds bool, locktime uint64) uint64 {
// If we're expressing the relative lock time in blocks, then the
// corresponding sequence number is simply the desired input age.
if !isMilliseconds {
return locktime
}
// Set the 22nd bit which indicates the lock time is in milliseconds, then
// shift the locktime over by 19 since the time granularity is in
// 524288-millisecond intervals (2^19). This results in a max lock-time of
// 34,359,214,080 seconds, or 1.1 years.
return domainmessage.SequenceLockTimeIsSeconds |
locktime>>domainmessage.SequenceLockTimeGranularity
}

View File

@ -155,3 +155,50 @@ func deserializeSubnetwork(serializedSNetBytes []byte) (*subnetwork, error) {
gasLimit: gasLimit, gasLimit: gasLimit,
}, nil }, nil
} }
func (dag *BlockDAG) validateGasLimit(block *util.Block) error {
var currentSubnetworkID *subnetworkid.SubnetworkID
var currentSubnetworkGasLimit uint64
var currentGasUsage uint64
var err error
// We assume here that transactions are ordered by subnetworkID,
// since it was already validated in checkTransactionSanity
for _, tx := range block.Transactions() {
msgTx := tx.MsgTx()
// In native and Built-In subnetworks all txs must have Gas = 0, and that was already validated in checkTransactionSanity
// Therefore - no need to check them here.
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) || msgTx.SubnetworkID.IsBuiltIn() {
continue
}
if !msgTx.SubnetworkID.IsEqual(currentSubnetworkID) {
currentSubnetworkID = &msgTx.SubnetworkID
currentGasUsage = 0
currentSubnetworkGasLimit, err = dag.GasLimit(currentSubnetworkID)
if err != nil {
return errors.Errorf("Error getting gas limit for subnetworkID '%s': %s", currentSubnetworkID, err)
}
}
newGasUsage := currentGasUsage + msgTx.Gas
if newGasUsage < currentGasUsage { // check for overflow
str := fmt.Sprintf("Block gas usage in subnetwork with ID %s has overflown", currentSubnetworkID)
return ruleError(ErrInvalidGas, str)
}
if newGasUsage > currentSubnetworkGasLimit {
str := fmt.Sprintf("Block wastes too much gas in subnetwork with ID %s", currentSubnetworkID)
return ruleError(ErrInvalidGas, str)
}
currentGasUsage = newGasUsage
}
return nil
}
// SubnetworkID returns the node's subnetwork ID
func (dag *BlockDAG) SubnetworkID() *subnetworkid.SubnetworkID {
return dag.subnetworkID
}

View File

@ -5,7 +5,13 @@ import (
"time" "time"
) )
const syncRateWindowDuration = 15 * time.Minute const (
syncRateWindowDuration = 15 * time.Minute
// isDAGCurrentMaxDiff is the number of blocks from the network tips (estimated by timestamps) for the current
// to be considered not synced
isDAGCurrentMaxDiff = 40_000
)
// addBlockProcessingTimestamp adds the last block processing timestamp in order to measure the recent sync rate. // addBlockProcessingTimestamp adds the last block processing timestamp in order to measure the recent sync rate.
// //
@ -58,3 +64,40 @@ func (dag *BlockDAG) IsSyncRateBelowThreshold(maxDeviation float64) bool {
func (dag *BlockDAG) uptime() time.Duration { func (dag *BlockDAG) uptime() time.Duration {
return mstime.Now().Sub(dag.startTime) return mstime.Now().Sub(dag.startTime)
} }
// isSynced returns whether or not the DAG believes it is synced. Several
// factors are used to guess, but the key factors that allow the DAG to
// believe it is synced are:
// - Latest block has a timestamp newer than 24 hours ago
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) isSynced() bool {
// Not synced if the virtual's selected parent has a timestamp
// before 24 hours ago. If the DAG is empty, we take the genesis
// block timestamp.
//
// The DAG appears to be syncned if none of the checks reported
// otherwise.
var dagTimestamp int64
selectedTip := dag.selectedTip()
if selectedTip == nil {
dagTimestamp = dag.Params.GenesisBlock.Header.Timestamp.UnixMilliseconds()
} else {
dagTimestamp = selectedTip.timestamp
}
dagTime := mstime.UnixMilliseconds(dagTimestamp)
return dag.Now().Sub(dagTime) <= isDAGCurrentMaxDiff*dag.Params.TargetTimePerBlock
}
// IsSynced returns whether or not the DAG believes it is synced. Several
// factors are used to guess, but the key factors that allow the DAG to
// believe it is synced are:
// - Latest block has a timestamp newer than 24 hours ago
//
// This function is safe for concurrent access.
func (dag *BlockDAG) IsSynced() bool {
dag.dagLock.RLock()
defer dag.dagLock.RUnlock()
return dag.isSynced()
}

View File

@ -0,0 +1,21 @@
package blockdag
import (
"github.com/kaspanet/kaspad/dagconfig"
"testing"
)
func TestIsDAGCurrentMaxDiff(t *testing.T) {
netParams := []*dagconfig.Params{
&dagconfig.MainnetParams,
&dagconfig.TestnetParams,
&dagconfig.DevnetParams,
&dagconfig.RegressionNetParams,
&dagconfig.SimnetParams,
}
for _, params := range netParams {
if params.FinalityDuration < isDAGCurrentMaxDiff*params.TargetTimePerBlock {
t.Errorf("in %s, a DAG can be considered current even if it's below the finality point", params.Name)
}
}
}

View File

@ -315,14 +315,9 @@ func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactio
msgBlock.AddTransaction(tx.MsgTx()) msgBlock.AddTransaction(tx.MsgTx())
} }
version, err := dag.calcNextBlockVersion(node.selectedParent)
if err != nil {
return nil, err
}
timestamp := node.parents.bluest().PastMedianTime(dag) timestamp := node.parents.bluest().PastMedianTime(dag)
msgBlock.Header = domainmessage.BlockHeader{ msgBlock.Header = domainmessage.BlockHeader{
Version: version, Version: blockVersion,
ParentHashes: parentHashes, ParentHashes: parentHashes,
HashMerkleRoot: hashMerkleTree.Root(), HashMerkleRoot: hashMerkleTree.Root(),
AcceptedIDMerkleRoot: calculatedAccepetedIDMerkleRoot, AcceptedIDMerkleRoot: calculatedAccepetedIDMerkleRoot,

View File

@ -1,356 +0,0 @@
// Copyright (c) 2016-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
// ThresholdState define the various threshold states used when voting on
// consensus changes.
type ThresholdState byte
// These constants are used to identify specific threshold states.
const (
// ThresholdDefined is the first state for each deployment and is the
// state for the genesis block has by definition for all deployments.
ThresholdDefined ThresholdState = iota
// ThresholdStarted is the state for a deployment once its start time
// has been reached.
ThresholdStarted
// ThresholdLockedIn is the state for a deployment during the retarget
// period which is after the ThresholdStarted state period and the
// number of blocks that have voted for the deployment equal or exceed
// the required number of votes for the deployment.
ThresholdLockedIn
// ThresholdActive is the state for a deployment for all blocks after a
// retarget period in which the deployment was in the ThresholdLockedIn
// state.
ThresholdActive
// ThresholdFailed is the state for a deployment once its expiration
// time has been reached and it did not reach the ThresholdLockedIn
// state.
ThresholdFailed
// numThresholdsStates is the maximum number of threshold states used in
// tests.
numThresholdsStates
)
// thresholdStateStrings is a map of ThresholdState values back to their
// constant names for pretty printing.
var thresholdStateStrings = map[ThresholdState]string{
ThresholdDefined: "ThresholdDefined",
ThresholdStarted: "ThresholdStarted",
ThresholdLockedIn: "ThresholdLockedIn",
ThresholdActive: "ThresholdActive",
ThresholdFailed: "ThresholdFailed",
}
// String returns the ThresholdState as a human-readable name.
func (t ThresholdState) String() string {
if s := thresholdStateStrings[t]; s != "" {
return s
}
return fmt.Sprintf("Unknown ThresholdState (%d)", int(t))
}
// thresholdConditionChecker provides a generic interface that is invoked to
// determine when a consensus rule change threshold should be changed.
type thresholdConditionChecker interface {
// BeginTime returns the unix timestamp for the median block time after
// which voting on a rule change starts (at the next window).
BeginTime() uint64
// EndTime returns the unix timestamp for the median block time after
// which an attempted rule change fails if it has not already been
// locked in or activated.
EndTime() uint64
// RuleChangeActivationThreshold is the number of blocks for which the
// condition must be true in order to lock in a rule change.
RuleChangeActivationThreshold() uint64
// MinerConfirmationWindow is the number of blocks in each threshold
// state retarget window.
MinerConfirmationWindow() uint64
// Condition returns whether or not the rule change activation condition
// has been met. This typically involves checking whether or not the
// bit associated with the condition is set, but can be more complex as
// needed.
Condition(*blockNode) (bool, error)
}
// thresholdStateCache provides a type to cache the threshold states of each
// threshold window for a set of IDs.
type thresholdStateCache struct {
entries map[daghash.Hash]ThresholdState
}
// Lookup returns the threshold state associated with the given hash along with
// a boolean that indicates whether or not it is valid.
func (c *thresholdStateCache) Lookup(hash *daghash.Hash) (ThresholdState, bool) {
state, ok := c.entries[*hash]
return state, ok
}
// Update updates the cache to contain the provided hash to threshold state
// mapping.
func (c *thresholdStateCache) Update(hash *daghash.Hash, state ThresholdState) {
c.entries[*hash] = state
}
// newThresholdCaches returns a new array of caches to be used when calculating
// threshold states.
func newThresholdCaches(numCaches uint32) []thresholdStateCache {
caches := make([]thresholdStateCache, numCaches)
for i := 0; i < len(caches); i++ {
caches[i] = thresholdStateCache{
entries: make(map[daghash.Hash]ThresholdState),
}
}
return caches
}
// thresholdState returns the current rule change threshold state for the block
// AFTER the given node and deployment ID. The cache is used to ensure the
// threshold states for previous windows are only calculated once.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) thresholdState(prevNode *blockNode, checker thresholdConditionChecker, cache *thresholdStateCache) (ThresholdState, error) {
// The threshold state for the window that contains the genesis block is
// defined by definition.
confirmationWindow := checker.MinerConfirmationWindow()
if prevNode == nil || (prevNode.blueScore+1) < confirmationWindow {
return ThresholdDefined, nil
}
// Get the ancestor that is the last block of the previous confirmation
// window in order to get its threshold state. This can be done because
// the state is the same for all blocks within a given window.
prevNode = prevNode.SelectedAncestor(prevNode.blueScore -
(prevNode.blueScore+1)%confirmationWindow)
// Iterate backwards through each of the previous confirmation windows
// to find the most recently cached threshold state.
var neededStates []*blockNode
for prevNode != nil {
// Nothing more to do if the state of the block is already
// cached.
if _, ok := cache.Lookup(prevNode.hash); ok {
break
}
// The start and expiration times are based on the median block
// time, so calculate it now.
medianTime := prevNode.PastMedianTime(dag)
// The state is simply defined if the start time hasn't been
// been reached yet.
if uint64(medianTime.UnixMilliseconds()) < checker.BeginTime() {
cache.Update(prevNode.hash, ThresholdDefined)
break
}
// Add this node to the list of nodes that need the state
// calculated and cached.
neededStates = append(neededStates, prevNode)
// Get the ancestor that is the last block of the previous
// confirmation window.
prevNode = prevNode.RelativeAncestor(confirmationWindow)
}
// Start with the threshold state for the most recent confirmation
// window that has a cached state.
state := ThresholdDefined
if prevNode != nil {
var ok bool
state, ok = cache.Lookup(prevNode.hash)
if !ok {
return ThresholdFailed, errors.Errorf(
"thresholdState: cache lookup failed for %s",
prevNode.hash)
}
}
// Since each threshold state depends on the state of the previous
// window, iterate starting from the oldest unknown window.
for neededNum := len(neededStates) - 1; neededNum >= 0; neededNum-- {
prevNode := neededStates[neededNum]
switch state {
case ThresholdDefined:
// The deployment of the rule change fails if it expires
// before it is accepted and locked in.
medianTime := prevNode.PastMedianTime(dag)
medianTimeUnix := uint64(medianTime.UnixMilliseconds())
if medianTimeUnix >= checker.EndTime() {
state = ThresholdFailed
break
}
// The state for the rule moves to the started state
// once its start time has been reached (and it hasn't
// already expired per the above).
if medianTimeUnix >= checker.BeginTime() {
state = ThresholdStarted
}
case ThresholdStarted:
// The deployment of the rule change fails if it expires
// before it is accepted and locked in.
medianTime := prevNode.PastMedianTime(dag)
if uint64(medianTime.UnixMilliseconds()) >= checker.EndTime() {
state = ThresholdFailed
break
}
// At this point, the rule change is still being voted
// on by the miners, so iterate backwards through the
// confirmation window to count all of the votes in it.
var count uint64
windowNodes := make([]*blockNode, 0, confirmationWindow)
windowNodes = append(windowNodes, prevNode)
windowNodes = append(windowNodes, blueBlockWindow(prevNode, confirmationWindow-1)...)
for _, current := range windowNodes {
condition, err := checker.Condition(current)
if err != nil {
return ThresholdFailed, err
}
if condition {
count++
}
}
// The state is locked in if the number of blocks in the
// period that voted for the rule change meets the
// activation threshold.
if count >= checker.RuleChangeActivationThreshold() {
state = ThresholdLockedIn
}
case ThresholdLockedIn:
// The new rule becomes active when its previous state
// was locked in.
state = ThresholdActive
// Nothing to do if the previous state is active or failed since
// they are both terminal states.
case ThresholdActive:
case ThresholdFailed:
}
// Update the cache to avoid recalculating the state in the
// future.
cache.Update(prevNode.hash, state)
}
return state, nil
}
// ThresholdState returns the current rule change threshold state of the given
// deployment ID for the block AFTER the blueScore of the current DAG.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) ThresholdState(deploymentID uint32) (ThresholdState, error) {
dag.dagLock.Lock()
defer dag.dagLock.Unlock()
state, err := dag.deploymentState(dag.selectedTip(), deploymentID)
return state, err
}
// IsDeploymentActive returns true if the target deploymentID is active, and
// false otherwise.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) IsDeploymentActive(deploymentID uint32) (bool, error) {
dag.dagLock.Lock()
defer dag.dagLock.Unlock()
state, err := dag.deploymentState(dag.selectedTip(), deploymentID)
if err != nil {
return false, err
}
return state == ThresholdActive, nil
}
// deploymentState returns the current rule change threshold for a given
// deploymentID. The threshold is evaluated from the point of view of the block
// node passed in as the first argument to this method.
//
// It is important to note that, as the variable name indicates, this function
// expects the block node prior to the block for which the deployment state is
// desired. In other words, the returned deployment state is for the block
// AFTER the passed node.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) deploymentState(prevNode *blockNode, deploymentID uint32) (ThresholdState, error) {
if deploymentID > uint32(len(dag.Params.Deployments)) {
return ThresholdFailed, errors.Errorf("deployment ID %d does not exist", deploymentID)
}
deployment := &dag.Params.Deployments[deploymentID]
checker := deploymentChecker{deployment: deployment, dag: dag}
cache := &dag.deploymentCaches[deploymentID]
return dag.thresholdState(prevNode, checker, cache)
}
// initThresholdCaches initializes the threshold state caches for each warning
// bit and defined deployment and provides warnings if the DAG is current per
// the warnUnknownVersions and warnUnknownRuleActivations functions.
func (dag *BlockDAG) initThresholdCaches() error {
// Initialize the warning and deployment caches by calculating the
// threshold state for each of them. This will ensure the caches are
// populated and any states that needed to be recalculated due to
// definition changes is done now.
prevNode := dag.selectedTip().selectedParent
for bit := uint32(0); bit < vbNumBits; bit++ {
checker := bitConditionChecker{bit: bit, dag: dag}
cache := &dag.warningCaches[bit]
_, err := dag.thresholdState(prevNode, checker, cache)
if err != nil {
return err
}
}
for id := 0; id < len(dag.Params.Deployments); id++ {
deployment := &dag.Params.Deployments[id]
cache := &dag.deploymentCaches[id]
checker := deploymentChecker{deployment: deployment, dag: dag}
_, err := dag.thresholdState(prevNode, checker, cache)
if err != nil {
return err
}
}
// No warnings about unknown rules or versions until the DAG is
// synced.
if dag.isSynced() {
// Warn if a high enough percentage of the last blocks have
// unexpected versions.
bestNode := dag.selectedTip()
if err := dag.warnUnknownVersions(bestNode); err != nil {
return err
}
// Warn if any unknown new rules are either about to activate or
// have already been activated.
if err := dag.warnUnknownRuleActivations(bestNode); err != nil {
return err
}
}
return nil
}

View File

@ -1,134 +0,0 @@
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockdag
import (
"testing"
"github.com/kaspanet/kaspad/util/daghash"
)
// TestThresholdStateStringer tests the stringized output for the
// ThresholdState type.
func TestThresholdStateStringer(t *testing.T) {
t.Parallel()
tests := []struct {
in ThresholdState
want string
}{
{ThresholdDefined, "ThresholdDefined"},
{ThresholdStarted, "ThresholdStarted"},
{ThresholdLockedIn, "ThresholdLockedIn"},
{ThresholdActive, "ThresholdActive"},
{ThresholdFailed, "ThresholdFailed"},
{0xff, "Unknown ThresholdState (255)"},
}
// Detect additional threshold states that don't have the stringer added.
if len(tests)-1 != int(numThresholdsStates) {
t.Errorf("It appears a threshold statewas added without " +
"adding an associated stringer test")
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
result := test.in.String()
if result != test.want {
t.Errorf("String #%d\n got: %s want: %s", i, result,
test.want)
continue
}
}
}
// TestThresholdStateCache ensure the threshold state cache works as intended
// including adding entries, updating existing entries, and flushing.
func TestThresholdStateCache(t *testing.T) {
t.Parallel()
tests := []struct {
name string
numEntries int
state ThresholdState
}{
{name: "2 entries defined", numEntries: 2, state: ThresholdDefined},
{name: "7 entries started", numEntries: 7, state: ThresholdStarted},
{name: "10 entries active", numEntries: 10, state: ThresholdActive},
{name: "5 entries locked in", numEntries: 5, state: ThresholdLockedIn},
{name: "3 entries failed", numEntries: 3, state: ThresholdFailed},
}
nextTest:
for _, test := range tests {
cache := &newThresholdCaches(1)[0]
for i := 0; i < test.numEntries; i++ {
var hash daghash.Hash
hash[0] = uint8(i + 1)
// Ensure the hash isn't available in the cache already.
_, ok := cache.Lookup(&hash)
if ok {
t.Errorf("Lookup (%s): has entry for hash %v",
test.name, hash)
continue nextTest
}
// Ensure hash that was added to the cache reports it's
// available and the state is the expected value.
cache.Update(&hash, test.state)
state, ok := cache.Lookup(&hash)
if !ok {
t.Errorf("Lookup (%s): missing entry for hash "+
"%v", test.name, hash)
continue nextTest
}
if state != test.state {
t.Errorf("Lookup (%s): state mismatch - got "+
"%v, want %v", test.name, state,
test.state)
continue nextTest
}
// Ensure adding an existing hash with the same state
// doesn't break the existing entry.
cache.Update(&hash, test.state)
state, ok = cache.Lookup(&hash)
if !ok {
t.Errorf("Lookup (%s): missing entry after "+
"second add for hash %v", test.name,
hash)
continue nextTest
}
if state != test.state {
t.Errorf("Lookup (%s): state mismatch after "+
"second add - got %v, want %v",
test.name, state, test.state)
continue nextTest
}
// Ensure adding an existing hash with a different state
// updates the existing entry.
newState := ThresholdFailed
if newState == test.state {
newState = ThresholdStarted
}
cache.Update(&hash, newState)
state, ok = cache.Lookup(&hash)
if !ok {
t.Errorf("Lookup (%s): missing entry after "+
"state change for hash %v", test.name,
hash)
continue nextTest
}
if state != newState {
t.Errorf("Lookup (%s): state mismatch after "+
"state change - got %v, want %v",
test.name, state, newState)
continue nextTest
}
}
}
}

View File

@ -6,13 +6,11 @@ package blockdag
import ( import (
"fmt" "fmt"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"math" "math"
"sort" "sort"
"time"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/dagconfig" "github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/domainmessage" "github.com/kaspanet/kaspad/domainmessage"
@ -128,13 +126,55 @@ func CalcBlockSubsidy(blueScore uint64, dagParams *dagconfig.Params) uint64 {
// CheckTransactionSanity performs some preliminary checks on a transaction to // CheckTransactionSanity performs some preliminary checks on a transaction to
// ensure it is sane. These checks are context free. // ensure it is sane. These checks are context free.
func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID) error { func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID) error {
isCoinbase := tx.IsCoinBase() err := checkTransactionInputCount(tx)
// A transaction must have at least one input. if err != nil {
return err
}
err = checkTransactionAmountRanges(tx)
if err != nil {
return err
}
err = checkDuplicateTransactionInputs(tx)
if err != nil {
return err
}
err = checkCoinbaseLength(tx)
if err != nil {
return err
}
err = checkTransactionPayloadHash(tx)
if err != nil {
return err
}
err = checkGasInBuiltInOrNativeTransactions(tx)
if err != nil {
return err
}
err = checkSubnetworkRegistryTransaction(tx)
if err != nil {
return err
}
err = checkNativeTransactionPayload(tx)
if err != nil {
return err
}
err = checkTransactionSubnetwork(tx, subnetworkID)
if err != nil {
return err
}
return nil
}
func checkTransactionInputCount(tx *util.Tx) error {
// A non-coinbase transaction must have at least one input.
msgTx := tx.MsgTx() msgTx := tx.MsgTx()
if !isCoinbase && len(msgTx.TxIn) == 0 { if !tx.IsCoinBase() && len(msgTx.TxIn) == 0 {
return ruleError(ErrNoTxInputs, "transaction has no inputs") return ruleError(ErrNoTxInputs, "transaction has no inputs")
} }
return nil
}
func checkTransactionAmountRanges(tx *util.Tx) error {
// Ensure the transaction amounts are in range. Each transaction // Ensure the transaction amounts are in range. Each transaction
// output must not be negative or more than the max allowed per // output must not be negative or more than the max allowed per
// transaction. Also, the total of all outputs must abide by the same // transaction. Also, the total of all outputs must abide by the same
@ -142,7 +182,7 @@ func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID
// as a sompi. One kaspa is a quantity of sompi as defined by the // as a sompi. One kaspa is a quantity of sompi as defined by the
// SompiPerKaspa constant. // SompiPerKaspa constant.
var totalSompi uint64 var totalSompi uint64
for _, txOut := range msgTx.TxOut { for _, txOut := range tx.MsgTx().TxOut {
sompi := txOut.Value sompi := txOut.Value
if sompi > util.MaxSompi { if sompi > util.MaxSompi {
str := fmt.Sprintf("transaction output value of %d is "+ str := fmt.Sprintf("transaction output value of %d is "+
@ -170,20 +210,25 @@ func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID
return ruleError(ErrBadTxOutValue, str) return ruleError(ErrBadTxOutValue, str)
} }
} }
return nil
}
// Check for duplicate transaction inputs. func checkDuplicateTransactionInputs(tx *util.Tx) error {
existingTxOut := make(map[domainmessage.Outpoint]struct{}) existingTxOut := make(map[domainmessage.Outpoint]struct{})
for _, txIn := range msgTx.TxIn { for _, txIn := range tx.MsgTx().TxIn {
if _, exists := existingTxOut[txIn.PreviousOutpoint]; exists { if _, exists := existingTxOut[txIn.PreviousOutpoint]; exists {
return ruleError(ErrDuplicateTxInputs, "transaction "+ return ruleError(ErrDuplicateTxInputs, "transaction "+
"contains duplicate inputs") "contains duplicate inputs")
} }
existingTxOut[txIn.PreviousOutpoint] = struct{}{} existingTxOut[txIn.PreviousOutpoint] = struct{}{}
} }
return nil
}
func checkCoinbaseLength(tx *util.Tx) error {
// Coinbase payload length must not exceed the max length. // Coinbase payload length must not exceed the max length.
if isCoinbase { if tx.IsCoinBase() {
payloadLen := len(msgTx.Payload) payloadLen := len(tx.MsgTx().Payload)
if payloadLen > MaxCoinbasePayloadLen { if payloadLen > MaxCoinbasePayloadLen {
str := fmt.Sprintf("coinbase transaction payload length "+ str := fmt.Sprintf("coinbase transaction payload length "+
"of %d is out of range (max: %d)", "of %d is out of range (max: %d)",
@ -193,7 +238,7 @@ func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID
} else { } else {
// Previous transaction outputs referenced by the inputs to this // Previous transaction outputs referenced by the inputs to this
// transaction must not be null. // transaction must not be null.
for _, txIn := range msgTx.TxIn { for _, txIn := range tx.MsgTx().TxIn {
if isNullOutpoint(&txIn.PreviousOutpoint) { if isNullOutpoint(&txIn.PreviousOutpoint) {
return ruleError(ErrBadTxInput, "transaction "+ return ruleError(ErrBadTxInput, "transaction "+
"input refers to previous output that "+ "input refers to previous output that "+
@ -201,8 +246,11 @@ func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID
} }
} }
} }
return nil
}
// Check payload's hash func checkTransactionPayloadHash(tx *util.Tx) error {
msgTx := tx.MsgTx()
if !msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) { if !msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
payloadHash := daghash.DoubleHashH(msgTx.Payload) payloadHash := daghash.DoubleHashH(msgTx.Payload)
if !msgTx.PayloadHash.IsEqual(&payloadHash) { if !msgTx.PayloadHash.IsEqual(&payloadHash) {
@ -211,29 +259,44 @@ func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID
} else if msgTx.PayloadHash != nil { } else if msgTx.PayloadHash != nil {
return ruleError(ErrInvalidPayloadHash, "unexpected non-empty payload hash in native subnetwork") return ruleError(ErrInvalidPayloadHash, "unexpected non-empty payload hash in native subnetwork")
} }
return nil
}
func checkGasInBuiltInOrNativeTransactions(tx *util.Tx) error {
// Transactions in native, registry and coinbase subnetworks must have Gas = 0 // Transactions in native, registry and coinbase subnetworks must have Gas = 0
if msgTx.SubnetworkID.IsBuiltInOrNative() && msgTx.Gas > 0 { msgTx := tx.MsgTx()
if (msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) ||
msgTx.SubnetworkID.IsBuiltIn()) &&
msgTx.Gas > 0 {
return ruleError(ErrInvalidGas, "transaction in the native or "+ return ruleError(ErrInvalidGas, "transaction in the native or "+
"registry subnetworks has gas > 0 ") "registry subnetworks has gas > 0 ")
} }
return nil
}
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDRegistry) { func checkSubnetworkRegistryTransaction(tx *util.Tx) error {
err := validateSubnetworkRegistryTransaction(msgTx) if tx.MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDRegistry) {
err := validateSubnetworkRegistryTransaction(tx.MsgTx())
if err != nil { if err != nil {
return err return err
} }
} }
return nil
}
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) && func checkNativeTransactionPayload(tx *util.Tx) error {
len(msgTx.Payload) > 0 { msgTx := tx.MsgTx()
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) && len(msgTx.Payload) > 0 {
return ruleError(ErrInvalidPayload, return ruleError(ErrInvalidPayload, "transaction in the native subnetwork includes a payload")
"transaction in the native subnetwork includes a payload")
} }
return nil
}
func checkTransactionSubnetwork(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID) error {
// If we are a partial node, only transactions on built in subnetworks // If we are a partial node, only transactions on built in subnetworks
// or our own subnetwork may have a payload // or our own subnetwork may have a payload
msgTx := tx.MsgTx()
isLocalNodeFull := subnetworkID == nil isLocalNodeFull := subnetworkID == nil
shouldTxBeFull := msgTx.SubnetworkID.IsBuiltIn() || shouldTxBeFull := msgTx.SubnetworkID.IsBuiltIn() ||
msgTx.SubnetworkID.IsEqual(subnetworkID) msgTx.SubnetworkID.IsEqual(subnetworkID)
@ -242,7 +305,6 @@ func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID
"transaction that was expected to be partial has a payload "+ "transaction that was expected to be partial has a payload "+
"with length > 0") "with length > 0")
} }
return nil return nil
} }
@ -390,37 +452,28 @@ func CalcTxMass(tx *util.Tx, previousScriptPubKeys [][]byte) uint64 {
// //
// The flags do not modify the behavior of this function directly, however they // The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkProofOfWork. // are needed to pass along to checkProofOfWork.
func (dag *BlockDAG) checkBlockHeaderSanity(block *util.Block, flags BehaviorFlags) (delay time.Duration, err error) { func (dag *BlockDAG) checkBlockHeaderSanity(block *util.Block, flags BehaviorFlags) error {
// Ensure the proof of work bits in the block header is in min/max range // Ensure the proof of work bits in the block header is in min/max range
// and the block hash is less than the target value described by the // and the block hash is less than the target value described by the
// bits. // bits.
header := &block.MsgBlock().Header header := &block.MsgBlock().Header
err = dag.checkProofOfWork(header, flags) err := dag.checkProofOfWork(header, flags)
if err != nil { if err != nil {
return 0, err return err
} }
if len(header.ParentHashes) == 0 { if len(header.ParentHashes) == 0 {
if !header.BlockHash().IsEqual(dag.Params.GenesisHash) { if !header.BlockHash().IsEqual(dag.Params.GenesisHash) {
return 0, ruleError(ErrNoParents, "block has no parents") return ruleError(ErrNoParents, "block has no parents")
} }
} else { } else {
err = checkBlockParentsOrder(header) err = checkBlockParentsOrder(header)
if err != nil { if err != nil {
return 0, err return err
} }
} }
// Ensure the block time is not too far in the future. If it's too far, return return nil
// the duration of time that should be waited before the block becomes valid.
// This check needs to be last as it does not return an error but rather marks the
// header as delayed (and valid).
maxTimestamp := dag.Now().Add(time.Duration(dag.TimestampDeviationTolerance) * dag.Params.TargetTimePerBlock)
if header.Timestamp.After(maxTimestamp) {
return header.Timestamp.Sub(maxTimestamp), nil
}
return 0, nil
} }
//checkBlockParentsOrder ensures that the block's parents are ordered by hash //checkBlockParentsOrder ensures that the block's parents are ordered by hash
@ -443,56 +496,56 @@ func checkBlockParentsOrder(header *domainmessage.BlockHeader) error {
// //
// The flags do not modify the behavior of this function directly, however they // The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkBlockHeaderSanity. // are needed to pass along to checkBlockHeaderSanity.
func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) (time.Duration, error) { func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) error {
delay, err := dag.checkBlockHeaderSanity(block, flags) err := dag.checkBlockHeaderSanity(block, flags)
if err != nil { if err != nil {
return 0, err return err
} }
err = dag.checkBlockContainsAtLeastOneTransaction(block) err = dag.checkBlockContainsAtLeastOneTransaction(block)
if err != nil { if err != nil {
return 0, err return err
} }
err = dag.checkBlockContainsLessThanMaxBlockMassTransactions(block) err = dag.checkBlockContainsLessThanMaxBlockMassTransactions(block)
if err != nil { if err != nil {
return 0, err return err
} }
err = dag.checkFirstBlockTransactionIsCoinbase(block) err = dag.checkFirstBlockTransactionIsCoinbase(block)
if err != nil { if err != nil {
return 0, err return err
} }
err = dag.checkBlockContainsOnlyOneCoinbase(block) err = dag.checkBlockContainsOnlyOneCoinbase(block)
if err != nil { if err != nil {
return 0, err return err
} }
err = dag.checkBlockTransactionOrder(block) err = dag.checkBlockTransactionOrder(block)
if err != nil { if err != nil {
return 0, err return err
} }
err = dag.checkNoNonNativeTransactions(block) err = dag.checkNoNonNativeTransactions(block)
if err != nil { if err != nil {
return 0, err return err
} }
err = dag.checkBlockTransactionSanity(block) err = dag.checkBlockTransactionSanity(block)
if err != nil { if err != nil {
return 0, err return err
} }
err = dag.checkBlockHashMerkleRoot(block) err = dag.checkBlockHashMerkleRoot(block)
if err != nil { if err != nil {
return 0, err return err
} }
// The following check will be fairly quick since the transaction IDs // The following check will be fairly quick since the transaction IDs
// are already cached due to building the merkle tree above. // are already cached due to building the merkle tree above.
err = dag.checkBlockDuplicateTransactions(block) err = dag.checkBlockDuplicateTransactions(block)
if err != nil { if err != nil {
return 0, err return err
} }
err = dag.checkBlockDoubleSpends(block) err = dag.checkBlockDoubleSpends(block)
if err != nil { if err != nil {
return 0, err return err
} }
return delay, nil return nil
} }
func (dag *BlockDAG) checkBlockContainsAtLeastOneTransaction(block *util.Block) error { func (dag *BlockDAG) checkBlockContainsAtLeastOneTransaction(block *util.Block) error {
@ -715,11 +768,16 @@ func (dag *BlockDAG) validateParents(blockHeader *domainmessage.BlockHeader, par
// for how the flags modify its behavior. // for how the flags modify its behavior.
// //
// This function MUST be called with the dag state lock held (for writes). // This function MUST be called with the dag state lock held (for writes).
func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, flags BehaviorFlags) error { func (dag *BlockDAG) checkBlockContext(block *util.Block, flags BehaviorFlags) error {
parents, err := lookupParentNodes(block, dag)
if err != nil {
return dag.handleLookupParentNodesError(block, err)
}
bluestParent := parents.bluest() bluestParent := parents.bluest()
fastAdd := flags&BFFastAdd == BFFastAdd fastAdd := flags&BFFastAdd == BFFastAdd
err := dag.validateParents(&block.MsgBlock().Header, parents) err = dag.validateParents(&block.MsgBlock().Header, parents)
if err != nil { if err != nil {
return err return err
} }
@ -733,10 +791,26 @@ func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, flag
return nil return nil
} }
func (dag *BlockDAG) validateAllTxsFinalized(block *util.Block, node *blockNode, bluestParent *blockNode) error { func (dag *BlockDAG) handleLookupParentNodesError(block *util.Block, err error) error {
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrInvalidAncestorBlock {
err := dag.addNodeToIndexWithInvalidAncestor(block)
if err != nil {
return err
}
}
return err
}
func (dag *BlockDAG) checkBlockTransactionsFinalized(block *util.Block, node *blockNode, flags BehaviorFlags) error {
fastAdd := flags&BFFastAdd == BFFastAdd || dag.index.NodeStatus(node).KnownValid()
if fastAdd {
return nil
}
blockTime := block.MsgBlock().Header.Timestamp blockTime := block.MsgBlock().Header.Timestamp
if !block.IsGenesis() { if !block.IsGenesis() {
blockTime = bluestParent.PastMedianTime(dag) blockTime = node.selectedParent.PastMedianTime(dag)
} }
// Ensure all transactions in the block are finalized. // Ensure all transactions in the block are finalized.
@ -977,7 +1051,7 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
// A transaction can only be included within a block // A transaction can only be included within a block
// once the sequence locks of *all* its inputs are // once the sequence locks of *all* its inputs are
// active. // active.
sequenceLock, err := dag.calcSequenceLock(block, pastUTXO, tx, false) sequenceLock, err := dag.calcSequenceLock(block, pastUTXO, tx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1002,6 +1076,18 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
return feeData, nil return feeData, nil
} }
func (node *blockNode) validateUTXOCommitment(multiset *secp256k1.MultiSet) error {
calculatedMultisetHash := daghash.Hash(*multiset.Finalize())
if !calculatedMultisetHash.IsEqual(node.utxoCommitment) {
str := fmt.Sprintf("block %s UTXO commitment is invalid - block "+
"header indicates %s, but calculated value is %s", node.hash,
node.utxoCommitment, calculatedMultisetHash)
return ruleError(ErrBadUTXOCommitment, str)
}
return nil
}
// CheckConnectBlockTemplate fully validates that connecting the passed block to // CheckConnectBlockTemplate fully validates that connecting the passed block to
// the DAG does not violate any consensus rules, aside from the proof of // the DAG does not violate any consensus rules, aside from the proof of
// work requirement. // work requirement.
@ -1023,21 +1109,17 @@ func (dag *BlockDAG) CheckConnectBlockTemplateNoLock(block *util.Block) error {
header := block.MsgBlock().Header header := block.MsgBlock().Header
delay, err := dag.checkBlockSanity(block, flags) err := dag.checkBlockSanity(block, flags)
if err != nil { if err != nil {
return err return err
} }
if delay != 0 { _, isDelayed := dag.shouldBlockBeDelayed(block)
if isDelayed {
return errors.Errorf("Block timestamp is too far in the future") return errors.Errorf("Block timestamp is too far in the future")
} }
parents, err := lookupParentNodes(block, dag) err = dag.checkBlockContext(block, flags)
if err != nil {
return err
}
err = dag.checkBlockContext(block, parents, flags)
if err != nil { if err != nil {
return err return err
} }
@ -1049,3 +1131,24 @@ func (dag *BlockDAG) CheckConnectBlockTemplateNoLock(block *util.Block) error {
return err return err
} }
func (dag *BlockDAG) checkDuplicateBlock(blockHash *daghash.Hash, flags BehaviorFlags) error {
wasBlockStored := flags&BFWasStored == BFWasStored
if dag.IsInDAG(blockHash) && !wasBlockStored {
str := fmt.Sprintf("already have block %s", blockHash)
return ruleError(ErrDuplicateBlock, str)
}
// The block must not already exist as an orphan.
if _, exists := dag.orphans[*blockHash]; exists {
str := fmt.Sprintf("already have block (orphan) %s", blockHash)
return ruleError(ErrDuplicateBlock, str)
}
if dag.isKnownDelayedBlock(blockHash) {
str := fmt.Sprintf("already have block (delayed) %s", blockHash)
return ruleError(ErrDuplicateBlock, str)
}
return nil
}

View File

@ -172,16 +172,13 @@ func TestCheckBlockSanity(t *testing.T) {
if len(block.Transactions()) < 3 { if len(block.Transactions()) < 3 {
t.Fatalf("Too few transactions in block, expect at least 3, got %v", len(block.Transactions())) t.Fatalf("Too few transactions in block, expect at least 3, got %v", len(block.Transactions()))
} }
delay, err := dag.checkBlockSanity(block, BFNone) err = dag.checkBlockSanity(block, BFNone)
if err != nil { if err != nil {
t.Errorf("CheckBlockSanity: %v", err) t.Errorf("CheckBlockSanity: %v", err)
} }
if delay != 0 {
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
}
// Test with block with wrong transactions sorting order // Test with block with wrong transactions sorting order
blockWithWrongTxOrder := util.NewBlock(&BlockWithWrongTxOrder) blockWithWrongTxOrder := util.NewBlock(&BlockWithWrongTxOrder)
delay, err = dag.checkBlockSanity(blockWithWrongTxOrder, BFNone) err = dag.checkBlockSanity(blockWithWrongTxOrder, BFNone)
if err == nil { if err == nil {
t.Errorf("CheckBlockSanity: transactions disorder is not detected") t.Errorf("CheckBlockSanity: transactions disorder is not detected")
} }
@ -192,9 +189,6 @@ func TestCheckBlockSanity(t *testing.T) {
t.Errorf("CheckBlockSanity: wrong error returned, expect ErrTransactionsNotSorted, got"+ t.Errorf("CheckBlockSanity: wrong error returned, expect ErrTransactionsNotSorted, got"+
" %v, err %s", ruleErr.ErrorCode, err) " %v, err %s", ruleErr.ErrorCode, err)
} }
if delay != 0 {
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
}
var invalidParentsOrderBlock = domainmessage.MsgBlock{ var invalidParentsOrderBlock = domainmessage.MsgBlock{
Header: domainmessage.BlockHeader{ Header: domainmessage.BlockHeader{
@ -463,7 +457,7 @@ func TestCheckBlockSanity(t *testing.T) {
} }
utilInvalidBlock := util.NewBlock(&invalidParentsOrderBlock) utilInvalidBlock := util.NewBlock(&invalidParentsOrderBlock)
delay, err = dag.checkBlockSanity(utilInvalidBlock, BFNone) err = dag.checkBlockSanity(utilInvalidBlock, BFNone)
if err == nil { if err == nil {
t.Errorf("CheckBlockSanity: error is nil when it shouldn't be") t.Errorf("CheckBlockSanity: error is nil when it shouldn't be")
} }
@ -473,21 +467,6 @@ func TestCheckBlockSanity(t *testing.T) {
} else if rError.ErrorCode != ErrWrongParentsOrder { } else if rError.ErrorCode != ErrWrongParentsOrder {
t.Errorf("CheckBlockSanity: Expected error was ErrWrongParentsOrder but got %v", err) t.Errorf("CheckBlockSanity: Expected error was ErrWrongParentsOrder but got %v", err)
} }
if delay != 0 {
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
}
blockInTheFuture := Block100000
expectedDelay := 10 * time.Second
deviationTolerance := time.Duration(dag.TimestampDeviationTolerance) * dag.Params.TargetTimePerBlock
blockInTheFuture.Header.Timestamp = dag.Now().Add(deviationTolerance + expectedDelay)
delay, err = dag.checkBlockSanity(util.NewBlock(&blockInTheFuture), BFNoPoWCheck)
if err != nil {
t.Errorf("CheckBlockSanity: %v", err)
}
if delay != expectedDelay {
t.Errorf("CheckBlockSanity: expected %s delay but got %s", expectedDelay, delay)
}
} }
func TestPastMedianTime(t *testing.T) { func TestPastMedianTime(t *testing.T) {

View File

@ -1,294 +0,0 @@
// Copyright (c) 2016-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockdag
import (
"math"
"github.com/kaspanet/kaspad/dagconfig"
)
const (
// vbTopBits defines the bits to set in the version to signal that the
// version bits scheme is being used.
vbTopBits = 0x10000000
// vbTopMask is the bitmask to use to determine whether or not the
// version bits scheme is in use.
vbTopMask = 0xe0000000
// vbNumBits is the total number of bits available for use with the
// version bits scheme.
vbNumBits = 29
// unknownVerNumToCheck is the number of previous blocks to consider
// when checking for a threshold of unknown block versions for the
// purposes of warning the user.
unknownVerNumToCheck = 100
// unknownVerWarnNum is the threshold of previous blocks that have an
// unknown version to use for the purposes of warning the user.
unknownVerWarnNum = unknownVerNumToCheck / 2
)
// bitConditionChecker provides a thresholdConditionChecker which can be used to
// test whether or not a specific bit is set when it's not supposed to be
// according to the expected version based on the known deployments and the
// current state of the DAG. This is useful for detecting and warning about
// unknown rule activations.
type bitConditionChecker struct {
bit uint32
dag *BlockDAG
}
// Ensure the bitConditionChecker type implements the thresholdConditionChecker
// interface.
var _ thresholdConditionChecker = bitConditionChecker{}
// BeginTime returns the unix timestamp for the median block time after which
// voting on a rule change starts (at the next window).
//
// Since this implementation checks for unknown rules, it returns 0 so the rule
// is always treated as active.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) BeginTime() uint64 {
return 0
}
// EndTime returns the unix timestamp for the median block time after which an
// attempted rule change fails if it has not already been locked in or
// activated.
//
// Since this implementation checks for unknown rules, it returns the maximum
// possible timestamp so the rule is always treated as active.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) EndTime() uint64 {
return math.MaxUint64
}
// RuleChangeActivationThreshold is the number of blocks for which the condition
// must be true in order to lock in a rule change.
//
// This implementation returns the value defined by the DAG params the checker
// is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) RuleChangeActivationThreshold() uint64 {
return c.dag.Params.RuleChangeActivationThreshold
}
// MinerConfirmationWindow is the number of blocks in each threshold state
// retarget window.
//
// This implementation returns the value defined by the DAG params the checker
// is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) MinerConfirmationWindow() uint64 {
return c.dag.Params.MinerConfirmationWindow
}
// Condition returns true when the specific bit associated with the checker is
// set and it's not supposed to be according to the expected version based on
// the known deployments and the current state of the DAG.
//
// This function MUST be called with the DAG state lock held (for writes).
//
// This is part of the thresholdConditionChecker interface implementation.
func (c bitConditionChecker) Condition(node *blockNode) (bool, error) {
conditionMask := uint32(1) << c.bit
version := uint32(node.version)
if version&vbTopMask != vbTopBits {
return false, nil
}
if version&conditionMask == 0 {
return false, nil
}
expectedVersion, err := c.dag.calcNextBlockVersion(node.selectedParent)
if err != nil {
return false, err
}
return uint32(expectedVersion)&conditionMask == 0, nil
}
// deploymentChecker provides a thresholdConditionChecker which can be used to
// test a specific deployment rule. This is required for properly detecting
// and activating consensus rule changes.
type deploymentChecker struct {
deployment *dagconfig.ConsensusDeployment
dag *BlockDAG
}
// Ensure the deploymentChecker type implements the thresholdConditionChecker
// interface.
var _ thresholdConditionChecker = deploymentChecker{}
// BeginTime returns the unix timestamp for the median block time after which
// voting on a rule change starts (at the next window).
//
// This implementation returns the value defined by the specific deployment the
// checker is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) BeginTime() uint64 {
return c.deployment.StartTime
}
// EndTime returns the unix timestamp for the median block time after which an
// attempted rule change fails if it has not already been locked in or
// activated.
//
// This implementation returns the value defined by the specific deployment the
// checker is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) EndTime() uint64 {
return c.deployment.ExpireTime
}
// RuleChangeActivationThreshold is the number of blocks for which the condition
// must be true in order to lock in a rule change.
//
// This implementation returns the value defined by the DAG params the checker
// is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) RuleChangeActivationThreshold() uint64 {
return c.dag.Params.RuleChangeActivationThreshold
}
// MinerConfirmationWindow is the number of blocks in each threshold state
// retarget window.
//
// This implementation returns the value defined by the DAG params the checker
// is associated with.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) MinerConfirmationWindow() uint64 {
return c.dag.Params.MinerConfirmationWindow
}
// Condition returns true when the specific bit defined by the deployment
// associated with the checker is set.
//
// This is part of the thresholdConditionChecker interface implementation.
func (c deploymentChecker) Condition(node *blockNode) (bool, error) {
conditionMask := uint32(1) << c.deployment.BitNumber
version := uint32(node.version)
return (version&vbTopMask == vbTopBits) && (version&conditionMask != 0),
nil
}
// calcNextBlockVersion calculates the expected version of the block after the
// passed previous block node based on the state of started and locked in
// rule change deployments.
//
// This function differs from the exported CalcNextBlockVersion in that the
// exported version uses the selected tip as the previous block node
// while this function accepts any block node.
//
// This function MUST be called with the DAG state lock held (for writes).
func (dag *BlockDAG) calcNextBlockVersion(prevNode *blockNode) (int32, error) {
// Set the appropriate bits for each actively defined rule deployment
// that is either in the process of being voted on, or locked in for the
// activation at the next threshold window change.
expectedVersion := uint32(vbTopBits)
for id := 0; id < len(dag.Params.Deployments); id++ {
deployment := &dag.Params.Deployments[id]
cache := &dag.deploymentCaches[id]
checker := deploymentChecker{deployment: deployment, dag: dag}
state, err := dag.thresholdState(prevNode, checker, cache)
if err != nil {
return 0, err
}
if state == ThresholdStarted || state == ThresholdLockedIn {
expectedVersion |= uint32(1) << deployment.BitNumber
}
}
return int32(expectedVersion), nil
}
// CalcNextBlockVersion calculates the expected version of the block after the
// end of the current selected tip based on the state of started and locked in
// rule change deployments.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) CalcNextBlockVersion() (int32, error) {
version, err := dag.calcNextBlockVersion(dag.selectedTip())
return version, err
}
// warnUnknownRuleActivations displays a warning when any unknown new rules are
// either about to activate or have been activated. This will only happen once
// when new rules have been activated and every block for those about to be
// activated.
//
// This function MUST be called with the DAG state lock held (for writes)
func (dag *BlockDAG) warnUnknownRuleActivations(node *blockNode) error {
// Warn if any unknown new rules are either about to activate or have
// already been activated.
for bit := uint32(0); bit < vbNumBits; bit++ {
checker := bitConditionChecker{bit: bit, dag: dag}
cache := &dag.warningCaches[bit]
state, err := dag.thresholdState(node.selectedParent, checker, cache)
if err != nil {
return err
}
switch state {
case ThresholdActive:
if !dag.unknownRulesWarned {
log.Warnf("Unknown new rules activated (bit %d)",
bit)
dag.unknownRulesWarned = true
}
case ThresholdLockedIn:
window := checker.MinerConfirmationWindow()
activationBlueScore := window - (node.blueScore % window)
log.Warnf("Unknown new rules are about to activate in "+
"%d blueScore (bit %d)", activationBlueScore, bit)
}
}
return nil
}
// warnUnknownVersions logs a warning if a high enough percentage of the last
// blocks have unexpected versions.
//
// This function MUST be called with the DAG state lock held (for writes)
func (dag *BlockDAG) warnUnknownVersions(node *blockNode) error {
// Nothing to do if already warned.
if dag.unknownVersionsWarned {
return nil
}
// Warn if enough previous blocks have unexpected versions.
numUpgraded := uint32(0)
for i := uint32(0); i < unknownVerNumToCheck && node != nil; i++ {
expectedVersion, err := dag.calcNextBlockVersion(node.selectedParent)
if err != nil {
return err
}
if (node.version & ^expectedVersion) != 0 {
numUpgraded++
}
node = node.selectedParent
}
if numUpgraded > unknownVerWarnNum {
log.Warn("Unknown block versions are being mined, so new " +
"rules might be in effect. Are you running the " +
"latest version of the software?")
dag.unknownVersionsWarned = true
}
return nil
}

View File

@ -6,7 +6,6 @@ package dagconfig
import ( import (
"github.com/kaspanet/kaspad/util/network" "github.com/kaspanet/kaspad/util/network"
"math"
"math/big" "math/big"
"time" "time"
@ -161,7 +160,6 @@ type Params struct {
// on. // on.
RuleChangeActivationThreshold uint64 RuleChangeActivationThreshold uint64
MinerConfirmationWindow uint64 MinerConfirmationWindow uint64
Deployments [DefinedDeployments]ConsensusDeployment
// Mempool parameters // Mempool parameters
RelayNonStdTxs bool RelayNonStdTxs bool
@ -215,13 +213,6 @@ var MainnetParams = Params{
// target proof of work timespan / target proof of work spacing // target proof of work timespan / target proof of work spacing
RuleChangeActivationThreshold: 1916, // 95% of MinerConfirmationWindow RuleChangeActivationThreshold: 1916, // 95% of MinerConfirmationWindow
MinerConfirmationWindow: 2016, // MinerConfirmationWindow: 2016, //
Deployments: [DefinedDeployments]ConsensusDeployment{
DeploymentTestDummy: {
BitNumber: 28,
StartTime: 1199145601000, // January 1, 2008 UTC
ExpireTime: 1230767999000, // December 31, 2008 UTC
},
},
// Mempool parameters // Mempool parameters
RelayNonStdTxs: false, RelayNonStdTxs: false,
@ -270,13 +261,6 @@ var RegressionNetParams = Params{
// target proof of work timespan / target proof of work spacing // target proof of work timespan / target proof of work spacing
RuleChangeActivationThreshold: 108, // 75% of MinerConfirmationWindow RuleChangeActivationThreshold: 108, // 75% of MinerConfirmationWindow
MinerConfirmationWindow: 144, MinerConfirmationWindow: 144,
Deployments: [DefinedDeployments]ConsensusDeployment{
DeploymentTestDummy: {
BitNumber: 28,
StartTime: 0, // Always available for vote
ExpireTime: math.MaxInt64, // Never expires
},
},
// Mempool parameters // Mempool parameters
RelayNonStdTxs: true, RelayNonStdTxs: true,
@ -323,13 +307,6 @@ var TestnetParams = Params{
// target proof of work timespan / target proof of work spacing // target proof of work timespan / target proof of work spacing
RuleChangeActivationThreshold: 1512, // 75% of MinerConfirmationWindow RuleChangeActivationThreshold: 1512, // 75% of MinerConfirmationWindow
MinerConfirmationWindow: 2016, MinerConfirmationWindow: 2016,
Deployments: [DefinedDeployments]ConsensusDeployment{
DeploymentTestDummy: {
BitNumber: 28,
StartTime: 1199145601000, // January 1, 2008 UTC
ExpireTime: 1230767999000, // December 31, 2008 UTC
},
},
// Mempool parameters // Mempool parameters
RelayNonStdTxs: true, RelayNonStdTxs: true,
@ -382,13 +359,6 @@ var SimnetParams = Params{
// target proof of work timespan / target proof of work spacing // target proof of work timespan / target proof of work spacing
RuleChangeActivationThreshold: 75, // 75% of MinerConfirmationWindow RuleChangeActivationThreshold: 75, // 75% of MinerConfirmationWindow
MinerConfirmationWindow: 100, MinerConfirmationWindow: 100,
Deployments: [DefinedDeployments]ConsensusDeployment{
DeploymentTestDummy: {
BitNumber: 28,
StartTime: 0, // Always available for vote
ExpireTime: math.MaxInt64, // Never expires
},
},
// Mempool parameters // Mempool parameters
RelayNonStdTxs: true, RelayNonStdTxs: true,
@ -433,13 +403,6 @@ var DevnetParams = Params{
// target proof of work timespan / target proof of work spacing // target proof of work timespan / target proof of work spacing
RuleChangeActivationThreshold: 1512, // 75% of MinerConfirmationWindow RuleChangeActivationThreshold: 1512, // 75% of MinerConfirmationWindow
MinerConfirmationWindow: 2016, MinerConfirmationWindow: 2016,
Deployments: [DefinedDeployments]ConsensusDeployment{
DeploymentTestDummy: {
BitNumber: 28,
StartTime: 1199145601000, // January 1, 2008 UTC
ExpireTime: 1230767999000, // December 31, 2008 UTC
},
},
// Mempool parameters // Mempool parameters
RelayNonStdTxs: true, RelayNonStdTxs: true,

View File

@ -53,12 +53,6 @@ type Config struct {
// utxo set. // utxo set.
CalcSequenceLockNoLock func(*util.Tx, blockdag.UTXOSet) (*blockdag.SequenceLock, error) CalcSequenceLockNoLock func(*util.Tx, blockdag.UTXOSet) (*blockdag.SequenceLock, error)
// IsDeploymentActive returns true if the target deploymentID is
// active, and false otherwise. The mempool uses this function to gauge
// if transactions using new to be soft-forked rules should be allowed
// into the mempool or not.
IsDeploymentActive func(deploymentID uint32) (bool, error)
// SigCache defines a signature cache to use. // SigCache defines a signature cache to use.
SigCache *txscript.SigCache SigCache *txscript.SigCache

View File

@ -1,13 +1,8 @@
package rpc package rpc
import ( import (
"fmt"
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/rpc/model" "github.com/kaspanet/kaspad/rpc/model"
"github.com/kaspanet/kaspad/util/daghash" "github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
"strings"
) )
// handleGetBlockDAGInfo implements the getBlockDagInfo command. // handleGetBlockDAGInfo implements the getBlockDagInfo command.
@ -28,72 +23,5 @@ func handleGetBlockDAGInfo(s *Server, cmd interface{}, closeChan <-chan struct{}
Bip9SoftForks: make(map[string]*model.Bip9SoftForkDescription), Bip9SoftForks: make(map[string]*model.Bip9SoftForkDescription),
} }
// Finally, query the BIP0009 version bits state for all currently
// defined BIP0009 soft-fork deployments.
for deployment, deploymentDetails := range params.Deployments {
// Map the integer deployment ID into a human readable
// fork-name.
var forkName string
switch deployment {
case dagconfig.DeploymentTestDummy:
forkName = "dummy"
default:
return nil, &model.RPCError{
Code: model.ErrRPCInternal.Code,
Message: fmt.Sprintf("Unknown deployment %d "+
"detected", deployment),
}
}
// Query the dag for the current status of the deployment as
// identified by its deployment ID.
deploymentStatus, err := dag.ThresholdState(uint32(deployment))
if err != nil {
context := "Failed to obtain deployment status"
return nil, internalRPCError(err.Error(), context)
}
// Attempt to convert the current deployment status into a
// human readable string. If the status is unrecognized, then a
// non-nil error is returned.
statusString, err := softForkStatus(deploymentStatus)
if err != nil {
return nil, &model.RPCError{
Code: model.ErrRPCInternal.Code,
Message: fmt.Sprintf("unknown deployment status: %d",
deploymentStatus),
}
}
// Finally, populate the soft-fork description with all the
// information gathered above.
dagInfo.Bip9SoftForks[forkName] = &model.Bip9SoftForkDescription{
Status: strings.ToLower(statusString),
Bit: deploymentDetails.BitNumber,
StartTime: int64(deploymentDetails.StartTime),
Timeout: int64(deploymentDetails.ExpireTime),
}
}
return dagInfo, nil return dagInfo, nil
} }
// softForkStatus converts a ThresholdState state into a human readable string
// corresponding to the particular state.
func softForkStatus(state blockdag.ThresholdState) (string, error) {
switch state {
case blockdag.ThresholdDefined:
return "defined", nil
case blockdag.ThresholdStarted:
return "started", nil
case blockdag.ThresholdLockedIn:
return "lockedin", nil
case blockdag.ThresholdActive:
return "active", nil
case blockdag.ThresholdFailed:
return "failed", nil
default:
return "", errors.Errorf("unknown deployment state: %s", state)
}
}