mirror of
https://github.com/luxfi/proto.git
synced 2026-10-30 15:21:09 +00:00
Adds .hanzo/workflows/cicd.yml calling the canonical pipeline and drops the GitHub-hosted workflows it replaces. Lands here because this is where the repo is written — git.hanzo.ai is upstream and github.com is its push mirror, so the same change made on github.com never reaches the runner fleet.
158 lines
4.4 KiB
Go
158 lines
4.4 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package state
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/luxfi/container/iterator"
|
|
"github.com/luxfi/math"
|
|
"github.com/luxfi/proto/p/config"
|
|
"github.com/luxfi/timer/mockable"
|
|
"github.com/luxfi/vm/components/gas"
|
|
|
|
txfee "github.com/luxfi/proto/p/txs/fee"
|
|
validatorfee "github.com/luxfi/proto/p/validators/fee"
|
|
)
|
|
|
|
func NextBlockTime(
|
|
config validatorfee.Config,
|
|
state Chain,
|
|
clk *mockable.Clock,
|
|
) (time.Time, bool, error) {
|
|
var (
|
|
timestamp = clk.Time()
|
|
parentTime = state.GetTimestamp()
|
|
)
|
|
if parentTime.After(timestamp) {
|
|
timestamp = parentTime
|
|
}
|
|
// [timestamp] = max(now, parentTime)
|
|
|
|
// If the NextStakerChangeTime is after timestamp, then we shouldn't return
|
|
// that the time was capped.
|
|
nextStakerChangeTimeCap := timestamp.Add(time.Second)
|
|
nextStakerChangeTime, err := GetNextStakerChangeTime(config, state, nextStakerChangeTimeCap)
|
|
if err != nil {
|
|
return time.Time{}, false, fmt.Errorf("failed getting next staker change time: %w", err)
|
|
}
|
|
|
|
// timeWasCapped means that [timestamp] was reduced to [nextStakerChangeTime]
|
|
timeWasCapped := !timestamp.Before(nextStakerChangeTime)
|
|
if timeWasCapped {
|
|
timestamp = nextStakerChangeTime
|
|
}
|
|
// [timestamp] = min(max(now, parentTime), nextStakerChangeTime)
|
|
return timestamp, timeWasCapped, nil
|
|
}
|
|
|
|
// GetNextStakerChangeTime returns the next time a staker will be either added
|
|
// to or removed from the validator set. If the next staker change time is
|
|
// further in the future than [nextTime], then [nextTime] is returned.
|
|
func GetNextStakerChangeTime(
|
|
config validatorfee.Config,
|
|
state Chain,
|
|
nextTime time.Time,
|
|
) (time.Time, error) {
|
|
currentIterator, err := state.GetCurrentStakerIterator()
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
defer currentIterator.Release()
|
|
|
|
pendingIterator, err := state.GetPendingStakerIterator()
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
defer pendingIterator.Release()
|
|
|
|
for _, it := range []iterator.Iterator[*Staker]{currentIterator, pendingIterator} {
|
|
// If the iterator is empty, skip it
|
|
if !it.Next() {
|
|
continue
|
|
}
|
|
|
|
time := it.Value().NextTime
|
|
if time.Before(nextTime) {
|
|
nextTime = time
|
|
}
|
|
}
|
|
|
|
return getNextL1ValidatorEvictionTime(config, state, nextTime)
|
|
}
|
|
|
|
func getNextL1ValidatorEvictionTime(
|
|
config validatorfee.Config,
|
|
state Chain,
|
|
nextTime time.Time,
|
|
) (time.Time, error) {
|
|
l1ValidatorIterator, err := state.GetActiveL1ValidatorsIterator()
|
|
if err != nil {
|
|
return time.Time{}, fmt.Errorf("could not iterate over active L1 validators: %w", err)
|
|
}
|
|
defer l1ValidatorIterator.Release()
|
|
|
|
// If there are no L1 validators, return
|
|
if !l1ValidatorIterator.Next() {
|
|
return nextTime, nil
|
|
}
|
|
|
|
// Calculate the remaining funds that the next validator to evict has.
|
|
var (
|
|
// GetActiveL1ValidatorsIterator iterates in order of increasing
|
|
// EndAccumulatedFee, so the first L1 validator is the next L1 validator
|
|
// to evict.
|
|
l1Validator = l1ValidatorIterator.Value()
|
|
accruedFees = state.GetAccruedFees()
|
|
)
|
|
remainingFunds, err := math.Sub(l1Validator.EndAccumulatedFee, accruedFees)
|
|
if err != nil {
|
|
return time.Time{}, fmt.Errorf("could not calculate remaining funds: %w", err)
|
|
}
|
|
|
|
// Calculate how many seconds the remaining funds can last for.
|
|
var (
|
|
currentTime = state.GetTimestamp()
|
|
maxSeconds = uint64(nextTime.Sub(currentTime) / time.Second)
|
|
)
|
|
feeState := validatorfee.State{
|
|
Current: gas.Gas(state.NumActiveL1Validators()),
|
|
Excess: state.GetL1ValidatorExcess(),
|
|
}
|
|
remainingSeconds := feeState.SecondsRemaining(
|
|
config,
|
|
maxSeconds,
|
|
remainingFunds,
|
|
)
|
|
|
|
deactivationTime := currentTime.Add(time.Duration(remainingSeconds) * time.Second)
|
|
if deactivationTime.Before(nextTime) {
|
|
nextTime = deactivationTime
|
|
}
|
|
return nextTime, nil
|
|
}
|
|
|
|
// PickFeeCalculator creates either a simple or a dynamic fee calculator,
|
|
// depending on the active upgrade.
|
|
//
|
|
// PickFeeCalculator does not modify [state].
|
|
func PickFeeCalculator(config *config.Internal, state Chain) txfee.Calculator {
|
|
timestamp := state.GetTimestamp()
|
|
if !config.UpgradeConfig.IsQuasarActivated(timestamp) {
|
|
return txfee.NewSimpleCalculator(0)
|
|
}
|
|
|
|
feeState := state.GetFeeState()
|
|
gasPrice := gas.CalculatePrice(
|
|
config.DynamicFeeConfig.MinPrice,
|
|
feeState.Excess,
|
|
config.DynamicFeeConfig.ExcessConversionConstant,
|
|
)
|
|
return txfee.NewDynamicCalculator(
|
|
config.DynamicFeeConfig.Weights,
|
|
gasPrice,
|
|
)
|
|
}
|