mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
153 lines
4.3 KiB
Go
153 lines
4.3 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/node/vms/components/gas"
|
|
"github.com/luxfi/node/vms/platformvm/config"
|
|
"github.com/luxfi/timer/mockable"
|
|
|
|
txfee "github.com/luxfi/node/vms/platformvm/txs/fee"
|
|
validatorfee "github.com/luxfi/node/vms/platformvm/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 returns the dynamic fee calculator; the legacy simple
|
|
// (zero-fee) calculator is unreachable under activate-all-implicitly.
|
|
//
|
|
// PickFeeCalculator does not modify [state].
|
|
func PickFeeCalculator(config *config.Internal, state Chain) txfee.Calculator {
|
|
feeState := state.GetFeeState()
|
|
gasPrice := gas.CalculatePrice(
|
|
config.DynamicFeeConfig.MinPrice,
|
|
feeState.Excess,
|
|
config.DynamicFeeConfig.ExcessConversionConstant,
|
|
)
|
|
return txfee.NewDynamicCalculator(
|
|
config.DynamicFeeConfig.Weights,
|
|
gasPrice,
|
|
)
|
|
}
|