mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-03 04:36:51 +00:00

* In blockParentBuilder.BuildParents check if a block is isInFutureOfVirtualGenesisChildren instead of checking if it has reachability data * Add comment
245 lines
9.3 KiB
Go
245 lines
9.3 KiB
Go
package blockparentbuilder
|
|
|
|
import (
|
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/hashset"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type blockParentBuilder struct {
|
|
databaseContext model.DBManager
|
|
blockHeaderStore model.BlockHeaderStore
|
|
dagTopologyManager model.DAGTopologyManager
|
|
parentsManager model.ParentsManager
|
|
reachabilityDataStore model.ReachabilityDataStore
|
|
pruningStore model.PruningStore
|
|
|
|
genesisHash *externalapi.DomainHash
|
|
maxBlockLevel int
|
|
}
|
|
|
|
// New creates a new instance of a BlockParentBuilder
|
|
func New(
|
|
databaseContext model.DBManager,
|
|
blockHeaderStore model.BlockHeaderStore,
|
|
dagTopologyManager model.DAGTopologyManager,
|
|
parentsManager model.ParentsManager,
|
|
|
|
reachabilityDataStore model.ReachabilityDataStore,
|
|
pruningStore model.PruningStore,
|
|
|
|
genesisHash *externalapi.DomainHash,
|
|
maxBlockLevel int,
|
|
) model.BlockParentBuilder {
|
|
return &blockParentBuilder{
|
|
databaseContext: databaseContext,
|
|
blockHeaderStore: blockHeaderStore,
|
|
dagTopologyManager: dagTopologyManager,
|
|
parentsManager: parentsManager,
|
|
|
|
reachabilityDataStore: reachabilityDataStore,
|
|
pruningStore: pruningStore,
|
|
genesisHash: genesisHash,
|
|
maxBlockLevel: maxBlockLevel,
|
|
}
|
|
}
|
|
|
|
func (bpb *blockParentBuilder) BuildParents(stagingArea *model.StagingArea,
|
|
daaScore uint64, directParentHashes []*externalapi.DomainHash) ([]externalapi.BlockLevelParents, error) {
|
|
|
|
// Late on we'll mutate direct parent hashes, so we first clone it.
|
|
directParentHashesCopy := make([]*externalapi.DomainHash, len(directParentHashes))
|
|
copy(directParentHashesCopy, directParentHashes)
|
|
|
|
pruningPoint, err := bpb.pruningStore.PruningPoint(bpb.databaseContext, stagingArea)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The first candidates to be added should be from a parent in the future of the pruning
|
|
// point, so later on we'll know that every block that doesn't have reachability data
|
|
// (i.e. pruned) is necessarily in the past of the current candidates and cannot be
|
|
// considered as a valid candidate.
|
|
// This is why we sort the direct parent headers in a way that the first one will be
|
|
// in the future of the pruning point.
|
|
directParentHeaders := make([]externalapi.BlockHeader, len(directParentHashesCopy))
|
|
firstParentInFutureOfPruningPointIndex := 0
|
|
foundFirstParentInFutureOfPruningPoint := false
|
|
for i, directParentHash := range directParentHashesCopy {
|
|
isInFutureOfPruningPoint, err := bpb.dagTopologyManager.IsAncestorOf(stagingArea, pruningPoint, directParentHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !isInFutureOfPruningPoint {
|
|
continue
|
|
}
|
|
|
|
firstParentInFutureOfPruningPointIndex = i
|
|
foundFirstParentInFutureOfPruningPoint = true
|
|
break
|
|
}
|
|
|
|
if !foundFirstParentInFutureOfPruningPoint {
|
|
return nil, errors.New("BuildParents should get at least one parent in the future of the pruning point")
|
|
}
|
|
|
|
oldFirstDirectParent := directParentHashesCopy[0]
|
|
directParentHashesCopy[0] = directParentHashesCopy[firstParentInFutureOfPruningPointIndex]
|
|
directParentHashesCopy[firstParentInFutureOfPruningPointIndex] = oldFirstDirectParent
|
|
|
|
for i, directParentHash := range directParentHashesCopy {
|
|
directParentHeader, err := bpb.blockHeaderStore.BlockHeader(bpb.databaseContext, stagingArea, directParentHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
directParentHeaders[i] = directParentHeader
|
|
}
|
|
|
|
type blockToReferences map[externalapi.DomainHash][]*externalapi.DomainHash
|
|
candidatesByLevelToReferenceBlocksMap := make(map[int]blockToReferences)
|
|
|
|
// Direct parents are guaranteed to be in one other's anticones so add them all to
|
|
// all the block levels they occupy
|
|
for _, directParentHeader := range directParentHeaders {
|
|
directParentHash := consensushashing.HeaderHash(directParentHeader)
|
|
blockLevel := directParentHeader.BlockLevel(bpb.maxBlockLevel)
|
|
for i := 0; i <= blockLevel; i++ {
|
|
if _, exists := candidatesByLevelToReferenceBlocksMap[i]; !exists {
|
|
candidatesByLevelToReferenceBlocksMap[i] = make(map[externalapi.DomainHash][]*externalapi.DomainHash)
|
|
}
|
|
candidatesByLevelToReferenceBlocksMap[i][*directParentHash] = []*externalapi.DomainHash{directParentHash}
|
|
}
|
|
}
|
|
|
|
virtualGenesisChildren, err := bpb.dagTopologyManager.Children(stagingArea, model.VirtualGenesisBlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
virtualGenesisChildrenHeaders := make(map[externalapi.DomainHash]externalapi.BlockHeader, len(virtualGenesisChildren))
|
|
for _, child := range virtualGenesisChildren {
|
|
virtualGenesisChildrenHeaders[*child], err = bpb.blockHeaderStore.BlockHeader(bpb.databaseContext, stagingArea, child)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
for _, directParentHeader := range directParentHeaders {
|
|
for blockLevel, blockLevelParentsInHeader := range bpb.parentsManager.Parents(directParentHeader) {
|
|
isEmptyLevel := false
|
|
if _, exists := candidatesByLevelToReferenceBlocksMap[blockLevel]; !exists {
|
|
candidatesByLevelToReferenceBlocksMap[blockLevel] = make(map[externalapi.DomainHash][]*externalapi.DomainHash)
|
|
isEmptyLevel = true
|
|
}
|
|
|
|
for _, parent := range blockLevelParentsInHeader {
|
|
isInFutureOfVirtualGenesisChildren := false
|
|
hasReachabilityData, err := bpb.reachabilityDataStore.HasReachabilityData(bpb.databaseContext, stagingArea, parent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hasReachabilityData {
|
|
// If a block is in the future of one of the virtual genesis children it means we have the full DAG between the current block
|
|
// and this parent, so there's no need for any indirect reference blocks, and normal reachability queries can be used.
|
|
isInFutureOfVirtualGenesisChildren, err = bpb.dagTopologyManager.IsAnyAncestorOf(stagingArea, virtualGenesisChildren, parent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Reference blocks are the blocks that are used in reachability queries to check if
|
|
// a candidate is in the future of another candidate. In most cases this is just the
|
|
// block itself, but in the case where a block doesn't have reachability data we need
|
|
// to use some blocks in its future as reference instead.
|
|
// If we make sure to add a parent in the future of the pruning point first, we can
|
|
// know that any pruned candidate that is in the past of some blocks in the pruning
|
|
// point anticone should have should be a parent (in the relevant level) of one of
|
|
// the virtual genesis children in the pruning point anticone. So we can check which
|
|
// virtual genesis children have this block as parent and use those block as
|
|
// reference blocks.
|
|
var referenceBlocks []*externalapi.DomainHash
|
|
if isInFutureOfVirtualGenesisChildren {
|
|
referenceBlocks = []*externalapi.DomainHash{parent}
|
|
} else {
|
|
for childHash, childHeader := range virtualGenesisChildrenHeaders {
|
|
childHash := childHash // Assign to a new pointer to avoid `range` pointer reuse
|
|
if bpb.parentsManager.ParentsAtLevel(childHeader, blockLevel).Contains(parent) {
|
|
referenceBlocks = append(referenceBlocks, &childHash)
|
|
}
|
|
}
|
|
}
|
|
|
|
if isEmptyLevel {
|
|
candidatesByLevelToReferenceBlocksMap[blockLevel][*parent] = referenceBlocks
|
|
continue
|
|
}
|
|
|
|
if !isInFutureOfVirtualGenesisChildren {
|
|
continue
|
|
}
|
|
|
|
toRemove := hashset.New()
|
|
isAncestorOfAnyCandidate := false
|
|
for candidate, candidateReferences := range candidatesByLevelToReferenceBlocksMap[blockLevel] {
|
|
candidate := candidate // Assign to a new pointer to avoid `range` pointer reuse
|
|
isInFutureOfCurrentCandidate, err := bpb.dagTopologyManager.IsAnyAncestorOf(stagingArea, candidateReferences, parent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if isInFutureOfCurrentCandidate {
|
|
toRemove.Add(&candidate)
|
|
continue
|
|
}
|
|
|
|
if isAncestorOfAnyCandidate {
|
|
continue
|
|
}
|
|
|
|
isAncestorOfCurrentCandidate, err := bpb.dagTopologyManager.IsAncestorOfAny(stagingArea, parent, candidateReferences)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if isAncestorOfCurrentCandidate {
|
|
isAncestorOfAnyCandidate = true
|
|
}
|
|
}
|
|
|
|
if toRemove.Length() > 0 {
|
|
for hash := range toRemove {
|
|
delete(candidatesByLevelToReferenceBlocksMap[blockLevel], hash)
|
|
}
|
|
}
|
|
|
|
// We should add the block as a candidate if it's in the future of another candidate
|
|
// or in the anticone of all candidates.
|
|
if !isAncestorOfAnyCandidate || toRemove.Length() > 0 {
|
|
candidatesByLevelToReferenceBlocksMap[blockLevel][*parent] = referenceBlocks
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
parents := make([]externalapi.BlockLevelParents, 0, len(candidatesByLevelToReferenceBlocksMap))
|
|
for blockLevel := 0; blockLevel < len(candidatesByLevelToReferenceBlocksMap); blockLevel++ {
|
|
if blockLevel > 0 {
|
|
if _, ok := candidatesByLevelToReferenceBlocksMap[blockLevel][*bpb.genesisHash]; ok && len(candidatesByLevelToReferenceBlocksMap[blockLevel]) == 1 {
|
|
break
|
|
}
|
|
}
|
|
|
|
levelBlocks := make(externalapi.BlockLevelParents, 0, len(candidatesByLevelToReferenceBlocksMap[blockLevel]))
|
|
for block := range candidatesByLevelToReferenceBlocksMap[blockLevel] {
|
|
block := block // Assign to a new pointer to avoid `range` pointer reuse
|
|
levelBlocks = append(levelBlocks, &block)
|
|
}
|
|
|
|
parents = append(parents, levelBlocks)
|
|
}
|
|
return parents, nil
|
|
}
|