mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-03-13 11:55:05 +00:00
* [NOD-540] Implement reachability (#545) * [NOD-540] Begin implementing reachability. * [NOD-540] Finish implementing reachability. * [NOD-540] Implement TestIsFutureBlock. * [NOD-540] Implement TestInsertFutureBlock. * [NOD-540] Add comments. * [NOD-540] Add comment for interval in blockNode. * [NOD-540] Updated comments over insertFutureBlock and isFutureBlock. * [NOD-540] Implement interval splitting methods. * [NOD-540] Begin implementing tree manipulation in blockNode. * [NOD-540] Implement countSubtreesUp. * [NOD-540] Add a comment explaining an impossible condition. * [NOD-540] Implement applyIntervalDown. * [NOD-540] Moved the reachability tree stuff into reachability.go. * [NOD-540] Add some comments. * [NOD-540] Add more comments, implement isInPast. * [NOD-540] Fix comments. * [NOD-540] Implement TestSplitFraction. * [NOD-540] Implement TestSplitExact. * [NOD-540] Implement TestSplit. * [NOD-540] Add comments to structs. * [NOD-540] Implement TestAddTreeChild. * [NOD-540] Fix a comment. * [NOD-540] Rename isInPast to isAncestorOf. * [NOD-540] Rename futureBlocks to futureCoveringSet. * [NOD-540] Rename isFutureBlock to isInFuture. * [NOD-540] move reachabilityInterval to the top of reachability.go. * [NOD-540] Change "s.t." to "such that" in a comment. * [NOD-540] Fix indentation. * [NOD-540] Fix a potential bug involving float inaccuracy. * [NOD-540] Wrote a more descriptive error message. * [NOD-540] Fix error messsage. * [NOD-540] Fix the recursive countSubtreesUp. * [NOD-540] Rename countSubtreesUp to countSubtrees and applyIntervalDown to propagateInterval. * [NOD-540] Implement updating reachability for a valid new block. * [NOD-540] Implement a disk storage for reachability data. * [NOD-540] Fix not all tree nodes being written to the database. * [NOD-540] Implement serialization for reachabilityData. * [NOD-540] Implement some deserialization for reachabilityData. * [NOD-540] Implement restoring the reachabilityStore on node restart. * [NOD-540] Made interval and remainingInterval pointers. * [NOD-540] Rename setTreeInterval to setInterval. * [NOD-540] Rename reindexTreeIntervals to reindexIntervals and fixed the comment above it. * [NOD-540] Expand the comment above reindexIntervals. * [NOD-540] Fix comment above countSubtrees. * [NOD-540] Fix comment above countSubtrees some more. * [NOD-540] Fix comment above split. * [NOD-540] Fix comment above isAncestorOf. * [NOD-540] Fix comment above reachabilityTreeNode. * [NOD-540] Fix weird condition in addTreeChild. * [NOD-540] Rename addTreeChild to addChild. * [NOD-540] Fix weird condition in splitFraction. * [NOD-540] Reverse the lines in reachabilityTreeNode.String(). * [NOD-540] Renamed f to fraction and x to size. * [NOD-540] Fix comment above bisect. * [NOD-540] Implement rtn.isAncestorOf(). * [NOD-540] Use treeNode isAncestorOf instead of treeInterval isAncestorOf. * [NOD-540] Use newReachabilityInterval instead of struct initialization. * [NOD-540] Make reachabilityTreeNode.String() use strings.Join. * [NOD-540] Use sync.RWMutex instead of locks.PriorityMutex. * [NOD-540] Rename thisTreeNode to newTreeNode. * [NOD-540] Rename setTreeNode to addTreeNode. * [NOD-540] Extracted selectedParentAnticone to a separate function. * [NOD-540] Rename node to this. * [NOD-540] Move updateReachability and isAncestorOf from dag.go to reachability.go. * [NOD-540] Add whitespace after multiline function signatures in reachability.go. * [NOD-540] Make splitFraction return an error on empty interval. * [NOD-540] Add a comment about rounding to splitFraction. * [NOD-540] Replace sneaky tabs with spaces. * [NOD-540] Rename split to splitExponential. * [NOD-540] Extract exponentialFractions to a separate function. * [NOD-540] Rename bisect to findIndex. * [NOD-540] Add call to reachabilityStore.clearDirtyEntries at the end of saveChangesFromBlock. * [NOD-540] Explain the dirty hack in reachabilityStore.init(). * [NOD-540] Split the function signature for deserializeReachabilityData to two lines. * [NOD-540] Add a comment about float precision loss to exponentialFractions. * [NOD-540] Corrected a comment about float precision loss to exponentialFractions. * [NOD-540] Fixed a comment about float precision loss to exponentialFractions some more. * [NOD-540] Added further comments above futureCoveringBlockSet. * [NOD-540] Rename addTreeNode to setTreeNode. * [NOD-540] Rename splitExponential to splitWithExponentialBias. * [NOD-540] Fix object references in reachabilityData deserialization (#563) * [NOD-540] Fix broken references in deserialization. * [NOD-540] Fix broken references in futureCoveringSet deserialization. Also add comments. * [NOD-540] Don't deserialize on the first pass in reachabilityStore.init(). * [NOD-540] Remove redundant assignment to loaded[hash]. * [NOD-540] Use NewHash instead of SetBytes. Rename data to destination. * [NOD-540] Preallocate futureCoveringSet. * [NOD-541] Implement GHOSTDAG (#560) * [NOD-541] Implement GHOSTDAG * [NOD-541] Replace the old PHANTOM variant with GHOSTDAG * [NOD-541] Move dag.updateReachability to the top of dag.applyDAGChanges to update reachability before the virtual block is updated * [NOD-541] Fix blueAnticoneSize * [NOD-541] Initialize node.bluesAnticoneSizes * [NOD-541] Fix pastUTXO and applyBlueBlocks blues order * [NOD-541] Add serialization logic to node.bluesAnticoneSizes * [NOD-541] Fix GHOSTDAG to not count the new block and the blue candidates anticone, add selected parent to blues, and save to node.bluesAnticoneSizes properly * [NOD-541] Fix test names in inner strings * [NOD-541] Writing TestGHOSTDAG * [NOD-541] In blueAnticoneSize change node->current * [NOD-541] name ghostdag return values * [NOD-541] fix ghostdag to return slice * [NOD-541] Split k-cluster violation rules * [NOD-541] Add missing space * [NOD-541] Add comment to ghostdag * [NOD-541] In selectedParentAnticone rename past->selectedParentPast * [NOD-541] Fix misrefernces to TestChainUpdates * [NOD-541] Fix ghostdag comment * [NOD-541] Make PrepareBlockForTest in blockdag package * [NOD-541] Make PrepareBlockForTest in blockdag package * [NOD-541] Assign to selectedParentAnticone[i] instead of appending * [NOD-541] Remove redundant forceTransactions arguments from PrepareBlockForTEST * [NOD-541] Add non-selected parents to anticoneHeap * [NOD-541] add test for ghostdag * [NOD-541] Add comments * [NOD-541] Use adjusted time for initializing blockNode * [NOD-541] Rename isAncestorOf -> isAncestorOfBlueCandidate * [NOD-541] Remove params from PrepareBlockForTest * [NOD-541] Fix TestChainHeight * [NOD-541] Remove recursive lock * [NOD-541] Fix TestTxIndexConnectBlock * [NOD-541] Fix TestBlueBlockWindow * [NOD-541] Put prepareAndProcessBlock in common_test.go * [NOD-541] Fix TestConfirmations * [NOD-541] Fix TestAcceptingBlock * [NOD-541] Fix TestDifficulty * [NOD-541] Fix TestVirtualBlock * [NOD-541] Fix TestSelectedPath * [NOD-541] Fix TestChainUpdates * [NOD-541] Shorten TestDifficulty test time * [NOD-541] Make PrepareBlockForTest use minimal valid block time * [NOD-541] Remove TODO comment * [NOD-541] Move blockdag related mining functions to mining.go * [NOD-541] Use NextBlockCoinbaseTransaction instead of NextBlockCoinbaseTransactionNoLock in NextCoinbaseFromAddress * [NOD-541] Remove useMinimalTime from BlockForMining * [NOD-541] Make MedianAdjustedTime a *BlockDAG method * [NOD-541] Fix ghostdag to use anticone slice instead of heap * [NOD-541] Fix NewBlockTemplate locks * [NOD-541] Fix ghostdag comments * [NOD-541] Convert MedianAdjustedTime to NextBlockTime * [NOD-541] Fix ghostdag comment * [NOD-541] Fix TestGHOSTDAG comment * [NOD-541] Add comment before sanity check * [NOD-541] Explicitly initialize .blues in ghostdag * [NOD-541] Rename *blockNode.lessThan to *blockNode.less * [NOD-541] Remove redundant check if block != chainBlock * [NOD-541] Fix comment * [NOD-541] Fix comment * [NOD-497] Add comment; General refactoring * [NOD-497] General refactoring. * [NOD-497] Use isAncestor of the tree rather than the node * [NOD-497] Remove reachability mutex lock as it is redundant (dag lock is held so no need); General refactoring. * [NOD-497] Update comment * [NOD-497] Undo test blocktimestamp * [NOD-497] Update comments; Use BlockNode.less for blockset; * [NOD-497] Change processBlock to return boolean and not the delay duration (merge conflict) * [NOD-497] Undo change for bluest to use less; Change blocknode less to use daghash.Less Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com> Co-authored-by: Dan Aharoni <dereeno@protonmail.com>
243 lines
8.1 KiB
Go
243 lines
8.1 KiB
Go
// Copyright (c) 2015-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/pkg/errors"
|
|
"time"
|
|
|
|
"github.com/kaspanet/kaspad/util/daghash"
|
|
"github.com/kaspanet/kaspad/wire"
|
|
)
|
|
|
|
// blockStatus is a bit field representing the validation state of the block.
|
|
type blockStatus byte
|
|
|
|
const (
|
|
// statusDataStored indicates that the block's payload is stored on disk.
|
|
statusDataStored blockStatus = 1 << iota
|
|
|
|
// statusValid indicates that the block has been fully validated.
|
|
statusValid
|
|
|
|
// statusValidateFailed indicates that the block has failed validation.
|
|
statusValidateFailed
|
|
|
|
// statusInvalidAncestor indicates that one of the block's ancestors has
|
|
// has failed validation, thus the block is also invalid.
|
|
statusInvalidAncestor
|
|
|
|
// statusNone indicates that the block has no validation state flags set.
|
|
//
|
|
// NOTE: This must be defined last in order to avoid influencing iota.
|
|
statusNone blockStatus = 0
|
|
)
|
|
|
|
// KnownValid returns whether the block is known to be valid. This will return
|
|
// false for a valid block that has not been fully validated yet.
|
|
func (status blockStatus) KnownValid() bool {
|
|
return status&statusValid != 0
|
|
}
|
|
|
|
// KnownInvalid returns whether the block is known to be invalid. This may be
|
|
// because the block itself failed validation or any of its ancestors is
|
|
// invalid. This will return false for invalid blocks that have not been proven
|
|
// invalid yet.
|
|
func (status blockStatus) KnownInvalid() bool {
|
|
return status&(statusValidateFailed|statusInvalidAncestor) != 0
|
|
}
|
|
|
|
// blockNode represents a block within the block DAG. The DAG is stored into
|
|
// the block database.
|
|
type blockNode struct {
|
|
// NOTE: Additions, deletions, or modifications to the order of the
|
|
// definitions in this struct should not be changed without considering
|
|
// how it affects alignment on 64-bit platforms. The current order is
|
|
// specifically crafted to result in minimal padding. There will be
|
|
// hundreds of thousands of these in memory, so a few extra bytes of
|
|
// padding adds up.
|
|
|
|
// parents is the parent blocks for this node.
|
|
parents blockSet
|
|
|
|
// selectedParent is the selected parent for this node.
|
|
// The selected parent is the parent that if chosen will maximize the blue score of this block
|
|
selectedParent *blockNode
|
|
|
|
// children are all the blocks that refer to this block as a parent
|
|
children blockSet
|
|
|
|
// blues are all blue blocks in this block's worldview that are in its selected parent anticone
|
|
blues []*blockNode
|
|
|
|
// blueScore is the count of all the blue blocks in this block's past
|
|
blueScore uint64
|
|
|
|
// bluesAnticoneSizes is a map holding the set of blues affected by this block and their
|
|
// modified blue anticone size.
|
|
bluesAnticoneSizes map[daghash.Hash]uint32
|
|
|
|
// hash is the double sha 256 of the block.
|
|
hash *daghash.Hash
|
|
|
|
// chainHeight is the number of hops you need to go down the selected parent chain in order to get to the genesis block.
|
|
chainHeight uint64
|
|
|
|
// Some fields from block headers to aid in reconstructing headers
|
|
// from memory. These must be treated as immutable and are intentionally
|
|
// ordered to avoid padding on 64-bit platforms.
|
|
version int32
|
|
bits uint32
|
|
nonce uint64
|
|
timestamp int64
|
|
hashMerkleRoot *daghash.Hash
|
|
acceptedIDMerkleRoot *daghash.Hash
|
|
utxoCommitment *daghash.Hash
|
|
|
|
// status is a bitfield representing the validation state of the block. The
|
|
// status field, unlike the other fields, may be written to and so should
|
|
// only be accessed using the concurrent-safe NodeStatus method on
|
|
// blockIndex once the node has been added to the global index.
|
|
status blockStatus
|
|
|
|
// isFinalized determines whether the node is below the finality point.
|
|
isFinalized bool
|
|
}
|
|
|
|
func calculateChainHeight(node *blockNode) uint64 {
|
|
if node.isGenesis() {
|
|
return 0
|
|
}
|
|
return node.selectedParent.chainHeight + 1
|
|
}
|
|
|
|
// newBlockNode returns a new block node for the given block header and parents, and the
|
|
// anticone of its selected parent (parent with highest blue score).
|
|
// selectedParentAnticone is used to update reachability data we store for future reachability queries.
|
|
// This function is NOT safe for concurrent access.
|
|
func (dag *BlockDAG) newBlockNode(blockHeader *wire.BlockHeader, parents blockSet) (node *blockNode, selectedParentAnticone []*blockNode) {
|
|
node = &blockNode{
|
|
parents: parents,
|
|
children: make(blockSet),
|
|
timestamp: dag.timeSource.AdjustedTime().Unix(),
|
|
bluesAnticoneSizes: make(map[daghash.Hash]uint32),
|
|
}
|
|
|
|
// blockHeader is nil only for the virtual block
|
|
if blockHeader != nil {
|
|
node.hash = blockHeader.BlockHash()
|
|
node.version = blockHeader.Version
|
|
node.bits = blockHeader.Bits
|
|
node.nonce = blockHeader.Nonce
|
|
node.timestamp = blockHeader.Timestamp.Unix()
|
|
node.hashMerkleRoot = blockHeader.HashMerkleRoot
|
|
node.acceptedIDMerkleRoot = blockHeader.AcceptedIDMerkleRoot
|
|
node.utxoCommitment = blockHeader.UTXOCommitment
|
|
} else {
|
|
node.hash = &daghash.ZeroHash
|
|
}
|
|
|
|
if len(parents) > 0 {
|
|
var err error
|
|
selectedParentAnticone, err = dag.ghostdag(node)
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "unexpected error in GHOSTDAG"))
|
|
}
|
|
node.chainHeight = calculateChainHeight(node)
|
|
}
|
|
return node, selectedParentAnticone
|
|
}
|
|
|
|
// updateParentsChildren updates the node's parents to point to new node
|
|
func (node *blockNode) updateParentsChildren() {
|
|
for _, parent := range node.parents {
|
|
parent.children.add(node)
|
|
}
|
|
}
|
|
|
|
func (node *blockNode) less(other *blockNode) bool {
|
|
if node.blueScore == other.blueScore {
|
|
return daghash.Less(node.hash, other.hash)
|
|
}
|
|
|
|
return node.blueScore < other.blueScore
|
|
}
|
|
|
|
// Header constructs a block header from the node and returns it.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (node *blockNode) Header() *wire.BlockHeader {
|
|
// No lock is needed because all accessed fields are immutable.
|
|
return &wire.BlockHeader{
|
|
Version: node.version,
|
|
ParentHashes: node.ParentHashes(),
|
|
HashMerkleRoot: node.hashMerkleRoot,
|
|
AcceptedIDMerkleRoot: node.acceptedIDMerkleRoot,
|
|
UTXOCommitment: node.utxoCommitment,
|
|
Timestamp: time.Unix(node.timestamp, 0),
|
|
Bits: node.bits,
|
|
Nonce: node.nonce,
|
|
}
|
|
}
|
|
|
|
// SelectedAncestor returns the ancestor block node at the provided chain-height by following
|
|
// the selected-parents chain backwards from this node. The returned block will be nil when a
|
|
// height is requested that is after the height of the passed node.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (node *blockNode) SelectedAncestor(chainHeight uint64) *blockNode {
|
|
if chainHeight < 0 || chainHeight > node.chainHeight {
|
|
return nil
|
|
}
|
|
|
|
n := node
|
|
for ; n != nil && n.chainHeight != chainHeight; n = n.selectedParent {
|
|
// Intentionally left blank
|
|
}
|
|
|
|
return n
|
|
}
|
|
|
|
// RelativeAncestor returns the ancestor block node a relative 'distance' of
|
|
// chain-blocks before this node. This is equivalent to calling Ancestor with
|
|
// the node's chain-height minus provided distance.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (node *blockNode) RelativeAncestor(distance uint64) *blockNode {
|
|
return node.SelectedAncestor(node.chainHeight - distance)
|
|
}
|
|
|
|
// CalcPastMedianTime returns the median time of the previous few blocks
|
|
// prior to, and including, the block node.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (node *blockNode) PastMedianTime(dag *BlockDAG) time.Time {
|
|
window := blueBlockWindow(node, 2*dag.TimestampDeviationTolerance-1)
|
|
medianTimestamp, err := window.medianTimestamp()
|
|
if err != nil {
|
|
panic(fmt.Sprintf("blueBlockWindow: %s", err))
|
|
}
|
|
return time.Unix(medianTimestamp, 0)
|
|
}
|
|
|
|
func (node *blockNode) ParentHashes() []*daghash.Hash {
|
|
return node.parents.hashes()
|
|
}
|
|
|
|
// isGenesis returns if the current block is the genesis block
|
|
func (node *blockNode) isGenesis() bool {
|
|
return len(node.parents) == 0
|
|
}
|
|
|
|
func (node *blockNode) finalityScore(dag *BlockDAG) uint64 {
|
|
return node.blueScore / uint64(dag.dagParams.FinalityInterval)
|
|
}
|
|
|
|
// String returns a string that contains the block hash.
|
|
func (node blockNode) String() string {
|
|
return node.hash.String()
|
|
}
|