github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/state_changes.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package executor 5 6 import ( 7 "errors" 8 "fmt" 9 "time" 10 11 "github.com/ava-labs/avalanchego/ids" 12 "github.com/ava-labs/avalanchego/utils/constants" 13 "github.com/ava-labs/avalanchego/vms/platformvm/reward" 14 "github.com/ava-labs/avalanchego/vms/platformvm/state" 15 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 16 ) 17 18 var ( 19 ErrChildBlockAfterStakerChangeTime = errors.New("proposed timestamp later than next staker change time") 20 ErrChildBlockBeyondSyncBound = errors.New("proposed timestamp is too far in the future relative to local time") 21 ) 22 23 // VerifyNewChainTime returns nil if the [newChainTime] is a valid chain time 24 // given the wall clock time ([now]) and when the next staking set change occurs 25 // ([nextStakerChangeTime]). 26 // Requires: 27 // - [newChainTime] <= [nextStakerChangeTime]: so that no staking set changes 28 // are skipped. 29 // - [newChainTime] <= [now] + [SyncBound]: to ensure chain time approximates 30 // "real" time. 31 func VerifyNewChainTime( 32 newChainTime, 33 nextStakerChangeTime, 34 now time.Time, 35 ) error { 36 // Only allow timestamp to move as far forward as the time of the next 37 // staker set change 38 if newChainTime.After(nextStakerChangeTime) { 39 return fmt.Errorf( 40 "%w, proposed timestamp (%s), next staker change time (%s)", 41 ErrChildBlockAfterStakerChangeTime, 42 newChainTime, 43 nextStakerChangeTime, 44 ) 45 } 46 47 // Only allow timestamp to reasonably far forward 48 maxNewChainTime := now.Add(SyncBound) 49 if newChainTime.After(maxNewChainTime) { 50 return fmt.Errorf( 51 "%w, proposed time (%s), local time (%s)", 52 ErrChildBlockBeyondSyncBound, 53 newChainTime, 54 now, 55 ) 56 } 57 return nil 58 } 59 60 // AdvanceTimeTo applies all state changes to [parentState] resulting from 61 // advancing the chain time to [newChainTime]. 62 // Returns true iff the validator set changed. 63 func AdvanceTimeTo( 64 backend *Backend, 65 parentState state.Chain, 66 newChainTime time.Time, 67 ) (bool, error) { 68 // We promote pending stakers to current stakers first and remove 69 // completed stakers from the current staker set. We assume that any 70 // promoted staker will not immediately be removed from the current staker 71 // set. This is guaranteed by the following invariants. 72 // 73 // Invariant: MinStakeDuration > 0 => guarantees [StartTime] != [EndTime] 74 // Invariant: [newChainTime] <= nextStakerChangeTime. 75 76 changes, err := state.NewDiffOn(parentState) 77 if err != nil { 78 return false, err 79 } 80 81 pendingStakerIterator, err := parentState.GetPendingStakerIterator() 82 if err != nil { 83 return false, err 84 } 85 defer pendingStakerIterator.Release() 86 87 var changed bool 88 // Promote any pending stakers to current if [StartTime] <= [newChainTime]. 89 for pendingStakerIterator.Next() { 90 stakerToRemove := pendingStakerIterator.Value() 91 if stakerToRemove.StartTime.After(newChainTime) { 92 break 93 } 94 95 stakerToAdd := *stakerToRemove 96 stakerToAdd.NextTime = stakerToRemove.EndTime 97 stakerToAdd.Priority = txs.PendingToCurrentPriorities[stakerToRemove.Priority] 98 99 if stakerToRemove.Priority == txs.SubnetPermissionedValidatorPendingPriority { 100 if err := changes.PutCurrentValidator(&stakerToAdd); err != nil { 101 return false, err 102 } 103 changes.DeletePendingValidator(stakerToRemove) 104 changed = true 105 continue 106 } 107 108 supply, err := changes.GetCurrentSupply(stakerToRemove.SubnetID) 109 if err != nil { 110 return false, err 111 } 112 113 rewards, err := GetRewardsCalculator(backend, parentState, stakerToRemove.SubnetID) 114 if err != nil { 115 return false, err 116 } 117 118 potentialReward := rewards.Calculate( 119 stakerToRemove.EndTime.Sub(stakerToRemove.StartTime), 120 stakerToRemove.Weight, 121 supply, 122 ) 123 stakerToAdd.PotentialReward = potentialReward 124 125 // Invariant: [rewards.Calculate] can never return a [potentialReward] 126 // such that [supply + potentialReward > maximumSupply]. 127 changes.SetCurrentSupply(stakerToRemove.SubnetID, supply+potentialReward) 128 129 switch stakerToRemove.Priority { 130 case txs.PrimaryNetworkValidatorPendingPriority, txs.SubnetPermissionlessValidatorPendingPriority: 131 if err := changes.PutCurrentValidator(&stakerToAdd); err != nil { 132 return false, err 133 } 134 changes.DeletePendingValidator(stakerToRemove) 135 136 case txs.PrimaryNetworkDelegatorApricotPendingPriority, txs.PrimaryNetworkDelegatorBanffPendingPriority, txs.SubnetPermissionlessDelegatorPendingPriority: 137 changes.PutCurrentDelegator(&stakerToAdd) 138 changes.DeletePendingDelegator(stakerToRemove) 139 140 default: 141 return false, fmt.Errorf("expected staker priority got %d", stakerToRemove.Priority) 142 } 143 144 changed = true 145 } 146 147 // Remove any current stakers whose [EndTime] <= [newChainTime]. 148 currentStakerIterator, err := parentState.GetCurrentStakerIterator() 149 if err != nil { 150 return false, err 151 } 152 defer currentStakerIterator.Release() 153 154 for currentStakerIterator.Next() { 155 stakerToRemove := currentStakerIterator.Value() 156 if stakerToRemove.EndTime.After(newChainTime) { 157 break 158 } 159 160 // Invariant: Permissioned stakers are encountered first for a given 161 // timestamp because their priority is the smallest. 162 if stakerToRemove.Priority != txs.SubnetPermissionedValidatorCurrentPriority { 163 // Permissionless stakers are removed by the RewardValidatorTx, not 164 // an AdvanceTimeTx. 165 break 166 } 167 168 changes.DeleteCurrentValidator(stakerToRemove) 169 changed = true 170 } 171 172 if backend.Config.UpgradeConfig.IsEtnaActivated(newChainTime) { 173 previousChainTime := changes.GetTimestamp() 174 duration := uint64(newChainTime.Sub(previousChainTime) / time.Second) 175 176 feeState := changes.GetFeeState() 177 feeState = feeState.AdvanceTime( 178 backend.Config.DynamicFeeConfig.MaxCapacity, 179 backend.Config.DynamicFeeConfig.MaxPerSecond, 180 backend.Config.DynamicFeeConfig.TargetPerSecond, 181 duration, 182 ) 183 changes.SetFeeState(feeState) 184 } 185 186 changes.SetTimestamp(newChainTime) 187 return changed, changes.Apply(parentState) 188 } 189 190 func GetRewardsCalculator( 191 backend *Backend, 192 parentState state.Chain, 193 subnetID ids.ID, 194 ) (reward.Calculator, error) { 195 if subnetID == constants.PrimaryNetworkID { 196 return backend.Rewards, nil 197 } 198 199 transformSubnet, err := GetTransformSubnetTx(parentState, subnetID) 200 if err != nil { 201 return nil, err 202 } 203 204 return reward.NewCalculator(reward.Config{ 205 MaxConsumptionRate: transformSubnet.MaxConsumptionRate, 206 MinConsumptionRate: transformSubnet.MinConsumptionRate, 207 MintingPeriod: backend.Config.RewardConfig.MintingPeriod, 208 SupplyCap: transformSubnet.MaximumSupply, 209 }), nil 210 }