mirror of
https://github.com/luxfi/session.git
synced 2026-10-30 15:17:14 +00:00
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers; omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
154 lines
3.9 KiB
Go
154 lines
3.9 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package swarm implements epoch-based swarm assignment for session workloads.
|
|
// Swarms are dynamically formed committees of nodes assigned to execute sessions.
|
|
package swarm
|
|
|
|
import (
|
|
"errors"
|
|
"sort"
|
|
|
|
"github.com/luxfi/session/core"
|
|
)
|
|
|
|
// Config holds swarm assignment configuration.
|
|
type Config struct {
|
|
// MinCommitteeSize is the minimum number of nodes in a committee
|
|
MinCommitteeSize int
|
|
|
|
// MaxCommitteeSize is the maximum number of nodes in a committee
|
|
MaxCommitteeSize int
|
|
|
|
// QuorumThreshold is the fraction of committee required for quorum (e.g., 0.67)
|
|
QuorumThreshold float64
|
|
}
|
|
|
|
// DefaultConfig returns the default swarm configuration.
|
|
func DefaultConfig() *Config {
|
|
return &Config{
|
|
MinCommitteeSize: 3,
|
|
MaxCommitteeSize: 21,
|
|
QuorumThreshold: 0.67,
|
|
}
|
|
}
|
|
|
|
// Assigner handles epoch-based swarm assignment.
|
|
type Assigner struct {
|
|
config *Config
|
|
}
|
|
|
|
// NewAssigner creates a new swarm assigner.
|
|
func NewAssigner(config *Config) *Assigner {
|
|
if config == nil {
|
|
config = DefaultConfig()
|
|
}
|
|
return &Assigner{config: config}
|
|
}
|
|
|
|
// AssignCommittee assigns a committee for a session based on epoch randomness.
|
|
// Uses deterministic selection based on epoch beacon and service ID.
|
|
func (a *Assigner) AssignCommittee(
|
|
serviceID core.ID,
|
|
epoch uint64,
|
|
epochRandomness core.ID,
|
|
eligibleNodes []core.ID,
|
|
) ([]core.ID, error) {
|
|
if len(eligibleNodes) < a.config.MinCommitteeSize {
|
|
return nil, errors.New("not enough eligible nodes")
|
|
}
|
|
|
|
// Compute selection seed: H(epoch_randomness || service_id || epoch)
|
|
seed := core.HashMulti(
|
|
epochRandomness[:],
|
|
serviceID[:],
|
|
core.Uint64ToBytes(epoch),
|
|
)
|
|
|
|
// Sort nodes deterministically by score
|
|
type scoredNode struct {
|
|
id core.ID
|
|
score core.ID
|
|
}
|
|
|
|
scored := make([]scoredNode, len(eligibleNodes))
|
|
for i, nodeID := range eligibleNodes {
|
|
// Score = H(seed || node_id)
|
|
scored[i] = scoredNode{
|
|
id: nodeID,
|
|
score: core.HashMulti(seed[:], nodeID[:]),
|
|
}
|
|
}
|
|
|
|
// Sort by score (lexicographic comparison of 32-byte hashes)
|
|
sort.Slice(scored, func(i, j int) bool {
|
|
for k := range 32 {
|
|
if scored[i].score[k] < scored[j].score[k] {
|
|
return true
|
|
}
|
|
if scored[i].score[k] > scored[j].score[k] {
|
|
return false
|
|
}
|
|
}
|
|
return false
|
|
})
|
|
|
|
// Select top N nodes
|
|
committeeSize := min(a.config.MaxCommitteeSize, len(scored))
|
|
|
|
committee := make([]core.ID, committeeSize)
|
|
for i := range committeeSize {
|
|
committee[i] = scored[i].id
|
|
}
|
|
|
|
return committee, nil
|
|
}
|
|
|
|
// QuorumSize returns the number of nodes required for quorum.
|
|
func (a *Assigner) QuorumSize(committeeSize int) int {
|
|
quorum := int(float64(committeeSize)*a.config.QuorumThreshold) + 1
|
|
if quorum > committeeSize {
|
|
return committeeSize
|
|
}
|
|
return quorum
|
|
}
|
|
|
|
// IsQuorum checks if the given number of participants meets quorum.
|
|
func (a *Assigner) IsQuorum(participants, committeeSize int) bool {
|
|
return participants >= a.QuorumSize(committeeSize)
|
|
}
|
|
|
|
// EpochBeacon represents epoch randomness for swarm assignment.
|
|
type EpochBeacon struct {
|
|
// Epoch number
|
|
Epoch uint64 `json:"epoch"`
|
|
|
|
// Randomness is the VRF output for this epoch
|
|
Randomness core.ID `json:"randomness"`
|
|
|
|
// PreviousBeacon is the hash of the previous epoch's beacon
|
|
PreviousBeacon core.ID `json:"previousBeacon"`
|
|
|
|
// AttestationID is the threshold attestation over this beacon
|
|
AttestationID core.ID `json:"attestationId"`
|
|
}
|
|
|
|
// ComputeBeaconID computes a deterministic beacon ID.
|
|
func ComputeBeaconID(epoch uint64, randomness, previousBeacon core.ID) core.ID {
|
|
return core.HashMulti(
|
|
[]byte("LUX:EpochBeacon:v1"),
|
|
core.Uint64ToBytes(epoch),
|
|
randomness[:],
|
|
previousBeacon[:],
|
|
)
|
|
}
|
|
|
|
// NewEpochBeacon creates a new epoch beacon.
|
|
func NewEpochBeacon(epoch uint64, randomness, previousBeacon core.ID) *EpochBeacon {
|
|
return &EpochBeacon{
|
|
Epoch: epoch,
|
|
Randomness: randomness,
|
|
PreviousBeacon: previousBeacon,
|
|
}
|
|
}
|