mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-22 14:02:32 +00:00
[NOD-1417] Implement reachability (#964)
* [NOD-1417] Implement reachability * [NOD-1417] Rename package name * [NOD-1417] Add UpdateReindexRoot to interface api * [NOD-1417] Remove redundant type * [NOD-1417] Rename reachabilityTreeManager/reachabilityTree to reachabilityManager * [NOD-1417] Fix typo * [NOD-1417] Remove redundant copyright message * [NOD-1417] Fix comment
This commit is contained in:
parent
a132f55302
commit
a436b30ebf
@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager"
|
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/processes/pastmediantimemanager"
|
"github.com/kaspanet/kaspad/domain/consensus/processes/pastmediantimemanager"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/processes/pruningmanager"
|
"github.com/kaspanet/kaspad/domain/consensus/processes/pruningmanager"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/processes/reachabilitytree"
|
"github.com/kaspanet/kaspad/domain/consensus/processes/reachabilitymanager"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/processes/transactionvalidator"
|
"github.com/kaspanet/kaspad/domain/consensus/processes/transactionvalidator"
|
||||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||||
@ -56,12 +56,14 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db database.Database
|
|||||||
dbManager := dbmanager.New(db)
|
dbManager := dbmanager.New(db)
|
||||||
|
|
||||||
// Processes
|
// Processes
|
||||||
reachabilityTree := reachabilitytree.New(
|
reachabilityManager := reachabilitymanager.New(
|
||||||
|
dbManager,
|
||||||
|
ghostdagDataStore,
|
||||||
blockRelationStore,
|
blockRelationStore,
|
||||||
reachabilityDataStore)
|
reachabilityDataStore)
|
||||||
dagTopologyManager := dagtopologymanager.New(
|
dagTopologyManager := dagtopologymanager.New(
|
||||||
dbManager,
|
dbManager,
|
||||||
reachabilityTree,
|
reachabilityManager,
|
||||||
blockRelationStore)
|
blockRelationStore)
|
||||||
ghostdagManager := ghostdagmanager.New(
|
ghostdagManager := ghostdagmanager.New(
|
||||||
dbManager,
|
dbManager,
|
||||||
@ -137,7 +139,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db database.Database
|
|||||||
pruningManager,
|
pruningManager,
|
||||||
blockValidator,
|
blockValidator,
|
||||||
dagTopologyManager,
|
dagTopologyManager,
|
||||||
reachabilityTree,
|
reachabilityManager,
|
||||||
difficultyManager,
|
difficultyManager,
|
||||||
pastMedianTimeManager,
|
pastMedianTimeManager,
|
||||||
ghostdagManager,
|
ghostdagManager,
|
||||||
|
@ -8,4 +8,5 @@ type ReachabilityTree interface {
|
|||||||
AddBlock(blockHash *externalapi.DomainHash) error
|
AddBlock(blockHash *externalapi.DomainHash) error
|
||||||
IsReachabilityTreeAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
|
IsReachabilityTreeAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
|
||||||
IsDAGAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
|
IsDAGAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
|
||||||
|
UpdateReindexRoot(selectedTip *externalapi.DomainHash) error
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
|
||||||
// ReachabilityData holds the set of data required to answer
|
// ReachabilityData holds the set of data required to answer
|
||||||
// reachability queries
|
// reachability queries
|
||||||
type ReachabilityData struct {
|
type ReachabilityData struct {
|
||||||
@ -23,8 +25,8 @@ type ReachabilityData struct {
|
|||||||
// case, and so reindexing should always succeed unless more than
|
// case, and so reindexing should always succeed unless more than
|
||||||
// 2^64 blocks are added to the DAG/tree.
|
// 2^64 blocks are added to the DAG/tree.
|
||||||
type ReachabilityTreeNode struct {
|
type ReachabilityTreeNode struct {
|
||||||
Children []*ReachabilityTreeNode
|
Children []*externalapi.DomainHash
|
||||||
Parent *ReachabilityTreeNode
|
Parent *externalapi.DomainHash
|
||||||
|
|
||||||
// interval is the index interval containing all intervals of
|
// interval is the index interval containing all intervals of
|
||||||
// blocks in this node's subtree
|
// blocks in this node's subtree
|
||||||
@ -39,12 +41,6 @@ type ReachabilityInterval struct {
|
|||||||
End uint64
|
End uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderedTreeNodeSet is an ordered set of reachabilityTreeNodes
|
|
||||||
// Note that this type does not validate order validity. It's the
|
|
||||||
// responsibility of the caller to construct instances of this
|
|
||||||
// type properly.
|
|
||||||
type OrderedTreeNodeSet []*ReachabilityTreeNode
|
|
||||||
|
|
||||||
// FutureCoveringTreeNodeSet represents a collection of blocks in the future of
|
// FutureCoveringTreeNodeSet represents a collection of blocks in the future of
|
||||||
// a certain block. Once a block B is added to the DAG, every block A_i in
|
// a certain block. Once a block B is added to the DAG, every block A_i in
|
||||||
// B's selected parent anticone must register B in its FutureCoveringTreeNodeSet. This allows
|
// B's selected parent anticone must register B in its FutureCoveringTreeNodeSet. This allows
|
||||||
@ -58,4 +54,4 @@ type OrderedTreeNodeSet []*ReachabilityTreeNode
|
|||||||
//
|
//
|
||||||
// See insertNode, hasAncestorOf, and reachabilityTree.isInPast for further
|
// See insertNode, hasAncestorOf, and reachabilityTree.isInPast for further
|
||||||
// details.
|
// details.
|
||||||
type FutureCoveringTreeNodeSet OrderedTreeNodeSet
|
type FutureCoveringTreeNodeSet []*externalapi.DomainHash
|
||||||
|
59
domain/consensus/processes/reachabilitymanager/fetch.go
Normal file
59
domain/consensus/processes/reachabilitymanager/fetch.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package reachabilitymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) data(blockHash *externalapi.DomainHash) (*model.ReachabilityData, error) {
|
||||||
|
return rt.reachabilityDataStore.ReachabilityData(rt.databaseContext, blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) futureCoveringSet(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||||
|
data, err := rt.data(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.FutureCoveringSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) treeNode(blockHash *externalapi.DomainHash) (*model.ReachabilityTreeNode, error) {
|
||||||
|
data, err := rt.data(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.TreeNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) interval(blockHash *externalapi.DomainHash) (*model.ReachabilityInterval, error) {
|
||||||
|
treeNode, err := rt.treeNode(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return treeNode.Interval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) children(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||||
|
data, err := rt.data(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.TreeNode.Children, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) parent(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
|
||||||
|
data, err := rt.data(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.TreeNode.Parent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) reindexRoot() (*externalapi.DomainHash, error) {
|
||||||
|
return rt.reachabilityDataStore.ReachabilityReindexRoot(rt.databaseContext)
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
package reachabilitymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// futureCoveringTreeNodeSet represents a collection of blocks in the future of
|
||||||
|
// a certain block. Once a block B is added to the DAG, every block A_i in
|
||||||
|
// B's selected parent anticone must register B in its futureCoveringTreeNodeSet. This allows
|
||||||
|
// to relatively quickly (O(log(|futureCoveringTreeNodeSet|))) query whether B
|
||||||
|
// is a descendent (is in the "future") of any block that previously
|
||||||
|
// registered it.
|
||||||
|
//
|
||||||
|
// Note that futureCoveringTreeNodeSet is meant to be queried only if B is not
|
||||||
|
// a reachability tree descendant of the block in question, as reachability
|
||||||
|
// tree queries are always O(1).
|
||||||
|
//
|
||||||
|
// See insertNode, hasAncestorOf, and reachabilityTree.isInPast for further
|
||||||
|
// details.
|
||||||
|
type futureCoveringTreeNodeSet orderedTreeNodeSet
|
||||||
|
|
||||||
|
// insertToFutureCoveringSet inserts the given block into this node's FutureCoveringSet
|
||||||
|
// while keeping it ordered by interval.
|
||||||
|
// If a block B ∈ node.FutureCoveringSet exists such that its interval
|
||||||
|
// contains block's interval, block need not be added. If block's
|
||||||
|
// interval contains B's interval, it replaces it.
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
// * Intervals never intersect unless one contains the other
|
||||||
|
// (this follows from the tree structure and the indexing rule).
|
||||||
|
// * Since node.FutureCoveringSet is kept ordered, a binary search can be
|
||||||
|
// used for insertion/queries.
|
||||||
|
// * Although reindexing may change a block's interval, the
|
||||||
|
// is-superset relation will by definition
|
||||||
|
// be always preserved.
|
||||||
|
func (rt *reachabilityManager) insertToFutureCoveringSet(node, futureNode *externalapi.DomainHash) error {
|
||||||
|
futureCoveringSet, err := rt.futureCoveringSet(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ancestorIndex, ok, err := rt.findAncestorIndexOfNode(futureCoveringSet, futureNode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
newSet := append([]*externalapi.DomainHash{futureNode}, futureCoveringSet...)
|
||||||
|
err := rt.stageFutureCoveringSet(node, newSet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate := futureCoveringSet[ancestorIndex]
|
||||||
|
candidateIsAncestorOfFutureNode, err := rt.IsReachabilityTreeAncestorOf(candidate, futureNode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if candidateIsAncestorOfFutureNode {
|
||||||
|
// candidate is an ancestor of futureNode, no need to insert
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
futureNodeIsAncestorOfCandidate, err := rt.IsReachabilityTreeAncestorOf(futureNode, candidate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if futureNodeIsAncestorOfCandidate {
|
||||||
|
// futureNode is an ancestor of candidate, and can thus replace it
|
||||||
|
newSet := make([]*externalapi.DomainHash, len(futureCoveringSet))
|
||||||
|
copy(newSet, futureCoveringSet)
|
||||||
|
newSet[ancestorIndex] = futureNode
|
||||||
|
|
||||||
|
return rt.stageFutureCoveringSet(node, newSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert futureNode in the correct index to maintain futureCoveringTreeNodeSet as
|
||||||
|
// a sorted-by-interval list.
|
||||||
|
// Note that ancestorIndex might be equal to len(futureCoveringTreeNodeSet)
|
||||||
|
left := futureCoveringSet[:ancestorIndex+1]
|
||||||
|
right := append([]*externalapi.DomainHash{futureNode}, futureCoveringSet[ancestorIndex+1:]...)
|
||||||
|
newSet := append(left, right...)
|
||||||
|
return rt.stageFutureCoveringSet(node, newSet)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// futureCoveringSetHasAncestorOf resolves whether the given node `other` is in the subtree of
|
||||||
|
// any node in this.FutureCoveringSet.
|
||||||
|
// See insertNode method for the complementary insertion behavior.
|
||||||
|
//
|
||||||
|
// Like the insert method, this method also relies on the fact that
|
||||||
|
// this.FutureCoveringSet is kept ordered by interval to efficiently perform a
|
||||||
|
// binary search over this.FutureCoveringSet and answer the query in
|
||||||
|
// O(log(|futureCoveringTreeNodeSet|)).
|
||||||
|
func (rt *reachabilityManager) futureCoveringSetHasAncestorOf(this, other *externalapi.DomainHash) (bool, error) {
|
||||||
|
futureCoveringSet, err := rt.futureCoveringSet(this)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ancestorIndex, ok, err := rt.findAncestorIndexOfNode(futureCoveringSet, other)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// No candidate to contain other
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate := futureCoveringSet[ancestorIndex]
|
||||||
|
return rt.IsReachabilityTreeAncestorOf(candidate, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
// futureCoveringSetString returns a string representation of the intervals in this futureCoveringSet.
|
||||||
|
func (rt *reachabilityManager) futureCoveringSetString(futureCoveringSet []*externalapi.DomainHash) (string, error) {
|
||||||
|
intervalsString := ""
|
||||||
|
for _, node := range futureCoveringSet {
|
||||||
|
nodeInterval, err := rt.interval(node)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
intervalsString += intervalString(nodeInterval)
|
||||||
|
}
|
||||||
|
return intervalsString, nil
|
||||||
|
}
|
123
domain/consensus/processes/reachabilitymanager/interval.go
Normal file
123
domain/consensus/processes/reachabilitymanager/interval.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package reachabilitymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newReachabilityInterval(start uint64, end uint64) *model.ReachabilityInterval {
|
||||||
|
return &model.ReachabilityInterval{Start: start, End: end}
|
||||||
|
}
|
||||||
|
|
||||||
|
// intervalSize returns the size of this interval. Note that intervals are
|
||||||
|
// inclusive from both sides.
|
||||||
|
func intervalSize(ri *model.ReachabilityInterval) uint64 {
|
||||||
|
return ri.End - ri.Start + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// intervalSplitInHalf splits this interval by a fraction of 0.5.
|
||||||
|
// See splitFraction for further details.
|
||||||
|
func intervalSplitInHalf(ri *model.ReachabilityInterval) (
|
||||||
|
left *model.ReachabilityInterval, right *model.ReachabilityInterval, err error) {
|
||||||
|
|
||||||
|
return intervalSplitFraction(ri, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// intervalSplitFraction splits this interval to two parts such that their
|
||||||
|
// union is equal to the original interval and the first (left) part
|
||||||
|
// contains the given fraction of the original interval's size.
|
||||||
|
// Note: if the split results in fractional parts, this method rounds
|
||||||
|
// the first part up and the last part down.
|
||||||
|
func intervalSplitFraction(ri *model.ReachabilityInterval, fraction float64) (
|
||||||
|
left *model.ReachabilityInterval, right *model.ReachabilityInterval, err error) {
|
||||||
|
|
||||||
|
if fraction < 0 || fraction > 1 {
|
||||||
|
return nil, nil, errors.Errorf("fraction must be between 0 and 1")
|
||||||
|
}
|
||||||
|
if intervalSize(ri) == 0 {
|
||||||
|
return nil, nil, errors.Errorf("cannot split an empty interval")
|
||||||
|
}
|
||||||
|
|
||||||
|
allocationSize := uint64(math.Ceil(float64(intervalSize(ri)) * fraction))
|
||||||
|
left = newReachabilityInterval(ri.Start, ri.Start+allocationSize-1)
|
||||||
|
right = newReachabilityInterval(ri.Start+allocationSize, ri.End)
|
||||||
|
return left, right, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// intervalSplitExact splits this interval to exactly |sizes| parts where
|
||||||
|
// |part_i| = sizes[i]. This method expects sum(sizes) to be exactly
|
||||||
|
// equal to the interval's size.
|
||||||
|
func intervalSplitExact(ri *model.ReachabilityInterval, sizes []uint64) ([]*model.ReachabilityInterval, error) {
|
||||||
|
sizesSum := uint64(0)
|
||||||
|
for _, size := range sizes {
|
||||||
|
sizesSum += size
|
||||||
|
}
|
||||||
|
if sizesSum != intervalSize(ri) {
|
||||||
|
return nil, errors.Errorf("sum of sizes must be equal to the interval's size")
|
||||||
|
}
|
||||||
|
|
||||||
|
intervals := make([]*model.ReachabilityInterval, len(sizes))
|
||||||
|
start := ri.Start
|
||||||
|
for i, size := range sizes {
|
||||||
|
intervals[i] = newReachabilityInterval(start, start+size-1)
|
||||||
|
start += size
|
||||||
|
}
|
||||||
|
return intervals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// intervalSplitWithExponentialBias splits this interval to |sizes| parts
|
||||||
|
// by the allocation rule described below. This method expects sum(sizes)
|
||||||
|
// to be smaller or equal to the interval's size. Every part_i is
|
||||||
|
// allocated at least sizes[i] capacity. The remaining budget is
|
||||||
|
// split by an exponentially biased rule described below.
|
||||||
|
//
|
||||||
|
// This rule follows the GHOSTDAG protocol behavior where the child
|
||||||
|
// with the largest subtree is expected to dominate the competition
|
||||||
|
// for new blocks and thus grow the most. However, we may need to
|
||||||
|
// add slack for non-largest subtrees in order to make CPU reindexing
|
||||||
|
// attacks unworthy.
|
||||||
|
func intervalSplitWithExponentialBias(ri *model.ReachabilityInterval, sizes []uint64) ([]*model.ReachabilityInterval, error) {
|
||||||
|
intervalSize := intervalSize(ri)
|
||||||
|
sizesSum := uint64(0)
|
||||||
|
for _, size := range sizes {
|
||||||
|
sizesSum += size
|
||||||
|
}
|
||||||
|
if sizesSum > intervalSize {
|
||||||
|
return nil, errors.Errorf("sum of sizes must be less than or equal to the interval's size")
|
||||||
|
}
|
||||||
|
if sizesSum == intervalSize {
|
||||||
|
return intervalSplitExact(ri, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a fractional bias to every size in the given sizes
|
||||||
|
totalBias := intervalSize - sizesSum
|
||||||
|
remainingBias := totalBias
|
||||||
|
biasedSizes := make([]uint64, len(sizes))
|
||||||
|
fractions := exponentialFractions(sizes)
|
||||||
|
for i, fraction := range fractions {
|
||||||
|
var bias uint64
|
||||||
|
if i == len(fractions)-1 {
|
||||||
|
bias = remainingBias
|
||||||
|
} else {
|
||||||
|
bias = uint64(math.Round(float64(totalBias) * fraction))
|
||||||
|
if bias > remainingBias {
|
||||||
|
bias = remainingBias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
biasedSizes[i] = sizes[i] + bias
|
||||||
|
remainingBias -= bias
|
||||||
|
}
|
||||||
|
return intervalSplitExact(ri, biasedSizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// intervalContains returns true if ri contains other.
|
||||||
|
func intervalContains(ri *model.ReachabilityInterval, other *model.ReachabilityInterval) bool {
|
||||||
|
return ri.Start <= other.Start && other.End <= ri.End
|
||||||
|
}
|
||||||
|
|
||||||
|
// intervalString returns a string representation of the interval.
|
||||||
|
func intervalString(ri *model.ReachabilityInterval) string {
|
||||||
|
return fmt.Sprintf("[%d,%d]", ri.Start, ri.End)
|
||||||
|
}
|
7
domain/consensus/processes/reachabilitymanager/log.go
Normal file
7
domain/consensus/processes/reachabilitymanager/log.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package reachabilitymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log, _ = logger.Get(logger.SubsystemTags.REAC)
|
@ -0,0 +1,79 @@
|
|||||||
|
package reachabilitymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// orderedTreeNodeSet is an ordered set of model.ReachabilityTreeNodes
|
||||||
|
// Note that this type does not validate order validity. It's the
|
||||||
|
// responsibility of the caller to construct instances of this
|
||||||
|
// type properly.
|
||||||
|
type orderedTreeNodeSet []*externalapi.DomainHash
|
||||||
|
|
||||||
|
// findAncestorOfNode finds the reachability tree ancestor of `node`
|
||||||
|
// among the nodes in `tns`.
|
||||||
|
func (rt *reachabilityManager) findAncestorOfNode(tns orderedTreeNodeSet, node *externalapi.DomainHash) (*externalapi.DomainHash, bool) {
|
||||||
|
ancestorIndex, ok, err := rt.findAncestorIndexOfNode(tns, node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return tns[ancestorIndex], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// findAncestorIndexOfNode finds the index of the reachability tree
|
||||||
|
// ancestor of `node` among the nodes in `tns`. It does so by finding
|
||||||
|
// the index of the block with the maximum start that is below the
|
||||||
|
// given block.
|
||||||
|
func (rt *reachabilityManager) findAncestorIndexOfNode(tns orderedTreeNodeSet, node *externalapi.DomainHash) (int, bool, error) {
|
||||||
|
treeNode, err := rt.treeNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockInterval := treeNode.Interval
|
||||||
|
end := blockInterval.End
|
||||||
|
|
||||||
|
low := 0
|
||||||
|
high := len(tns)
|
||||||
|
for low < high {
|
||||||
|
middle := (low + high) / 2
|
||||||
|
middleInterval, err := rt.interval(tns[middle])
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if end < middleInterval.Start {
|
||||||
|
high = middle
|
||||||
|
} else {
|
||||||
|
low = middle + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if low == 0 {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
return low - 1, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) propagateIntervals(tns orderedTreeNodeSet, intervals []*model.ReachabilityInterval,
|
||||||
|
subtreeSizeMaps []map[externalapi.DomainHash]uint64) error {
|
||||||
|
|
||||||
|
for i, node := range tns {
|
||||||
|
err := rt.stageInterval(node, intervals[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
subtreeSizeMap := subtreeSizeMaps[i]
|
||||||
|
err = rt.propagateInterval(node, subtreeSizeMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package reachabilitymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsDAGAncestorOf returns true if blockHashA is an ancestor of
|
||||||
|
// blockHashB in the DAG.
|
||||||
|
//
|
||||||
|
// Note: this method will return true if blockHashA == blockHashB
|
||||||
|
// The complexity of this method is O(log(|this.futureCoveringTreeNodeSet|))
|
||||||
|
func (rt *reachabilityManager) IsDAGAncestorOf(blockHashA, blockHashB *externalapi.DomainHash) (bool, error) {
|
||||||
|
// Check if this node is a reachability tree ancestor of the
|
||||||
|
// other node
|
||||||
|
isReachabilityTreeAncestor, err := rt.IsReachabilityTreeAncestorOf(blockHashA, blockHashB)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if isReachabilityTreeAncestor {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, use previously registered future blocks to complete the
|
||||||
|
// reachability test
|
||||||
|
return rt.futureCoveringSetHasAncestorOf(blockHashA, blockHashB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) UpdateReindexRoot(selectedTip *externalapi.DomainHash) error {
|
||||||
|
return rt.updateReindexRoot(selectedTip)
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package reachabilitymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// reachabilityManager maintains a structure that allows to answer
|
||||||
|
// reachability queries in sub-linear time
|
||||||
|
type reachabilityManager struct {
|
||||||
|
databaseContext model.DBReader
|
||||||
|
blockRelationStore model.BlockRelationStore
|
||||||
|
reachabilityDataStore model.ReachabilityDataStore
|
||||||
|
ghostdagDataStore model.GHOSTDAGDataStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// New instantiates a new reachabilityManager
|
||||||
|
func New(
|
||||||
|
databaseContext model.DBReader,
|
||||||
|
ghostdagDataStore model.GHOSTDAGDataStore,
|
||||||
|
blockRelationStore model.BlockRelationStore,
|
||||||
|
reachabilityDataStore model.ReachabilityDataStore,
|
||||||
|
) model.ReachabilityTree {
|
||||||
|
return &reachabilityManager{
|
||||||
|
databaseContext: databaseContext,
|
||||||
|
ghostdagDataStore: ghostdagDataStore,
|
||||||
|
blockRelationStore: blockRelationStore,
|
||||||
|
reachabilityDataStore: reachabilityDataStore,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlock adds the block with the given blockHash into the reachability tree.
|
||||||
|
func (rt *reachabilityManager) AddBlock(blockHash *externalapi.DomainHash) error {
|
||||||
|
// Allocate a new reachability tree node
|
||||||
|
newTreeNode := newReachabilityTreeNode()
|
||||||
|
err := rt.stageTreeNode(blockHash, newTreeNode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ghostdagData, err := rt.ghostdagDataStore.Get(rt.databaseContext, blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the genesis node, simply initialize it and return
|
||||||
|
if ghostdagData.SelectedParent == nil {
|
||||||
|
rt.stageReindexRoot(blockHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reindexRoot, err := rt.reindexRoot()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the node into the selected parent's reachability tree
|
||||||
|
err = rt.addChild(ghostdagData.SelectedParent, blockHash, reindexRoot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the block to the futureCoveringSets of all the blocks
|
||||||
|
// in the merget set
|
||||||
|
mergeSet := make([]*externalapi.DomainHash, len(ghostdagData.MergeSetBlues)+len(ghostdagData.MergeSetReds))
|
||||||
|
copy(mergeSet, ghostdagData.MergeSetBlues)
|
||||||
|
copy(mergeSet[len(ghostdagData.MergeSetBlues):], ghostdagData.MergeSetReds)
|
||||||
|
|
||||||
|
for _, current := range mergeSet {
|
||||||
|
err = rt.insertToFutureCoveringSet(current, blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
66
domain/consensus/processes/reachabilitymanager/stage.go
Normal file
66
domain/consensus/processes/reachabilitymanager/stage.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package reachabilitymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) stageData(blockHash *externalapi.DomainHash, data *model.ReachabilityData) {
|
||||||
|
rt.reachabilityDataStore.StageReachabilityData(blockHash, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) stageFutureCoveringSet(blockHash *externalapi.DomainHash, set model.FutureCoveringTreeNodeSet) error {
|
||||||
|
data, err := rt.data(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data.FutureCoveringSet = set
|
||||||
|
rt.reachabilityDataStore.StageReachabilityData(blockHash, data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) stageTreeNode(blockHash *externalapi.DomainHash, node *model.ReachabilityTreeNode) error {
|
||||||
|
data, err := rt.data(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data.TreeNode = node
|
||||||
|
rt.reachabilityDataStore.StageReachabilityData(blockHash, data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) stageReindexRoot(blockHash *externalapi.DomainHash) {
|
||||||
|
rt.reachabilityDataStore.StageReachabilityReindexRoot(blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) addChildAndStage(node, child *externalapi.DomainHash) error {
|
||||||
|
nodeData, err := rt.data(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeData.TreeNode.Children = append(nodeData.TreeNode.Children, child)
|
||||||
|
return rt.stageTreeNode(node, nodeData.TreeNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) stageParent(node, parent *externalapi.DomainHash) error {
|
||||||
|
treeNode, err := rt.treeNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
treeNode.Parent = parent
|
||||||
|
return rt.stageTreeNode(node, treeNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *reachabilityManager) stageInterval(node *externalapi.DomainHash, interval *model.ReachabilityInterval) error {
|
||||||
|
treeNode, err := rt.treeNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
treeNode.Interval = interval
|
||||||
|
return rt.stageTreeNode(node, treeNode)
|
||||||
|
}
|
1039
domain/consensus/processes/reachabilitymanager/tree.go
Normal file
1039
domain/consensus/processes/reachabilitymanager/tree.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,41 +0,0 @@
|
|||||||
package reachabilitytree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// reachabilityTree maintains a structure that allows to answer
|
|
||||||
// reachability queries in sub-linear time
|
|
||||||
type reachabilityTree struct {
|
|
||||||
blockRelationStore model.BlockRelationStore
|
|
||||||
reachabilityDataStore model.ReachabilityDataStore
|
|
||||||
}
|
|
||||||
|
|
||||||
// New instantiates a new ReachabilityTree
|
|
||||||
func New(
|
|
||||||
blockRelationStore model.BlockRelationStore,
|
|
||||||
reachabilityDataStore model.ReachabilityDataStore) model.ReachabilityTree {
|
|
||||||
return &reachabilityTree{
|
|
||||||
blockRelationStore: blockRelationStore,
|
|
||||||
reachabilityDataStore: reachabilityDataStore,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddBlock adds the block with the given blockHash into the reachability tree.
|
|
||||||
func (rt *reachabilityTree) AddBlock(blockHash *externalapi.DomainHash) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReachabilityTreeAncestorOf returns true if blockHashA is an
|
|
||||||
// ancestor of blockHashB in the reachability tree. Note that this
|
|
||||||
// does not necessarily mean that it isn't its ancestor in the DAG.
|
|
||||||
func (rt *reachabilityTree) IsReachabilityTreeAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDAGAncestorOf returns true if blockHashA is an ancestor of
|
|
||||||
// blockHashB in the DAG.
|
|
||||||
func (rt *reachabilityTree) IsDAGAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
@ -54,6 +54,7 @@ var (
|
|||||||
snvrLog = BackendLog.Logger("SNVR")
|
snvrLog = BackendLog.Logger("SNVR")
|
||||||
ibdsLog = BackendLog.Logger("IBDS")
|
ibdsLog = BackendLog.Logger("IBDS")
|
||||||
wsvcLog = BackendLog.Logger("WSVC")
|
wsvcLog = BackendLog.Logger("WSVC")
|
||||||
|
reacLog = BackendLog.Logger("REAC")
|
||||||
)
|
)
|
||||||
|
|
||||||
// SubsystemTags is an enum of all sub system tags
|
// SubsystemTags is an enum of all sub system tags
|
||||||
@ -85,7 +86,8 @@ var SubsystemTags = struct {
|
|||||||
DNSS,
|
DNSS,
|
||||||
SNVR,
|
SNVR,
|
||||||
IBDS,
|
IBDS,
|
||||||
WSVC string
|
WSVC,
|
||||||
|
REAC string
|
||||||
}{
|
}{
|
||||||
ADXR: "ADXR",
|
ADXR: "ADXR",
|
||||||
AMGR: "AMGR",
|
AMGR: "AMGR",
|
||||||
@ -115,6 +117,7 @@ var SubsystemTags = struct {
|
|||||||
SNVR: "SNVR",
|
SNVR: "SNVR",
|
||||||
IBDS: "IBDS",
|
IBDS: "IBDS",
|
||||||
WSVC: "WSVC",
|
WSVC: "WSVC",
|
||||||
|
REAC: "REAC",
|
||||||
}
|
}
|
||||||
|
|
||||||
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
||||||
@ -147,6 +150,7 @@ var subsystemLoggers = map[string]*Logger{
|
|||||||
SubsystemTags.SNVR: snvrLog,
|
SubsystemTags.SNVR: snvrLog,
|
||||||
SubsystemTags.IBDS: ibdsLog,
|
SubsystemTags.IBDS: ibdsLog,
|
||||||
SubsystemTags.WSVC: wsvcLog,
|
SubsystemTags.WSVC: wsvcLog,
|
||||||
|
SubsystemTags.REAC: reacLog,
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitLog attaches log file and error log file to the backend log.
|
// InitLog attaches log file and error log file to the backend log.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user