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  }