github.com/MetalBlockchain/metalgo@v1.11.9/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/MetalBlockchain/metalgo/ids" 12 "github.com/MetalBlockchain/metalgo/utils/constants" 13 "github.com/MetalBlockchain/metalgo/vms/platformvm/reward" 14 "github.com/MetalBlockchain/metalgo/vms/platformvm/state" 15 "github.com/MetalBlockchain/metalgo/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 changes.PutCurrentValidator(&stakerToAdd) 101 changes.DeletePendingValidator(stakerToRemove) 102 changed = true 103 continue 104 } 105 106 supply, err := changes.GetCurrentSupply(stakerToRemove.SubnetID) 107 if err != nil { 108 return false, err 109 } 110 111 rewards, err := GetRewardsCalculator(backend, parentState, stakerToRemove.SubnetID) 112 if err != nil { 113 return false, err 114 } 115 116 potentialReward := rewards.Calculate( 117 stakerToRemove.EndTime.Sub(stakerToRemove.StartTime), 118 stakerToRemove.Weight, 119 supply, 120 ) 121 stakerToAdd.PotentialReward = potentialReward 122 123 // Invariant: [rewards.Calculate] can never return a [potentialReward] 124 // such that [supply + potentialReward > maximumSupply]. 125 changes.SetCurrentSupply(stakerToRemove.SubnetID, supply+potentialReward) 126 127 switch stakerToRemove.Priority { 128 case txs.PrimaryNetworkValidatorPendingPriority, txs.SubnetPermissionlessValidatorPendingPriority: 129 changes.PutCurrentValidator(&stakerToAdd) 130 changes.DeletePendingValidator(stakerToRemove) 131 132 case txs.PrimaryNetworkDelegatorApricotPendingPriority, txs.PrimaryNetworkDelegatorBanffPendingPriority, txs.SubnetPermissionlessDelegatorPendingPriority: 133 changes.PutCurrentDelegator(&stakerToAdd) 134 changes.DeletePendingDelegator(stakerToRemove) 135 136 default: 137 return false, fmt.Errorf("expected staker priority got %d", stakerToRemove.Priority) 138 } 139 140 changed = true 141 } 142 143 // Remove any current stakers whose [EndTime] <= [newChainTime]. 144 currentStakerIterator, err := parentState.GetCurrentStakerIterator() 145 if err != nil { 146 return false, err 147 } 148 defer currentStakerIterator.Release() 149 150 for currentStakerIterator.Next() { 151 stakerToRemove := currentStakerIterator.Value() 152 if stakerToRemove.EndTime.After(newChainTime) { 153 break 154 } 155 156 // Invariant: Permissioned stakers are encountered first for a given 157 // timestamp because their priority is the smallest. 158 if stakerToRemove.Priority != txs.SubnetPermissionedValidatorCurrentPriority { 159 // Permissionless stakers are removed by the RewardValidatorTx, not 160 // an AdvanceTimeTx. 161 break 162 } 163 164 changes.DeleteCurrentValidator(stakerToRemove) 165 changed = true 166 } 167 168 if err := changes.Apply(parentState); err != nil { 169 return false, err 170 } 171 172 parentState.SetTimestamp(newChainTime) 173 return changed, nil 174 } 175 176 func GetRewardsCalculator( 177 backend *Backend, 178 parentState state.Chain, 179 subnetID ids.ID, 180 ) (reward.Calculator, error) { 181 if subnetID == constants.PrimaryNetworkID { 182 return backend.Rewards, nil 183 } 184 185 transformSubnet, err := GetTransformSubnetTx(parentState, subnetID) 186 if err != nil { 187 return nil, err 188 } 189 190 return reward.NewCalculator(reward.Config{ 191 MaxConsumptionRate: transformSubnet.MaxConsumptionRate, 192 MinConsumptionRate: transformSubnet.MinConsumptionRate, 193 MintingPeriod: backend.Config.RewardConfig.MintingPeriod, 194 SupplyCap: transformSubnet.MaximumSupply, 195 }), nil 196 }