mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27:26 +08:00
* wip: conversion of hotstuff from flow into Q-oriented model * bulk of tests * remaining non-integration tests * add integration test, adjust log interface, small tweaks * further adjustments, restore full pacemaker shape * add component lifecycle management+supervisor * further refinements * resolve timeout hanging * mostly finalized state for consensus * bulk of engine swap out * lifecycle-ify most types * wiring nearly complete, missing needed hooks for proposals * plugged in, vetting message validation paths * global consensus, plugged in and verified * app shard now wired in too * do not decode empty keys.yml (#456) * remove obsolete engine.maxFrames config parameter (#454) * default to Info log level unless debug is enabled (#453) * respect config's "logging" section params, remove obsolete single-file logging (#452) * Trivial code cleanup aiming to reduce Go compiler warnings (#451) * simplify range traversal * simplify channel read for single select case * delete rand.Seed() deprecated in Go 1.20 and no-op as of Go 1.24 * simplify range traversal * simplify channel read for single select case * remove redundant type from array * simplify range traversal * simplify channel read for single select case * RC slate * finalize 2.1.0.5 * Update comments in StrictMonotonicCounter Fix comment formatting and clarify description. --------- Co-authored-by: Black Swan <3999712+blacks1ne@users.noreply.github.com>
395 lines
14 KiB
Go
395 lines
14 KiB
Go
package forest
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"source.quilibrium.com/quilibrium/monorepo/consensus/models"
|
|
)
|
|
|
|
// LevelledForest contains multiple trees (which is a potentially disconnected
|
|
// planar graph). Each vertex in the graph has a level and a hash. A vertex can
|
|
// only have one parent, which must have strictly smaller level. A vertex can
|
|
// have multiple children, all with strictly larger level.
|
|
// A LevelledForest provides the ability to prune all vertices up to a specific
|
|
// level. A tree whose root is below the pruning threshold might decompose into
|
|
// multiple disconnected subtrees as a result of pruning.
|
|
// By design, the LevelledForest does _not_ touch the parent information for
|
|
// vertices that are on the lowest retained level. Thereby, it is possible to
|
|
// initialize the LevelledForest with a root vertex at the lowest retained
|
|
// level, without this root needing to have a parent. Furthermore, the root
|
|
// vertex can be at level 0 and in absence of a parent still satisfy the
|
|
// condition that any parent must be of lower level (mathematical principle of
|
|
// acuous truth) without the implementation needing to worry about unsigned
|
|
// integer underflow.
|
|
//
|
|
// LevelledForest is NOT safe for concurrent use by multiple goroutines.
|
|
type LevelledForest struct {
|
|
vertices VertexSet
|
|
verticesAtLevel map[uint64]VertexList
|
|
size uint64
|
|
LowestLevel uint64
|
|
}
|
|
|
|
type VertexList []*vertexContainer
|
|
type VertexSet map[models.Identity]*vertexContainer
|
|
|
|
// vertexContainer holds information about a tree vertex. Internally, we
|
|
// distinguish between
|
|
// - FULL container: has non-nil value for vertex.
|
|
// Used for vertices, which have been added to the tree.
|
|
// - EMPTY container: has NIL value for vertex.
|
|
// Used for vertices, which have NOT been added to the tree, but are
|
|
// referenced by vertices in the tree. An empty container is converted to a
|
|
// full container when the respective vertex is added to the tree
|
|
type vertexContainer struct {
|
|
id models.Identity
|
|
level uint64
|
|
children VertexList
|
|
|
|
// the following are only set if the state is actually known
|
|
vertex Vertex
|
|
}
|
|
|
|
// NewLevelledForest initializes a LevelledForest
|
|
func NewLevelledForest(lowestLevel uint64) *LevelledForest {
|
|
return &LevelledForest{
|
|
vertices: make(VertexSet),
|
|
verticesAtLevel: make(map[uint64]VertexList),
|
|
LowestLevel: lowestLevel,
|
|
}
|
|
}
|
|
|
|
// PruneUpToLevel prunes all states UP TO but NOT INCLUDING `level`.
|
|
func (f *LevelledForest) PruneUpToLevel(level uint64) error {
|
|
if level < f.LowestLevel {
|
|
return fmt.Errorf(
|
|
"new lowest level %d cannot be smaller than previous last retained level %d",
|
|
level,
|
|
f.LowestLevel,
|
|
)
|
|
}
|
|
if len(f.vertices) == 0 {
|
|
f.LowestLevel = level
|
|
return nil
|
|
}
|
|
|
|
elementsPruned := 0
|
|
|
|
// to optimize the pruning large level-ranges, we compare:
|
|
// * the number of levels for which we have stored vertex containers:
|
|
// len(f.verticesAtLevel)
|
|
// * the number of levels that need to be pruned: level-f.LowestLevel
|
|
// We iterate over the dimension which is smaller.
|
|
if uint64(len(f.verticesAtLevel)) < level-f.LowestLevel {
|
|
for l, vertices := range f.verticesAtLevel {
|
|
if l < level {
|
|
for _, v := range vertices {
|
|
if !f.isEmptyContainer(v) {
|
|
elementsPruned++
|
|
}
|
|
delete(f.vertices, v.id)
|
|
}
|
|
delete(f.verticesAtLevel, l)
|
|
}
|
|
}
|
|
} else {
|
|
for l := f.LowestLevel; l < level; l++ {
|
|
verticesAtLevel := f.verticesAtLevel[l]
|
|
for _, v := range verticesAtLevel {
|
|
if !f.isEmptyContainer(v) {
|
|
elementsPruned++
|
|
}
|
|
delete(f.vertices, v.id)
|
|
}
|
|
delete(f.verticesAtLevel, l)
|
|
|
|
}
|
|
}
|
|
f.LowestLevel = level
|
|
f.size -= uint64(elementsPruned)
|
|
return nil
|
|
}
|
|
|
|
// HasVertex returns true iff full vertex exists.
|
|
func (f *LevelledForest) HasVertex(id models.Identity) bool {
|
|
container, exists := f.vertices[id]
|
|
return exists && !f.isEmptyContainer(container)
|
|
}
|
|
|
|
// isEmptyContainer returns true iff vertexContainer container is empty, i.e.
|
|
// full vertex itself has not been added
|
|
func (f *LevelledForest) isEmptyContainer(
|
|
vertexContainer *vertexContainer,
|
|
) bool {
|
|
return vertexContainer.vertex == nil
|
|
}
|
|
|
|
// GetVertex returns (<full vertex>, true) if the vertex with `id` and `level`
|
|
// was found (nil, false) if full vertex is unknown
|
|
func (f *LevelledForest) GetVertex(id models.Identity) (Vertex, bool) {
|
|
container, exists := f.vertices[id]
|
|
if !exists || f.isEmptyContainer(container) {
|
|
return nil, false
|
|
}
|
|
return container.vertex, true
|
|
}
|
|
|
|
// GetSize returns the total number of vertices above the lowest pruned level.
|
|
// Note this call is not concurrent-safe, caller is responsible to ensure
|
|
// concurrency safety.
|
|
func (f *LevelledForest) GetSize() uint64 {
|
|
return f.size
|
|
}
|
|
|
|
// GetChildren returns a VertexIterator to iterate over the children
|
|
// An empty VertexIterator is returned, if no vertices are known whose parent is
|
|
// `id`.
|
|
func (f *LevelledForest) GetChildren(id models.Identity) VertexIterator {
|
|
// if vertex does not exist, container will be nil
|
|
if container, ok := f.vertices[id]; ok {
|
|
return newVertexIterator(container.children)
|
|
}
|
|
return newVertexIterator(nil) // VertexIterator gracefully handles nil slices
|
|
}
|
|
|
|
// GetNumberOfChildren returns number of children of given vertex
|
|
func (f *LevelledForest) GetNumberOfChildren(id models.Identity) int {
|
|
// if vertex does not exist, container is the default zero value for
|
|
// vertexContainer, which contains a nil-slice for its children
|
|
container := f.vertices[id]
|
|
num := 0
|
|
for _, child := range container.children {
|
|
if child.vertex != nil {
|
|
num++
|
|
}
|
|
}
|
|
return num
|
|
}
|
|
|
|
// GetVerticesAtLevel returns a VertexIterator to iterate over the Vertices at
|
|
// the specified level. An empty VertexIterator is returned, if no vertices are
|
|
// known at the specified level. If `level` is already pruned, an empty
|
|
// VertexIterator is returned.
|
|
func (f *LevelledForest) GetVerticesAtLevel(level uint64) VertexIterator {
|
|
return newVertexIterator(f.verticesAtLevel[level])
|
|
}
|
|
|
|
// GetNumberOfVerticesAtLevel returns the number of full vertices at given
|
|
// level. A full vertex is a vertex that was explicitly added to the forest. In
|
|
// contrast, an empty vertex container represents a vertex that is _referenced_
|
|
// as parent by one or more full vertices, but has not been added itself to the
|
|
// forest. We only count vertices that have been explicitly added to the forest
|
|
// and not yet pruned. (In comparision, we do _not_ count vertices that are
|
|
// _referenced_ as parent by vertices, but have not been added themselves).
|
|
func (f *LevelledForest) GetNumberOfVerticesAtLevel(level uint64) int {
|
|
num := 0
|
|
for _, container := range f.verticesAtLevel[level] {
|
|
if !f.isEmptyContainer(container) {
|
|
num++
|
|
}
|
|
}
|
|
return num
|
|
}
|
|
|
|
// AddVertex adds vertex to forest if vertex is within non-pruned levels
|
|
// Handles repeated addition of same vertex (keeps first added vertex).
|
|
// If vertex is at or below pruning level: method is NoOp.
|
|
// UNVALIDATED:
|
|
// requires that vertex would pass validity check LevelledForest.VerifyVertex(vertex).
|
|
func (f *LevelledForest) AddVertex(vertex Vertex) {
|
|
if vertex.Level() < f.LowestLevel {
|
|
return
|
|
}
|
|
container := f.getOrCreateVertexContainer(vertex.VertexID(), vertex.Level())
|
|
if !f.isEmptyContainer(container) { // the vertex was already stored
|
|
return
|
|
}
|
|
// container is empty, i.e. full vertex is new and should be stored in container
|
|
container.vertex = vertex // add vertex to container
|
|
f.registerWithParent(container)
|
|
f.size += 1
|
|
}
|
|
|
|
// registerWithParent retrieves the parent and registers the given vertex as a
|
|
// child. For a state, whose level equal to the pruning threshold, we do not
|
|
// inspect the parent at all. Thereby, this implementation can gracefully handle
|
|
// the corner case where the tree has a defined end vertex (distinct root). This
|
|
// is commonly the case in statechain (genesis, or spork root state).
|
|
// Mathematically, this means that this library can also represent bounded
|
|
// trees.
|
|
func (f *LevelledForest) registerWithParent(vertexContainer *vertexContainer) {
|
|
// caution, necessary for handling bounded trees:
|
|
// For root vertex (genesis state) the rank is _exactly_ at LowestLevel. For
|
|
// these states, a parent does not exist. In the implementation, we
|
|
// deliberately do not call the `Parent()` method, as its output is
|
|
// conceptually undefined. Thereby, we can gracefully handle the corner case
|
|
// of
|
|
// vertex.level = vertex.Parent().Level = LowestLevel = 0
|
|
if vertexContainer.level <= f.LowestLevel { // check (a)
|
|
return
|
|
}
|
|
|
|
_, parentRank := vertexContainer.vertex.Parent()
|
|
if parentRank < f.LowestLevel {
|
|
return
|
|
}
|
|
parentContainer := f.getOrCreateVertexContainer(
|
|
vertexContainer.vertex.Parent(),
|
|
)
|
|
parentContainer.children = append(parentContainer.children, vertexContainer)
|
|
}
|
|
|
|
// getOrCreateVertexContainer returns the vertexContainer if there exists one
|
|
// or creates a new vertexContainer and adds it to the internal data structures.
|
|
// (i.e. there exists an empty or full container with the same id but different
|
|
// level).
|
|
func (f *LevelledForest) getOrCreateVertexContainer(
|
|
id models.Identity,
|
|
level uint64,
|
|
) *vertexContainer {
|
|
container, exists := f.vertices[id]
|
|
if !exists {
|
|
container = &vertexContainer{
|
|
id: id,
|
|
level: level,
|
|
}
|
|
f.vertices[container.id] = container
|
|
vertices := f.verticesAtLevel[container.level]
|
|
f.verticesAtLevel[container.level] = append(vertices, container)
|
|
}
|
|
return container
|
|
}
|
|
|
|
// VerifyVertex verifies that adding vertex `v` would yield a valid Levelled
|
|
// Forest. Specifically, we verify that _all_ of the following conditions are
|
|
// satisfied:
|
|
//
|
|
// 1. `v.Level()` must be strictly larger than the level that `v` reports
|
|
// for its parent (maintains an acyclic graph).
|
|
//
|
|
// 2. If a vertex with the same ID as `v.VertexID()` exists in the graph or is
|
|
// referenced by another vertex within the graph, the level must be
|
|
// identical. (In other words, we don't have vertices with the same ID but
|
|
// different level)
|
|
//
|
|
// 3. Let `ParentLevel`, `ParentID` denote the level, ID that `v` reports for
|
|
// its parent. If a vertex with `ParentID` exists (or is referenced by other
|
|
// vertices as their parent), we require that the respective level is
|
|
// identical to `ParentLevel`.
|
|
//
|
|
// Notes:
|
|
// - If `v.Level()` has already been pruned, adding it to the forest is a
|
|
// NoOp. Hence, any vertex with level below the pruning threshold
|
|
// automatically passes.
|
|
// - By design, the LevelledForest does _not_ touch the parent information for
|
|
// vertices that are on the lowest retained level. Thereby, it is possible
|
|
// to initialize the LevelledForest with a root vertex at the lowest
|
|
// retained level, without this root needing to have a parent. Furthermore,
|
|
// the root vertex can be at level 0 and in absence of a parent still
|
|
// satisfy the condition that any parent must be of lower level
|
|
// (mathematical principle of vacuous truth) without the implementation
|
|
// needing to worry about unsigned integer underflow.
|
|
//
|
|
// Error returns:
|
|
// - InvalidVertexError if the input vertex is invalid for insertion to the
|
|
// forest.
|
|
func (f *LevelledForest) VerifyVertex(v Vertex) error {
|
|
if v.Level() < f.LowestLevel {
|
|
return nil
|
|
}
|
|
|
|
storedContainer, haveVertexContainer := f.vertices[v.VertexID()]
|
|
if !haveVertexContainer { // have no vertex with same id stored
|
|
// the only thing remaining to check is the parent information
|
|
return f.ensureConsistentParent(v)
|
|
}
|
|
|
|
// Found a vertex container, i.e. `v` already exists, or it is referenced by
|
|
// some other vertex. In all cases, `v.Level()` should match the
|
|
// vertexContainer's information
|
|
if v.Level() != storedContainer.level {
|
|
return NewInvalidVertexErrorf(
|
|
v,
|
|
"level conflicts with stored vertex with same id (%d!=%d)",
|
|
v.Level(),
|
|
storedContainer.level,
|
|
)
|
|
}
|
|
|
|
// vertex container is empty, i.e. `v` is referenced by some other vertex as
|
|
// its parent:
|
|
if f.isEmptyContainer(storedContainer) {
|
|
// the only thing remaining to check is the parent information
|
|
return f.ensureConsistentParent(v)
|
|
}
|
|
|
|
// vertex container holds a vertex with the same ID as `v`:
|
|
// The parent information from vertexContainer has already been checked for
|
|
// consistency. So we simply compare with the existing vertex for
|
|
// inconsistencies
|
|
|
|
// the vertex is at or below the lowest retained level, so we can't check the
|
|
// parent (it's pruned)
|
|
if v.Level() == f.LowestLevel {
|
|
return nil
|
|
}
|
|
|
|
newParentId, newParentLevel := v.Parent()
|
|
storedParentId, storedParentLevel := storedContainer.vertex.Parent()
|
|
if newParentId != storedParentId {
|
|
return NewInvalidVertexErrorf(
|
|
v,
|
|
"parent ID conflicts with stored parent (%x!=%x)",
|
|
newParentId,
|
|
storedParentId,
|
|
)
|
|
}
|
|
if newParentLevel != storedParentLevel {
|
|
return NewInvalidVertexErrorf(
|
|
v,
|
|
"parent level conflicts with stored parent (%d!=%d)",
|
|
newParentLevel,
|
|
storedParentLevel,
|
|
)
|
|
}
|
|
// all _relevant_ fields identical
|
|
return nil
|
|
}
|
|
|
|
// ensureConsistentParent verifies that vertex.Parent() is consistent with
|
|
// current forest.
|
|
// Returns InvalidVertexError if:
|
|
// * there is a parent with the same ID but different level;
|
|
// * the parent's level is _not_ smaller than the vertex's level
|
|
func (f *LevelledForest) ensureConsistentParent(vertex Vertex) error {
|
|
if vertex.Level() <= f.LowestLevel {
|
|
// the vertex is at or below the lowest retained level, so we can't check
|
|
// the parent (it's pruned)
|
|
return nil
|
|
}
|
|
|
|
// verify parent
|
|
parentID, parentLevel := vertex.Parent()
|
|
if !(vertex.Level() > parentLevel) {
|
|
return NewInvalidVertexErrorf(
|
|
vertex,
|
|
"vertex parent level (%d) must be smaller than proposed vertex level (%d)",
|
|
parentLevel,
|
|
vertex.Level(),
|
|
)
|
|
}
|
|
storedParent, haveParentStored := f.GetVertex(parentID)
|
|
if !haveParentStored {
|
|
return nil
|
|
}
|
|
if storedParent.Level() != parentLevel {
|
|
return NewInvalidVertexErrorf(
|
|
vertex,
|
|
"parent level conflicts with stored parent (%d!=%d)",
|
|
parentLevel,
|
|
storedParent.Level(),
|
|
)
|
|
}
|
|
return nil
|
|
}
|