mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-09-14 05:20:11 +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>
393 lines
10 KiB
Go
393 lines
10 KiB
Go
package blockdag
|
|
|
|
import (
|
|
"bytes"
|
|
"github.com/kaspanet/kaspad/database"
|
|
"github.com/kaspanet/kaspad/util/daghash"
|
|
"github.com/kaspanet/kaspad/wire"
|
|
"github.com/pkg/errors"
|
|
"io"
|
|
)
|
|
|
|
type reachabilityData struct {
|
|
treeNode *reachabilityTreeNode
|
|
futureCoveringSet futureCoveringBlockSet
|
|
}
|
|
|
|
type reachabilityStore struct {
|
|
dag *BlockDAG
|
|
dirty map[daghash.Hash]struct{}
|
|
loaded map[daghash.Hash]*reachabilityData
|
|
}
|
|
|
|
func newReachabilityStore(dag *BlockDAG) *reachabilityStore {
|
|
return &reachabilityStore{
|
|
dag: dag,
|
|
dirty: make(map[daghash.Hash]struct{}),
|
|
loaded: make(map[daghash.Hash]*reachabilityData),
|
|
}
|
|
}
|
|
|
|
func (store *reachabilityStore) setTreeNode(treeNode *reachabilityTreeNode) error {
|
|
// load the reachability data from DB to store.loaded
|
|
node := treeNode.blockNode
|
|
_, exists := store.reachabilityDataByHash(node.hash)
|
|
if !exists {
|
|
store.loaded[*node.hash] = &reachabilityData{}
|
|
}
|
|
|
|
store.loaded[*node.hash].treeNode = treeNode
|
|
store.setBlockAsDirty(node.hash)
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) setFutureCoveringSet(node *blockNode, futureCoveringSet futureCoveringBlockSet) error {
|
|
// load the reachability data from DB to store.loaded
|
|
_, exists := store.reachabilityDataByHash(node.hash)
|
|
if !exists {
|
|
return reachabilityNotFoundError(node)
|
|
}
|
|
|
|
store.loaded[*node.hash].futureCoveringSet = futureCoveringSet
|
|
store.setBlockAsDirty(node.hash)
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) setBlockAsDirty(blockHash *daghash.Hash) {
|
|
store.dirty[*blockHash] = struct{}{}
|
|
}
|
|
|
|
func reachabilityNotFoundError(node *blockNode) error {
|
|
return errors.Errorf("Couldn't find reachability data for block %s", node.hash)
|
|
}
|
|
|
|
func (store *reachabilityStore) treeNodeByBlockNode(node *blockNode) (*reachabilityTreeNode, error) {
|
|
reachabilityData, exists := store.reachabilityDataByHash(node.hash)
|
|
if !exists {
|
|
return nil, reachabilityNotFoundError(node)
|
|
}
|
|
return reachabilityData.treeNode, nil
|
|
}
|
|
|
|
func (store *reachabilityStore) futureCoveringSetByBlockNode(node *blockNode) (futureCoveringBlockSet, error) {
|
|
reachabilityData, exists := store.reachabilityDataByHash(node.hash)
|
|
if !exists {
|
|
return nil, reachabilityNotFoundError(node)
|
|
}
|
|
return reachabilityData.futureCoveringSet, nil
|
|
}
|
|
|
|
func (store *reachabilityStore) reachabilityDataByHash(hash *daghash.Hash) (*reachabilityData, bool) {
|
|
reachabilityData, ok := store.loaded[*hash]
|
|
return reachabilityData, ok
|
|
}
|
|
|
|
// flushToDB writes all dirty reachability data to the database.
|
|
func (store *reachabilityStore) flushToDB(dbTx database.Tx) error {
|
|
if len(store.dirty) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for hash := range store.dirty {
|
|
reachabilityData := store.loaded[hash]
|
|
err := store.dbStoreReachabilityData(dbTx, &hash, reachabilityData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) clearDirtyEntries() {
|
|
store.dirty = make(map[daghash.Hash]struct{})
|
|
}
|
|
|
|
func (store *reachabilityStore) init(dbTx database.Tx) error {
|
|
bucket := dbTx.Metadata().Bucket(reachabilityDataBucketName)
|
|
|
|
// TODO: (Stas) This is a quick and dirty hack.
|
|
// We iterate over the entire bucket twice:
|
|
// * First, populate the loaded set with all entries
|
|
// * Second, connect the parent/children pointers in each entry
|
|
// with other nodes, which are now guaranteed to exist
|
|
cursor := bucket.Cursor()
|
|
for ok := cursor.First(); ok; ok = cursor.Next() {
|
|
err := store.initReachabilityData(cursor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
cursor = bucket.Cursor()
|
|
for ok := cursor.First(); ok; ok = cursor.Next() {
|
|
err := store.loadReachabilityDataFromCursor(cursor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) initReachabilityData(cursor database.Cursor) error {
|
|
hash, err := daghash.NewHash(cursor.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
store.loaded[*hash] = &reachabilityData{
|
|
treeNode: &reachabilityTreeNode{},
|
|
futureCoveringSet: nil,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) loadReachabilityDataFromCursor(cursor database.Cursor) error {
|
|
hash, err := daghash.NewHash(cursor.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
reachabilityData, ok := store.reachabilityDataByHash(hash)
|
|
if !ok {
|
|
return errors.Errorf("cannot find reachability data for block hash: %s", hash)
|
|
}
|
|
|
|
err = store.deserializeReachabilityData(cursor.Value(), reachabilityData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Connect the treeNode with its blockNode
|
|
reachabilityData.treeNode.blockNode = store.dag.index.LookupNode(hash)
|
|
|
|
return nil
|
|
}
|
|
|
|
// dbStoreReachabilityData stores the reachability data to the database.
|
|
// This overwrites the current entry if there exists one.
|
|
func (store *reachabilityStore) dbStoreReachabilityData(dbTx database.Tx, hash *daghash.Hash, reachabilityData *reachabilityData) error {
|
|
serializedReachabilyData, err := store.serializeReachabilityData(reachabilityData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return dbTx.Metadata().Bucket(reachabilityDataBucketName).Put(hash[:], serializedReachabilyData)
|
|
}
|
|
|
|
func (store *reachabilityStore) serializeReachabilityData(reachabilityData *reachabilityData) ([]byte, error) {
|
|
w := &bytes.Buffer{}
|
|
err := store.serializeTreeNode(w, reachabilityData.treeNode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = store.serializeFutureCoveringSet(w, reachabilityData.futureCoveringSet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w.Bytes(), nil
|
|
}
|
|
|
|
func (store *reachabilityStore) serializeTreeNode(w io.Writer, treeNode *reachabilityTreeNode) error {
|
|
// Serialize the interval
|
|
err := store.serializeReachabilityInterval(w, treeNode.interval)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Serialize the remaining interval
|
|
err = store.serializeReachabilityInterval(w, treeNode.remainingInterval)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Serialize the parent
|
|
// If this is the genesis block, write the zero hash instead
|
|
parentHash := &daghash.ZeroHash
|
|
if treeNode.parent != nil {
|
|
parentHash = treeNode.parent.blockNode.hash
|
|
}
|
|
err = wire.WriteElement(w, parentHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Serialize the amount of children
|
|
err = wire.WriteVarInt(w, uint64(len(treeNode.children)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Serialize the children
|
|
for _, child := range treeNode.children {
|
|
err = wire.WriteElement(w, child.blockNode.hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) serializeReachabilityInterval(w io.Writer, interval *reachabilityInterval) error {
|
|
// Serialize start
|
|
err := wire.WriteElement(w, interval.start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Serialize end
|
|
err = wire.WriteElement(w, interval.end)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) serializeFutureCoveringSet(w io.Writer, futureCoveringSet futureCoveringBlockSet) error {
|
|
// Serialize the set size
|
|
err := wire.WriteVarInt(w, uint64(len(futureCoveringSet)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Serialize each block in the set
|
|
for _, block := range futureCoveringSet {
|
|
err = wire.WriteElement(w, block.blockNode.hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) deserializeReachabilityData(
|
|
serializedReachabilityDataBytes []byte, destination *reachabilityData) error {
|
|
|
|
r := bytes.NewBuffer(serializedReachabilityDataBytes)
|
|
|
|
// Deserialize the tree node
|
|
err := store.deserializeTreeNode(r, destination)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Deserialize the future covering set
|
|
err = store.deserializeFutureCoveringSet(r, destination)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) deserializeTreeNode(r io.Reader, destination *reachabilityData) error {
|
|
// Deserialize the interval
|
|
interval, err := store.deserializeReachabilityInterval(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
destination.treeNode.interval = interval
|
|
|
|
// Deserialize the remaining interval
|
|
remainingInterval, err := store.deserializeReachabilityInterval(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
destination.treeNode.remainingInterval = remainingInterval
|
|
|
|
// Deserialize the parent
|
|
// If this is the zero hash, this node is the genesis and as such doesn't have a parent
|
|
parentHash := &daghash.Hash{}
|
|
err = wire.ReadElement(r, parentHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !daghash.ZeroHash.IsEqual(parentHash) {
|
|
parentReachabilityData, ok := store.reachabilityDataByHash(parentHash)
|
|
if !ok {
|
|
return errors.Errorf("parent reachability data not found for hash: %s", parentHash)
|
|
}
|
|
destination.treeNode.parent = parentReachabilityData.treeNode
|
|
}
|
|
|
|
// Deserialize the amount of children
|
|
childCount, err := wire.ReadVarInt(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Deserialize the children
|
|
children := make([]*reachabilityTreeNode, childCount)
|
|
for i := uint64(0); i < childCount; i++ {
|
|
childHash := &daghash.Hash{}
|
|
err = wire.ReadElement(r, childHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
childReachabilityData, ok := store.reachabilityDataByHash(childHash)
|
|
if !ok {
|
|
return errors.Errorf("child reachability data not found for hash: %s", parentHash)
|
|
}
|
|
children[i] = childReachabilityData.treeNode
|
|
}
|
|
destination.treeNode.children = children
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) deserializeReachabilityInterval(r io.Reader) (*reachabilityInterval, error) {
|
|
interval := &reachabilityInterval{}
|
|
|
|
// Deserialize start
|
|
start := uint64(0)
|
|
err := wire.ReadElement(r, &start)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
interval.start = start
|
|
|
|
// Deserialize end
|
|
end := uint64(0)
|
|
err = wire.ReadElement(r, &end)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
interval.end = end
|
|
|
|
return interval, nil
|
|
}
|
|
|
|
func (store *reachabilityStore) deserializeFutureCoveringSet(r io.Reader, destination *reachabilityData) error {
|
|
// Deserialize the set size
|
|
setSize, err := wire.ReadVarInt(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Deserialize each block in the set
|
|
futureCoveringSet := make(futureCoveringBlockSet, setSize)
|
|
for i := uint64(0); i < setSize; i++ {
|
|
blockHash := &daghash.Hash{}
|
|
err = wire.ReadElement(r, blockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
blockNode := store.dag.index.LookupNode(blockHash)
|
|
if blockNode == nil {
|
|
return errors.Errorf("blockNode not found for hash %s", blockHash)
|
|
}
|
|
blockReachabilityData, ok := store.reachabilityDataByHash(blockHash)
|
|
if !ok {
|
|
return errors.Errorf("block reachability data not found for hash: %s", blockHash)
|
|
}
|
|
futureCoveringSet[i] = &futureCoveringBlock{
|
|
blockNode: blockNode,
|
|
treeNode: blockReachabilityData.treeNode,
|
|
}
|
|
}
|
|
destination.futureCoveringSet = futureCoveringSet
|
|
|
|
return nil
|
|
}
|