Ori Newman 63c6d7443b
In blockParentBuilder.BuildParents check if a block is isInFutureOfVi… (#1993)
* In blockParentBuilder.BuildParents check if a block is isInFutureOfVirtualGenesisChildren instead of checking if it has reachability data

* Add comment
2022-03-31 02:43:07 +03:00

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
}