github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/executor/proposal_tx_executor.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/database"
    12  	"github.com/MetalBlockchain/metalgo/ids"
    13  	"github.com/MetalBlockchain/metalgo/utils/math"
    14  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    15  	"github.com/MetalBlockchain/metalgo/vms/components/verify"
    16  	"github.com/MetalBlockchain/metalgo/vms/platformvm/reward"
    17  	"github.com/MetalBlockchain/metalgo/vms/platformvm/state"
    18  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs"
    19  )
    20  
    21  const (
    22  	// Maximum future start time for staking/delegating
    23  	MaxFutureStartTime = 24 * 7 * 2 * time.Hour
    24  
    25  	// SyncBound is the synchrony bound used for safe decision making
    26  	SyncBound = 10 * time.Second
    27  
    28  	MaxValidatorWeightFactor = 5
    29  )
    30  
    31  var (
    32  	_ txs.Visitor = (*ProposalTxExecutor)(nil)
    33  
    34  	ErrRemoveStakerTooEarly          = errors.New("attempting to remove staker before their end time")
    35  	ErrRemoveWrongStaker             = errors.New("attempting to remove wrong staker")
    36  	ErrChildBlockNotAfterParent      = errors.New("proposed timestamp not after current chain time")
    37  	ErrInvalidState                  = errors.New("generated output isn't valid state")
    38  	ErrShouldBePermissionlessStaker  = errors.New("expected permissionless staker")
    39  	ErrWrongTxType                   = errors.New("wrong transaction type")
    40  	ErrInvalidID                     = errors.New("invalid ID")
    41  	ErrProposedAddStakerTxAfterBanff = errors.New("staker transaction proposed after Banff")
    42  	ErrAdvanceTimeTxIssuedAfterBanff = errors.New("AdvanceTimeTx issued after Banff")
    43  )
    44  
    45  type ProposalTxExecutor struct {
    46  	// inputs, to be filled before visitor methods are called
    47  	*Backend
    48  	Tx *txs.Tx
    49  	// [OnCommitState] is the state used for validation.
    50  	// [OnCommitState] is modified by this struct's methods to
    51  	// reflect changes made to the state if the proposal is committed.
    52  	//
    53  	// Invariant: Both [OnCommitState] and [OnAbortState] represent the same
    54  	//            state when provided to this struct.
    55  	OnCommitState state.Diff
    56  	// [OnAbortState] is modified by this struct's methods to
    57  	// reflect changes made to the state if the proposal is aborted.
    58  	OnAbortState state.Diff
    59  }
    60  
    61  func (*ProposalTxExecutor) CreateChainTx(*txs.CreateChainTx) error {
    62  	return ErrWrongTxType
    63  }
    64  
    65  func (*ProposalTxExecutor) CreateSubnetTx(*txs.CreateSubnetTx) error {
    66  	return ErrWrongTxType
    67  }
    68  
    69  func (*ProposalTxExecutor) ImportTx(*txs.ImportTx) error {
    70  	return ErrWrongTxType
    71  }
    72  
    73  func (*ProposalTxExecutor) ExportTx(*txs.ExportTx) error {
    74  	return ErrWrongTxType
    75  }
    76  
    77  func (*ProposalTxExecutor) RemoveSubnetValidatorTx(*txs.RemoveSubnetValidatorTx) error {
    78  	return ErrWrongTxType
    79  }
    80  
    81  func (*ProposalTxExecutor) TransformSubnetTx(*txs.TransformSubnetTx) error {
    82  	return ErrWrongTxType
    83  }
    84  
    85  func (*ProposalTxExecutor) AddPermissionlessValidatorTx(*txs.AddPermissionlessValidatorTx) error {
    86  	return ErrWrongTxType
    87  }
    88  
    89  func (*ProposalTxExecutor) AddPermissionlessDelegatorTx(*txs.AddPermissionlessDelegatorTx) error {
    90  	return ErrWrongTxType
    91  }
    92  
    93  func (*ProposalTxExecutor) TransferSubnetOwnershipTx(*txs.TransferSubnetOwnershipTx) error {
    94  	return ErrWrongTxType
    95  }
    96  
    97  func (*ProposalTxExecutor) BaseTx(*txs.BaseTx) error {
    98  	return ErrWrongTxType
    99  }
   100  
   101  func (e *ProposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error {
   102  	// AddValidatorTx is a proposal transaction until the Banff fork
   103  	// activation. Following the activation, AddValidatorTxs must be issued into
   104  	// StandardBlocks.
   105  	currentTimestamp := e.OnCommitState.GetTimestamp()
   106  	if e.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) {
   107  		return fmt.Errorf(
   108  			"%w: timestamp (%s) >= Banff fork time (%s)",
   109  			ErrProposedAddStakerTxAfterBanff,
   110  			currentTimestamp,
   111  			e.Config.UpgradeConfig.BanffTime,
   112  		)
   113  	}
   114  
   115  	onAbortOuts, err := verifyAddValidatorTx(
   116  		e.Backend,
   117  		e.OnCommitState,
   118  		e.Tx,
   119  		tx,
   120  	)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	txID := e.Tx.ID()
   126  
   127  	// Set up the state if this tx is committed
   128  	// Consume the UTXOs
   129  	avax.Consume(e.OnCommitState, tx.Ins)
   130  	// Produce the UTXOs
   131  	avax.Produce(e.OnCommitState, txID, tx.Outs)
   132  
   133  	newStaker, err := state.NewPendingStaker(txID, tx)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	e.OnCommitState.PutPendingValidator(newStaker)
   139  
   140  	// Set up the state if this tx is aborted
   141  	// Consume the UTXOs
   142  	avax.Consume(e.OnAbortState, tx.Ins)
   143  	// Produce the UTXOs
   144  	avax.Produce(e.OnAbortState, txID, onAbortOuts)
   145  	return nil
   146  }
   147  
   148  func (e *ProposalTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) error {
   149  	// AddSubnetValidatorTx is a proposal transaction until the Banff fork
   150  	// activation. Following the activation, AddSubnetValidatorTxs must be
   151  	// issued into StandardBlocks.
   152  	currentTimestamp := e.OnCommitState.GetTimestamp()
   153  	if e.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) {
   154  		return fmt.Errorf(
   155  			"%w: timestamp (%s) >= Banff fork time (%s)",
   156  			ErrProposedAddStakerTxAfterBanff,
   157  			currentTimestamp,
   158  			e.Config.UpgradeConfig.BanffTime,
   159  		)
   160  	}
   161  
   162  	if err := verifyAddSubnetValidatorTx(
   163  		e.Backend,
   164  		e.OnCommitState,
   165  		e.Tx,
   166  		tx,
   167  	); err != nil {
   168  		return err
   169  	}
   170  
   171  	txID := e.Tx.ID()
   172  
   173  	// Set up the state if this tx is committed
   174  	// Consume the UTXOs
   175  	avax.Consume(e.OnCommitState, tx.Ins)
   176  	// Produce the UTXOs
   177  	avax.Produce(e.OnCommitState, txID, tx.Outs)
   178  
   179  	newStaker, err := state.NewPendingStaker(txID, tx)
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	e.OnCommitState.PutPendingValidator(newStaker)
   185  
   186  	// Set up the state if this tx is aborted
   187  	// Consume the UTXOs
   188  	avax.Consume(e.OnAbortState, tx.Ins)
   189  	// Produce the UTXOs
   190  	avax.Produce(e.OnAbortState, txID, tx.Outs)
   191  	return nil
   192  }
   193  
   194  func (e *ProposalTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error {
   195  	// AddDelegatorTx is a proposal transaction until the Banff fork
   196  	// activation. Following the activation, AddDelegatorTxs must be issued into
   197  	// StandardBlocks.
   198  	currentTimestamp := e.OnCommitState.GetTimestamp()
   199  	if e.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) {
   200  		return fmt.Errorf(
   201  			"%w: timestamp (%s) >= Banff fork time (%s)",
   202  			ErrProposedAddStakerTxAfterBanff,
   203  			currentTimestamp,
   204  			e.Config.UpgradeConfig.BanffTime,
   205  		)
   206  	}
   207  
   208  	onAbortOuts, err := verifyAddDelegatorTx(
   209  		e.Backend,
   210  		e.OnCommitState,
   211  		e.Tx,
   212  		tx,
   213  	)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	txID := e.Tx.ID()
   219  
   220  	// Set up the state if this tx is committed
   221  	// Consume the UTXOs
   222  	avax.Consume(e.OnCommitState, tx.Ins)
   223  	// Produce the UTXOs
   224  	avax.Produce(e.OnCommitState, txID, tx.Outs)
   225  
   226  	newStaker, err := state.NewPendingStaker(txID, tx)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	e.OnCommitState.PutPendingDelegator(newStaker)
   232  
   233  	// Set up the state if this tx is aborted
   234  	// Consume the UTXOs
   235  	avax.Consume(e.OnAbortState, tx.Ins)
   236  	// Produce the UTXOs
   237  	avax.Produce(e.OnAbortState, txID, onAbortOuts)
   238  	return nil
   239  }
   240  
   241  func (e *ProposalTxExecutor) AdvanceTimeTx(tx *txs.AdvanceTimeTx) error {
   242  	switch {
   243  	case tx == nil:
   244  		return txs.ErrNilTx
   245  	case len(e.Tx.Creds) != 0:
   246  		return errWrongNumberOfCredentials
   247  	}
   248  
   249  	// Validate [newChainTime]
   250  	newChainTime := tx.Timestamp()
   251  	if e.Config.UpgradeConfig.IsBanffActivated(newChainTime) {
   252  		return fmt.Errorf(
   253  			"%w: proposed timestamp (%s) >= Banff fork time (%s)",
   254  			ErrAdvanceTimeTxIssuedAfterBanff,
   255  			newChainTime,
   256  			e.Config.UpgradeConfig.BanffTime,
   257  		)
   258  	}
   259  
   260  	parentChainTime := e.OnCommitState.GetTimestamp()
   261  	if !newChainTime.After(parentChainTime) {
   262  		return fmt.Errorf(
   263  			"%w, proposed timestamp (%s), chain time (%s)",
   264  			ErrChildBlockNotAfterParent,
   265  			parentChainTime,
   266  			parentChainTime,
   267  		)
   268  	}
   269  
   270  	// Only allow timestamp to move forward as far as the time of next staker
   271  	// set change time
   272  	nextStakerChangeTime, err := state.GetNextStakerChangeTime(e.OnCommitState)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	now := e.Clk.Time()
   278  	if err := VerifyNewChainTime(
   279  		newChainTime,
   280  		nextStakerChangeTime,
   281  		now,
   282  	); err != nil {
   283  		return err
   284  	}
   285  
   286  	// Note that state doesn't change if this proposal is aborted
   287  	_, err = AdvanceTimeTo(e.Backend, e.OnCommitState, newChainTime)
   288  	return err
   289  }
   290  
   291  func (e *ProposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error {
   292  	switch {
   293  	case tx == nil:
   294  		return txs.ErrNilTx
   295  	case tx.TxID == ids.Empty:
   296  		return ErrInvalidID
   297  	case len(e.Tx.Creds) != 0:
   298  		return errWrongNumberOfCredentials
   299  	}
   300  
   301  	currentStakerIterator, err := e.OnCommitState.GetCurrentStakerIterator()
   302  	if err != nil {
   303  		return err
   304  	}
   305  	if !currentStakerIterator.Next() {
   306  		return fmt.Errorf("failed to get next staker to remove: %w", database.ErrNotFound)
   307  	}
   308  	stakerToReward := currentStakerIterator.Value()
   309  	currentStakerIterator.Release()
   310  
   311  	if stakerToReward.TxID != tx.TxID {
   312  		return fmt.Errorf(
   313  			"%w: %s != %s",
   314  			ErrRemoveWrongStaker,
   315  			stakerToReward.TxID,
   316  			tx.TxID,
   317  		)
   318  	}
   319  
   320  	// Verify that the chain's timestamp is the validator's end time
   321  	currentChainTime := e.OnCommitState.GetTimestamp()
   322  	if !stakerToReward.EndTime.Equal(currentChainTime) {
   323  		return fmt.Errorf(
   324  			"%w: TxID = %s with %s < %s",
   325  			ErrRemoveStakerTooEarly,
   326  			tx.TxID,
   327  			currentChainTime,
   328  			stakerToReward.EndTime,
   329  		)
   330  	}
   331  
   332  	stakerTx, _, err := e.OnCommitState.GetTx(stakerToReward.TxID)
   333  	if err != nil {
   334  		return fmt.Errorf("failed to get next removed staker tx: %w", err)
   335  	}
   336  
   337  	// Invariant: A [txs.DelegatorTx] does not also implement the
   338  	//            [txs.ValidatorTx] interface.
   339  	switch uStakerTx := stakerTx.Unsigned.(type) {
   340  	case txs.ValidatorTx:
   341  		if err := e.rewardValidatorTx(uStakerTx, stakerToReward); err != nil {
   342  			return err
   343  		}
   344  
   345  		// Handle staker lifecycle.
   346  		e.OnCommitState.DeleteCurrentValidator(stakerToReward)
   347  		e.OnAbortState.DeleteCurrentValidator(stakerToReward)
   348  	case txs.DelegatorTx:
   349  		if err := e.rewardDelegatorTx(uStakerTx, stakerToReward); err != nil {
   350  			return err
   351  		}
   352  
   353  		// Handle staker lifecycle.
   354  		e.OnCommitState.DeleteCurrentDelegator(stakerToReward)
   355  		e.OnAbortState.DeleteCurrentDelegator(stakerToReward)
   356  	default:
   357  		// Invariant: Permissioned stakers are removed by the advancement of
   358  		//            time and the current chain timestamp is == this staker's
   359  		//            EndTime. This means only permissionless stakers should be
   360  		//            left in the staker set.
   361  		return ErrShouldBePermissionlessStaker
   362  	}
   363  
   364  	// If the reward is aborted, then the current supply should be decreased.
   365  	currentSupply, err := e.OnAbortState.GetCurrentSupply(stakerToReward.SubnetID)
   366  	if err != nil {
   367  		return err
   368  	}
   369  	newSupply, err := math.Sub(currentSupply, stakerToReward.PotentialReward)
   370  	if err != nil {
   371  		return err
   372  	}
   373  	e.OnAbortState.SetCurrentSupply(stakerToReward.SubnetID, newSupply)
   374  	return nil
   375  }
   376  
   377  func (e *ProposalTxExecutor) rewardValidatorTx(uValidatorTx txs.ValidatorTx, validator *state.Staker) error {
   378  	var (
   379  		txID    = validator.TxID
   380  		stake   = uValidatorTx.Stake()
   381  		outputs = uValidatorTx.Outputs()
   382  		// Invariant: The staked asset must be equal to the reward asset.
   383  		stakeAsset = stake[0].Asset
   384  	)
   385  
   386  	// Refund the stake only when validator is about to leave
   387  	// the staking set
   388  	for i, out := range stake {
   389  		utxo := &avax.UTXO{
   390  			UTXOID: avax.UTXOID{
   391  				TxID:        txID,
   392  				OutputIndex: uint32(len(outputs) + i),
   393  			},
   394  			Asset: out.Asset,
   395  			Out:   out.Output(),
   396  		}
   397  		e.OnCommitState.AddUTXO(utxo)
   398  		e.OnAbortState.AddUTXO(utxo)
   399  	}
   400  
   401  	utxosOffset := 0
   402  
   403  	// Provide the reward here
   404  	reward := validator.PotentialReward
   405  	if reward > 0 {
   406  		validationRewardsOwner := uValidatorTx.ValidationRewardsOwner()
   407  		outIntf, err := e.Fx.CreateOutput(reward, validationRewardsOwner)
   408  		if err != nil {
   409  			return fmt.Errorf("failed to create output: %w", err)
   410  		}
   411  		out, ok := outIntf.(verify.State)
   412  		if !ok {
   413  			return ErrInvalidState
   414  		}
   415  
   416  		utxo := &avax.UTXO{
   417  			UTXOID: avax.UTXOID{
   418  				TxID:        txID,
   419  				OutputIndex: uint32(len(outputs) + len(stake)),
   420  			},
   421  			Asset: stakeAsset,
   422  			Out:   out,
   423  		}
   424  		e.OnCommitState.AddUTXO(utxo)
   425  		e.OnCommitState.AddRewardUTXO(txID, utxo)
   426  
   427  		utxosOffset++
   428  	}
   429  
   430  	// Provide the accrued delegatee rewards from successful delegations here.
   431  	delegateeReward, err := e.OnCommitState.GetDelegateeReward(
   432  		validator.SubnetID,
   433  		validator.NodeID,
   434  	)
   435  	if err != nil {
   436  		return fmt.Errorf("failed to fetch accrued delegatee rewards: %w", err)
   437  	}
   438  
   439  	if delegateeReward == 0 {
   440  		return nil
   441  	}
   442  
   443  	delegationRewardsOwner := uValidatorTx.DelegationRewardsOwner()
   444  	outIntf, err := e.Fx.CreateOutput(delegateeReward, delegationRewardsOwner)
   445  	if err != nil {
   446  		return fmt.Errorf("failed to create output: %w", err)
   447  	}
   448  	out, ok := outIntf.(verify.State)
   449  	if !ok {
   450  		return ErrInvalidState
   451  	}
   452  
   453  	onCommitUtxo := &avax.UTXO{
   454  		UTXOID: avax.UTXOID{
   455  			TxID:        txID,
   456  			OutputIndex: uint32(len(outputs) + len(stake) + utxosOffset),
   457  		},
   458  		Asset: stakeAsset,
   459  		Out:   out,
   460  	}
   461  	e.OnCommitState.AddUTXO(onCommitUtxo)
   462  	e.OnCommitState.AddRewardUTXO(txID, onCommitUtxo)
   463  
   464  	// Note: There is no [offset] if the RewardValidatorTx is
   465  	// aborted, because the validator reward is not awarded.
   466  	onAbortUtxo := &avax.UTXO{
   467  		UTXOID: avax.UTXOID{
   468  			TxID:        txID,
   469  			OutputIndex: uint32(len(outputs) + len(stake)),
   470  		},
   471  		Asset: stakeAsset,
   472  		Out:   out,
   473  	}
   474  	e.OnAbortState.AddUTXO(onAbortUtxo)
   475  	e.OnAbortState.AddRewardUTXO(txID, onAbortUtxo)
   476  	return nil
   477  }
   478  
   479  func (e *ProposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, delegator *state.Staker) error {
   480  	var (
   481  		txID    = delegator.TxID
   482  		stake   = uDelegatorTx.Stake()
   483  		outputs = uDelegatorTx.Outputs()
   484  		// Invariant: The staked asset must be equal to the reward asset.
   485  		stakeAsset = stake[0].Asset
   486  	)
   487  
   488  	// Refund the stake only when delegator is about to leave
   489  	// the staking set
   490  	for i, out := range stake {
   491  		utxo := &avax.UTXO{
   492  			UTXOID: avax.UTXOID{
   493  				TxID:        txID,
   494  				OutputIndex: uint32(len(outputs) + i),
   495  			},
   496  			Asset: out.Asset,
   497  			Out:   out.Output(),
   498  		}
   499  		e.OnCommitState.AddUTXO(utxo)
   500  		e.OnAbortState.AddUTXO(utxo)
   501  	}
   502  
   503  	// We're (possibly) rewarding a delegator, so we need to fetch
   504  	// the validator they are delegated to.
   505  	validator, err := e.OnCommitState.GetCurrentValidator(delegator.SubnetID, delegator.NodeID)
   506  	if err != nil {
   507  		return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err)
   508  	}
   509  
   510  	vdrTxIntf, _, err := e.OnCommitState.GetTx(validator.TxID)
   511  	if err != nil {
   512  		return fmt.Errorf("failed to get whether %s is a validator: %w", delegator.NodeID, err)
   513  	}
   514  
   515  	// Invariant: Delegators must only be able to reference validator
   516  	//            transactions that implement [txs.ValidatorTx]. All
   517  	//            validator transactions implement this interface except the
   518  	//            AddSubnetValidatorTx.
   519  	vdrTx, ok := vdrTxIntf.Unsigned.(txs.ValidatorTx)
   520  	if !ok {
   521  		return ErrWrongTxType
   522  	}
   523  
   524  	// Calculate split of reward between delegator/delegatee
   525  	delegateeReward, delegatorReward := reward.Split(delegator.PotentialReward, vdrTx.Shares())
   526  
   527  	utxosOffset := 0
   528  
   529  	// Reward the delegator here
   530  	reward := delegatorReward
   531  	if reward > 0 {
   532  		rewardsOwner := uDelegatorTx.RewardsOwner()
   533  		outIntf, err := e.Fx.CreateOutput(reward, rewardsOwner)
   534  		if err != nil {
   535  			return fmt.Errorf("failed to create output: %w", err)
   536  		}
   537  		out, ok := outIntf.(verify.State)
   538  		if !ok {
   539  			return ErrInvalidState
   540  		}
   541  		utxo := &avax.UTXO{
   542  			UTXOID: avax.UTXOID{
   543  				TxID:        txID,
   544  				OutputIndex: uint32(len(outputs) + len(stake)),
   545  			},
   546  			Asset: stakeAsset,
   547  			Out:   out,
   548  		}
   549  
   550  		e.OnCommitState.AddUTXO(utxo)
   551  		e.OnCommitState.AddRewardUTXO(txID, utxo)
   552  
   553  		utxosOffset++
   554  	}
   555  
   556  	if delegateeReward == 0 {
   557  		return nil
   558  	}
   559  
   560  	// Reward the delegatee here
   561  	if e.Config.UpgradeConfig.IsCortinaActivated(validator.StartTime) {
   562  		previousDelegateeReward, err := e.OnCommitState.GetDelegateeReward(
   563  			validator.SubnetID,
   564  			validator.NodeID,
   565  		)
   566  		if err != nil {
   567  			return fmt.Errorf("failed to get delegatee reward: %w", err)
   568  		}
   569  
   570  		// Invariant: The rewards calculator can never return a
   571  		//            [potentialReward] that would overflow the
   572  		//            accumulated rewards.
   573  		newDelegateeReward := previousDelegateeReward + delegateeReward
   574  
   575  		// For any validators starting after [CortinaTime], we defer rewarding the
   576  		// [reward] until their staking period is over.
   577  		err = e.OnCommitState.SetDelegateeReward(
   578  			validator.SubnetID,
   579  			validator.NodeID,
   580  			newDelegateeReward,
   581  		)
   582  		if err != nil {
   583  			return fmt.Errorf("failed to update delegatee reward: %w", err)
   584  		}
   585  	} else {
   586  		// For any validators who started prior to [CortinaTime], we issue the
   587  		// [delegateeReward] immediately.
   588  		delegationRewardsOwner := vdrTx.DelegationRewardsOwner()
   589  		outIntf, err := e.Fx.CreateOutput(delegateeReward, delegationRewardsOwner)
   590  		if err != nil {
   591  			return fmt.Errorf("failed to create output: %w", err)
   592  		}
   593  		out, ok := outIntf.(verify.State)
   594  		if !ok {
   595  			return ErrInvalidState
   596  		}
   597  		utxo := &avax.UTXO{
   598  			UTXOID: avax.UTXOID{
   599  				TxID:        txID,
   600  				OutputIndex: uint32(len(outputs) + len(stake) + utxosOffset),
   601  			},
   602  			Asset: stakeAsset,
   603  			Out:   out,
   604  		}
   605  
   606  		e.OnCommitState.AddUTXO(utxo)
   607  		e.OnCommitState.AddRewardUTXO(txID, utxo)
   608  	}
   609  	return nil
   610  }