mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
216 lines
5.0 KiB
Go
216 lines
5.0 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package horizon
|
|
|
|
import (
|
|
"github.com/luxfi/consensus/core/dag"
|
|
)
|
|
|
|
type VertexID [32]byte
|
|
|
|
type Meta interface {
|
|
ID() VertexID
|
|
Author() string
|
|
Round() uint64
|
|
Parents() []VertexID
|
|
}
|
|
|
|
type View interface {
|
|
Get(VertexID) (Meta, bool)
|
|
ByRound(round uint64) []Meta
|
|
Supports(from VertexID, author string, round uint64) bool
|
|
}
|
|
|
|
type Params struct{ N, F int }
|
|
|
|
// TransitiveClosure computes all ancestors of a vertex using BFS traversal.
|
|
// Returns the vertex itself plus all vertices reachable by following parent edges.
|
|
func TransitiveClosure[V comparable](store dag.Store[V], vertex V) []V {
|
|
visited := make(map[any]bool)
|
|
var result []V
|
|
queue := []V{vertex}
|
|
|
|
for len(queue) > 0 {
|
|
current := queue[0]
|
|
queue = queue[1:]
|
|
|
|
// Use interface conversion for map key
|
|
key := any(current)
|
|
if visited[key] {
|
|
continue
|
|
}
|
|
visited[key] = true
|
|
result = append(result, current)
|
|
|
|
// Add all parents to queue
|
|
if block, exists := store.Get(current); exists {
|
|
for _, parent := range block.Parents() {
|
|
parentKey := any(parent)
|
|
if !visited[parentKey] {
|
|
queue = append(queue, parent)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Certificate represents a proof that a vertex has achieved consensus.
|
|
type Certificate[V comparable] struct {
|
|
Vertex V
|
|
Proof []V // Vertices that contributed to the certificate
|
|
Threshold int // Minimum valid proofs required
|
|
}
|
|
|
|
// ValidateCertificate checks if a certificate meets the threshold with valid proofs.
|
|
func ValidateCertificate[V comparable](store dag.Store[V], cert Certificate[V], isValid func(V) bool) bool {
|
|
validCount := 0
|
|
for _, proof := range cert.Proof {
|
|
if isValid(proof) {
|
|
validCount++
|
|
}
|
|
}
|
|
return validCount >= cert.Threshold
|
|
}
|
|
|
|
// SkipList provides O(log n) traversal through DAG levels.
|
|
// Each vertex has pointers to ancestors at exponentially increasing distances.
|
|
type SkipList[V comparable] struct {
|
|
Levels map[any][]V // key is vertex, value is skip pointers
|
|
}
|
|
|
|
// BuildSkipList constructs a skip list with logarithmic skip pointers.
|
|
// Level 0: immediate parent, Level 1: 2 steps back, Level 2: 4 steps back, etc.
|
|
func BuildSkipList[V comparable](store dag.Store[V], vertices []V) *SkipList[V] {
|
|
sl := &SkipList[V]{
|
|
Levels: make(map[any][]V),
|
|
}
|
|
|
|
// Build skip pointers for each vertex
|
|
for _, v := range vertices {
|
|
block, exists := store.Get(v)
|
|
if !exists {
|
|
continue
|
|
}
|
|
|
|
parents := block.Parents()
|
|
if len(parents) == 0 {
|
|
sl.Levels[any(v)] = []V{}
|
|
continue
|
|
}
|
|
|
|
// Start with first parent as level 0
|
|
skips := []V{parents[0]}
|
|
|
|
// Build higher levels by following skip pointers
|
|
current := parents[0]
|
|
for level := 1; level < 8; level++ { // Max 8 levels (256 step jumps)
|
|
// Jump 2^level steps
|
|
for step := 0; step < (1 << level); step++ {
|
|
currentBlock, exists := store.Get(current)
|
|
if !exists {
|
|
break
|
|
}
|
|
currentParents := currentBlock.Parents()
|
|
if len(currentParents) == 0 {
|
|
break
|
|
}
|
|
current = currentParents[0]
|
|
}
|
|
skips = append(skips, current)
|
|
}
|
|
|
|
sl.Levels[any(v)] = skips
|
|
}
|
|
|
|
return sl
|
|
}
|
|
|
|
// FindPath finds a path from 'from' to 'to' using BFS.
|
|
// Returns the path as a slice of vertices and true if found, nil and false otherwise.
|
|
func FindPath[V comparable](store dag.Store[V], from, to V) ([]V, bool) {
|
|
// BFS to find path
|
|
type node struct {
|
|
vertex V
|
|
path []V
|
|
}
|
|
|
|
visited := make(map[any]bool)
|
|
queue := []node{{vertex: from, path: []V{from}}}
|
|
|
|
toKey := any(to)
|
|
|
|
for len(queue) > 0 {
|
|
current := queue[0]
|
|
queue = queue[1:]
|
|
|
|
currentKey := any(current.vertex)
|
|
if visited[currentKey] {
|
|
continue
|
|
}
|
|
visited[currentKey] = true
|
|
|
|
// Check if we've reached the target
|
|
if currentKey == toKey {
|
|
return current.path, true
|
|
}
|
|
|
|
// Add parents to queue
|
|
if block, exists := store.Get(current.vertex); exists {
|
|
for _, parent := range block.Parents() {
|
|
parentKey := any(parent)
|
|
if !visited[parentKey] {
|
|
newPath := make([]V, len(current.path)+1)
|
|
copy(newPath, current.path)
|
|
newPath[len(current.path)] = parent
|
|
queue = append(queue, node{vertex: parent, path: newPath})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// LowestCommonAncestor finds the LCA of two vertices in the DAG.
|
|
func LowestCommonAncestor[V comparable](store dag.Store[V], a, b V) (V, bool) {
|
|
// Get ancestors of a
|
|
ancestorsA := make(map[any]bool)
|
|
for _, v := range TransitiveClosure(store, a) {
|
|
ancestorsA[any(v)] = true
|
|
}
|
|
|
|
// Find first ancestor of b that is also an ancestor of a (BFS order = closest)
|
|
queue := []V{b}
|
|
visited := make(map[any]bool)
|
|
|
|
for len(queue) > 0 {
|
|
current := queue[0]
|
|
queue = queue[1:]
|
|
|
|
currentKey := any(current)
|
|
if visited[currentKey] {
|
|
continue
|
|
}
|
|
visited[currentKey] = true
|
|
|
|
if ancestorsA[currentKey] {
|
|
return current, true
|
|
}
|
|
|
|
if block, exists := store.Get(current); exists {
|
|
for _, parent := range block.Parents() {
|
|
parentKey := any(parent)
|
|
if !visited[parentKey] {
|
|
queue = append(queue, parent)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var zero V
|
|
return zero, false
|
|
}
|