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

* [NOD-1055] Give higher priority for requesting missing ancestors when sending a getdata message (#767) * [NOD-1063] Remove the remainingInterval field. * [NOD-1063] Add helper functions to reachabilityTreeNode. * [NOD-1063] Add reachabilityReindexRoot. * [NOD-1063] Start implementing findNextReachabilityReindexRoot. * [NOD-1063] Implement findCommonAncestor. * [NOD-1063] Implement findReachabilityTreeAncestorInChildren. * [NOD-1063] Add reachabilityReindexWindow. * [NOD-1063] Fix findReachabilityTreeAncestorInChildren. * [NOD-1063] Remove BlockDAG reference in findReachabilityTreeAncestorInChildren. * [NOD-1063] Extract updateReachabilityReindexRoot to a separate function. * [NOD-1063] Add reachabilityReindexSlack. * [NOD-1063] Implement splitReindexRootChildrenAroundChosen. * [NOD-1063] Implement calcReachabilityTreeNodeSizes. * [NOD-1063] Implement propagateChildIntervals. * [NOD-1063] Extract tightenReachabilityTreeIntervalsBeforeChosenReindexRootChild and tightenReachabilityTreeIntervalsAfterChosenReindexRootChild to separate functions. * [NOD-1063] Implement expandReachabilityTreeIntervalInChosenReindexRootChild. * [NOD-1063] Finished implementing concentrateReachabilityTreeIntervalAroundReindexRootChild. * [NOD-1063] Begin implementing reindexIntervalsBeforeReindexRoot. * [NOD-1063] Implement top-level logic of reindexIntervalsBeforeReindexRoot. * [NOD-1063] Implement reclaimIntervalBeforeChosenChild. * [NOD-1063] Add a debug log for reindexIntervalsBeforeReindexRoot. * [NOD-1063] Rename reindexIntervalsBeforeReindexRoot to reindexIntervalsEarlierThanReindexRoot. * [NOD-1063] Implement reclaimIntervalAfterChosenChild. * [NOD-1063] Add a debug log for updateReachabilityReindexRoot. * [NOD-1063] Convert modifiedTreeNodes from slices to sets. * [NOD-1063] Fix findCommonAncestor. * [NOD-1063] Fix reindexIntervalsEarlierThanReindexRoot.` * [NOD-1063] Remove redundant nil conditions. * [NOD-1063] Make map[*reachabilityTreeNode]struct{} into a type alias with a copyAllFrom method. * [NOD-1063] Remove setInterval. * [NOD-1063] Create a new struct to hold reachability stuff called reachabilityTree. * [NOD-1063] Rename functions under reachabilityTree. * [NOD-1063] Move reachabilityStore into reachabilityTree. * [NOD-1063] Move the rest of the functions in reachability.go into the reachabilityTree struct. * [NOD-1063] Update newReachabilityTree to take an instance of reachabilityStore. * [NOD-1063] Fix merge errors. * [NOD-1063] Fix merge errors. * [NOD-1063] Pass a reference to the dag into reachabilityTree. * [NOD-1063] Use Wrapf instead of Errorf. * [NOD-1063] Merge assignments. * [NOD-1063] Disambiguate a varaible name. * [NOD-1063] Add a test case for intervalBefore. * [NOD-1063] Simplify splitChildrenAroundChosenChild. * [NOD-1063] Fold temporary variables into newReachabilityInterval. * [NOD-1063] Fold more temporary variables into newReachabilityInterval. * [NOD-1063] Fix a bug in expandIntervalInReindexRootChosenChild. * [NOD-1063] Remove blockNode from futureCoveringBlock. * [NOD-1063] Get rid of futureCoveringBlock. * [NOD-1063] Use findIndex directly in findAncestorAmongChildren. * [NOD-1063] Make findIndex a bit nicer to use. Also rename it to findAncestorIndexOfNode. * [NOD-1063] Rename childIntervalAllocationRange to intervalRangeForChildAllocation. * [NOD-1063] Optimize findCommonAncestor. * [NOD-1063] In reindexIntervalsBeforeChosenChild, use chosenChild.interval.start - 1 instead of childrenBeforeChosen[len(childrenBeforeChosen)-1].interval.end + 1. * [NOD-1063] Rename reindexIntervalsBeforeChosenChild to reindexIntervalsBeforeNode. * [NOD-1063] Add a comment explain what "the chosen child" is. * [NOD-1063] In concentrateIntervalAroundReindexRootChosenChild, rename modifiedTreeNodes to allModifiedTreeNodes. * [NOD-1063] Extract propagateIntervals to a function. * [NOD-1063] Extract interval "contains" logic to a separate function. * [NOD-1063] Simplify "looping up" logic in reclaimIntervalXXXChosenChild. * [NOD-1063] Add comments to reclaimIntervalXXXChosenChild. * [NOD-1063] Rename copyAllFrom to addAll. * [NOD-1063] Rename reachabilityStore (the variable) to just store. * [NOD-1063] Fix an error message. * [NOD-1063] Reword a comment. * [NOD-1063] Don't return -1 from findAncestorIndexOfNode. * [NOD-1063] Extract slackReachabilityIntervalForReclaiming to a constant. * [NOD-1063] Add a missing condition. * [NOD-1063] Call isAncestorOf directly in insertNode. * [NOD-1063] Rename chosenReindexRootChild to reindexRootChosenChild. * [NOD-1063] Rename treeNodeSet to orderedTreeNodeSet. * [NOD-1063] Add a disclaimer to orderedTreeNodeSet. * [NOD-1063] Implement StoreReachabilityReindexRoot and FetchReachabilityReindexRoot. * [NOD-1063] Move storing the reindex root to within reachabilityTree. * [NOD-1063] Remove isAncestorOf from reachabilityInterval. * [NOD-1063] Add a comment about graph theory conventions. * [NOD-1063] Fix tests. * [NOD-1063] Change inclusion in isAncestorOf functions. * [NOD-1063] Rename a test. * [NOD-1063] Implement TestIsInFuture. * [NOD-1063] Fix error messages in TestIsInFuture. * [NOD-1063] Fix error messages in TestIsInFuture. * [NOD-1063] Rename isInSelectedParentChain to isInSelectedParentChainOf. * [NOD-1063] Rename isInFuture to isInPast. * [NOD-1063] Expand on a comment. * [NOD-1063] Rename modifiedTreeNodes. * [NOD-1063] Implement test: TestReindexIntervalsEarlierThanReindexRoot. * [NOD-1063] Implement test: TestUpdateReindexRoot. * [NOD-1063] Explain a check. * [NOD-1063] Use a method instead of calling reachabilityStore.loaded directly. * [NOD-1063] Lowercasified an error message. * [NOD-1063] Fix failing test. Co-authored-by: Ori Newman <orinewman1@gmail.com>
399 lines
10 KiB
Go
399 lines
10 KiB
Go
package blockdag
|
|
|
|
import (
|
|
"bytes"
|
|
"github.com/kaspanet/kaspad/database"
|
|
"github.com/kaspanet/kaspad/dbaccess"
|
|
"github.com/kaspanet/kaspad/util/daghash"
|
|
"github.com/kaspanet/kaspad/wire"
|
|
"github.com/pkg/errors"
|
|
"io"
|
|
)
|
|
|
|
type reachabilityData struct {
|
|
treeNode *reachabilityTreeNode
|
|
futureCoveringSet futureCoveringTreeNodeSet
|
|
}
|
|
|
|
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) {
|
|
// 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)
|
|
}
|
|
|
|
func (store *reachabilityStore) setFutureCoveringSet(node *blockNode, futureCoveringSet futureCoveringTreeNodeSet) error {
|
|
// load the reachability data from DB to store.loaded
|
|
_, exists := store.reachabilityDataByHash(node.hash)
|
|
if !exists {
|
|
return reachabilityNotFoundError(node.hash)
|
|
}
|
|
|
|
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(hash *daghash.Hash) error {
|
|
return errors.Errorf("couldn't find reachability data for block %s", hash)
|
|
}
|
|
|
|
func (store *reachabilityStore) treeNodeByBlockHash(hash *daghash.Hash) (*reachabilityTreeNode, error) {
|
|
reachabilityData, exists := store.reachabilityDataByHash(hash)
|
|
if !exists {
|
|
return nil, reachabilityNotFoundError(hash)
|
|
}
|
|
return reachabilityData.treeNode, nil
|
|
}
|
|
|
|
func (store *reachabilityStore) treeNodeByBlockNode(node *blockNode) (*reachabilityTreeNode, error) {
|
|
return store.treeNodeByBlockHash(node.hash)
|
|
}
|
|
|
|
func (store *reachabilityStore) futureCoveringSetByBlockNode(node *blockNode) (futureCoveringTreeNodeSet, error) {
|
|
reachabilityData, exists := store.reachabilityDataByHash(node.hash)
|
|
if !exists {
|
|
return nil, reachabilityNotFoundError(node.hash)
|
|
}
|
|
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(dbContext *dbaccess.TxContext) error {
|
|
if len(store.dirty) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for hash := range store.dirty {
|
|
hash := hash // Copy hash to a new variable to avoid passing the same pointer
|
|
reachabilityData := store.loaded[hash]
|
|
err := store.storeReachabilityData(dbContext, &hash, reachabilityData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) clearDirtyEntries() {
|
|
store.dirty = make(map[daghash.Hash]struct{})
|
|
}
|
|
|
|
func (store *reachabilityStore) init(dbContext dbaccess.Context) error {
|
|
// 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, err := dbaccess.ReachabilityDataCursor(dbContext)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cursor.Close()
|
|
|
|
for ok := cursor.First(); ok; ok = cursor.Next() {
|
|
err := store.initReachabilityData(cursor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
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 {
|
|
key, err := cursor.Key()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hash, err := daghash.NewHash(key.Suffix())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
store.loaded[*hash] = &reachabilityData{
|
|
treeNode: &reachabilityTreeNode{},
|
|
futureCoveringSet: nil,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *reachabilityStore) loadReachabilityDataFromCursor(cursor database.Cursor) error {
|
|
key, err := cursor.Key()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hash, err := daghash.NewHash(key.Suffix())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
reachabilityData, ok := store.reachabilityDataByHash(hash)
|
|
if !ok {
|
|
return errors.Errorf("cannot find reachability data for block hash: %s", hash)
|
|
}
|
|
|
|
serializedReachabilityData, err := cursor.Value()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = store.deserializeReachabilityData(serializedReachabilityData, reachabilityData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Connect the treeNode with its blockNode
|
|
reachabilityData.treeNode.blockNode, ok = store.dag.index.LookupNode(hash)
|
|
if !ok {
|
|
return errors.Errorf("block %s does not exist in the DAG", hash)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// storeReachabilityData stores the reachability data to the database.
|
|
// This overwrites the current entry if there exists one.
|
|
func (store *reachabilityStore) storeReachabilityData(dbContext dbaccess.Context, hash *daghash.Hash, reachabilityData *reachabilityData) error {
|
|
serializedReachabilyData, err := store.serializeReachabilityData(reachabilityData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return dbaccess.StoreReachabilityData(dbContext, 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 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 futureCoveringTreeNodeSet) error {
|
|
// Serialize the set size
|
|
err := wire.WriteVarInt(w, uint64(len(futureCoveringSet)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Serialize each node in the set
|
|
for _, node := range futureCoveringSet {
|
|
err = wire.WriteElement(w, node.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 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(futureCoveringTreeNodeSet, setSize)
|
|
for i := uint64(0); i < setSize; i++ {
|
|
blockHash := &daghash.Hash{}
|
|
err = wire.ReadElement(r, blockHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
blockReachabilityData, ok := store.reachabilityDataByHash(blockHash)
|
|
if !ok {
|
|
return errors.Errorf("block reachability data not found for hash: %s", blockHash)
|
|
}
|
|
futureCoveringSet[i] = blockReachabilityData.treeNode
|
|
}
|
|
destination.futureCoveringSet = futureCoveringSet
|
|
|
|
return nil
|
|
}
|