ceremonyclient/node/consensus/time/fork_choice.go
Cassandra Heart dbd95bd9e9
v2.1.0 (#439)
* v2.1.0 [omit consensus and adjacent] - this commit will be amended with the full release after the file copy is complete

* 2.1.0 main node rollup
2025-09-30 02:48:15 -05:00

172 lines
4.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package time
import (
"math"
"math/big"
"github.com/iden3/go-iden3-crypto/ff"
)
const SCALE uint64 = 0xffffffffffffffff
type Frame struct {
Distance *big.Int // 256-bit distance; 0 is best
Seniority uint64 // 0 ... SCALE (0 == evicted / black-listed)
ProverAddress []byte
}
type Branch struct {
Frames []Frame
}
// Params == all tunables.
type Params struct {
RMax *big.Int // maximum distance (constant)
WrNumer uint64 // distance weight numerator
WpNumer uint64 // seniority weight numerator
WDenom uint64 // common weight denominator
AlNumer uint64 // α numerator
AlDenom uint64 // α denominator
BlendWindow int // m — last m rounds window
BetaNumer uint64 // β numerator
BetaDenom uint64 // β denominator
Epsilon uint64 // tie margin in SCALE units
}
var RMaxDenom *big.Int
var DefaultForkChoiceParams Params
func init() {
RMaxDenom = ff.Modulus()
DefaultForkChoiceParams = Params{
RMax: new(big.Int).SetUint64(math.MaxUint64),
WrNumer: 7,
WpNumer: 3,
WDenom: 10,
AlNumer: 9,
AlDenom: 10,
BlendWindow: 5,
BetaNumer: 1,
BetaDenom: 10,
Epsilon: 0,
}
}
// ForkChoice returns the index of the branch every honest player should
// extend this round. `prevChoice` is the branch you extended last round
// (pass 0 the first time).
func ForkChoice(branches []Branch, cfg Params, prevChoice int) int {
bestIdx := prevChoice
bestScore := new(big.Int)
// Calculate score for current choice first
if prevChoice < len(branches) {
bestScore = branchScore(branches[prevChoice], cfg)
}
for i, br := range branches {
score := branchScore(br, cfg)
if score.Cmp(new(big.Int).Add(
bestScore,
new(big.Int).SetUint64(cfg.Epsilon),
)) > 0 {
bestScore = score
bestIdx = i
}
}
return bestIdx
}
func branchScore(br Branch, cfg Params) *big.Int {
if len(br.Frames) == 0 {
return big.NewInt(0) // empty fork cant win
}
// 1. Exponentially-decayed raw score
raw := big.NewInt(0)
alphaNum := cfg.AlNumer
alphaDen := cfg.AlDenom
// iterate oldest to newest
for _, frame := range br.Frames {
rho := new(big.Int).SetUint64(rhoScaled(frame.Distance, cfg.RMax))
pi := new(big.Int).SetUint64(clamp(frame.Seniority, 0, SCALE))
frameScore := new(big.Int).Quo(
new(big.Int).Add(
new(big.Int).Mul(new(big.Int).SetUint64(cfg.WrNumer), rho),
new(big.Int).Mul(new(big.Int).SetUint64(cfg.WpNumer), pi),
),
new(big.Int).SetUint64(cfg.WDenom),
)
raw = new(big.Int).Add(
new(big.Int).Quo(
new(big.Int).Mul(raw, new(big.Int).SetUint64(alphaNum)),
new(big.Int).SetUint64(alphaDen),
),
frameScore,
)
}
// 2. Blend bonus
m := cfg.BlendWindow
if m > len(br.Frames) {
m = len(br.Frames)
}
if m == 0 {
return big.NewInt(0)
}
uniq := make(map[string]struct{}, m)
for i := len(br.Frames) - m; i < len(br.Frames); i++ {
if br.Frames[i].Seniority > 0 { // ignore evicted players
uniq[string(br.Frames[i].ProverAddress)] = struct{}{}
}
}
blendScaled := new(big.Int).Quo(
new(big.Int).Mul(
new(big.Int).SetUint64(uint64(len(uniq))),
new(big.Int).SetUint64(SCALE),
),
new(big.Int).SetUint64(uint64(m)),
)
divBonus := new(big.Int).Add(
new(big.Int).SetUint64(SCALE),
new(big.Int).Quo(
new(big.Int).Mul(new(big.Int).SetUint64(cfg.BetaNumer), blendScaled),
new(big.Int).SetUint64(cfg.BetaDenom),
),
)
// 3. Finalized score
return new(big.Int).Quo(
new(big.Int).Mul(raw, divBonus),
new(big.Int).SetUint64(SCALE),
)
}
// rhoScaled = ((RMax rank) * SCALE) / RMax ∈ [0,SCALE]
func rhoScaled(rank, rMax *big.Int) uint64 {
tmp := new(big.Int).Sub(rMax, rank) // RMax rank
tmp.Mul(tmp, big.NewInt(0).SetUint64(SCALE)) // * SCALE
tmp.Quo(tmp, rMax) // / RMax
if tmp.Sign() < 0 {
return 0
}
if tmp.Cmp(big.NewInt(0).SetUint64(SCALE)) > 0 {
return SCALE
}
return tmp.Uint64()
}
func clamp(x, lo, hi uint64) uint64 {
if x < lo {
return lo
}
if x > hi {
return hi
}
return x
}