github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/block/executor/options.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  
    10  	"go.uber.org/zap"
    11  
    12  	"github.com/ava-labs/avalanchego/snow/consensus/snowman"
    13  	"github.com/ava-labs/avalanchego/snow/uptime"
    14  	"github.com/ava-labs/avalanchego/utils/constants"
    15  	"github.com/ava-labs/avalanchego/utils/logging"
    16  	"github.com/ava-labs/avalanchego/vms/platformvm/block"
    17  	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
    18  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    19  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    20  	"github.com/ava-labs/avalanchego/vms/platformvm/txs/executor"
    21  )
    22  
    23  var (
    24  	_ block.Visitor = (*options)(nil)
    25  
    26  	errUnexpectedProposalTxType           = errors.New("unexpected proposal transaction type")
    27  	errFailedFetchingStakerTx             = errors.New("failed fetching staker transaction")
    28  	errUnexpectedStakerTxType             = errors.New("unexpected staker transaction type")
    29  	errFailedFetchingPrimaryStaker        = errors.New("failed fetching primary staker")
    30  	errFailedFetchingSubnetTransformation = errors.New("failed fetching subnet transformation")
    31  	errFailedCalculatingUptime            = errors.New("failed calculating uptime")
    32  )
    33  
    34  // options supports build new option blocks
    35  type options struct {
    36  	// inputs populated before calling this struct's methods:
    37  	log                     logging.Logger
    38  	primaryUptimePercentage float64
    39  	uptimes                 uptime.Calculator
    40  	state                   state.Chain
    41  
    42  	// outputs populated by this struct's methods:
    43  	preferredBlock block.Block
    44  	alternateBlock block.Block
    45  }
    46  
    47  func (*options) BanffAbortBlock(*block.BanffAbortBlock) error {
    48  	return snowman.ErrNotOracle
    49  }
    50  
    51  func (*options) BanffCommitBlock(*block.BanffCommitBlock) error {
    52  	return snowman.ErrNotOracle
    53  }
    54  
    55  func (o *options) BanffProposalBlock(b *block.BanffProposalBlock) error {
    56  	timestamp := b.Timestamp()
    57  	blkID := b.ID()
    58  	nextHeight := b.Height() + 1
    59  
    60  	commitBlock, err := block.NewBanffCommitBlock(timestamp, blkID, nextHeight)
    61  	if err != nil {
    62  		return fmt.Errorf(
    63  			"failed to create commit block: %w",
    64  			err,
    65  		)
    66  	}
    67  
    68  	abortBlock, err := block.NewBanffAbortBlock(timestamp, blkID, nextHeight)
    69  	if err != nil {
    70  		return fmt.Errorf(
    71  			"failed to create abort block: %w",
    72  			err,
    73  		)
    74  	}
    75  
    76  	prefersCommit, err := o.prefersCommit(b.Tx)
    77  	if err != nil {
    78  		o.log.Debug("falling back to prefer commit",
    79  			zap.Error(err),
    80  		)
    81  		// We fall back to commit here to err on the side of over-rewarding
    82  		// rather than under-rewarding.
    83  		//
    84  		// Invariant: We must not return the error here, because the error would
    85  		// be treated as fatal. Errors can occur here due to a malicious block
    86  		// proposer or even in unusual virtuous cases.
    87  		prefersCommit = true
    88  	}
    89  
    90  	if prefersCommit {
    91  		o.preferredBlock = commitBlock
    92  		o.alternateBlock = abortBlock
    93  	} else {
    94  		o.preferredBlock = abortBlock
    95  		o.alternateBlock = commitBlock
    96  	}
    97  	return nil
    98  }
    99  
   100  func (*options) BanffStandardBlock(*block.BanffStandardBlock) error {
   101  	return snowman.ErrNotOracle
   102  }
   103  
   104  func (*options) ApricotAbortBlock(*block.ApricotAbortBlock) error {
   105  	return snowman.ErrNotOracle
   106  }
   107  
   108  func (*options) ApricotCommitBlock(*block.ApricotCommitBlock) error {
   109  	return snowman.ErrNotOracle
   110  }
   111  
   112  func (o *options) ApricotProposalBlock(b *block.ApricotProposalBlock) error {
   113  	blkID := b.ID()
   114  	nextHeight := b.Height() + 1
   115  
   116  	var err error
   117  	o.preferredBlock, err = block.NewApricotCommitBlock(blkID, nextHeight)
   118  	if err != nil {
   119  		return fmt.Errorf(
   120  			"failed to create commit block: %w",
   121  			err,
   122  		)
   123  	}
   124  
   125  	o.alternateBlock, err = block.NewApricotAbortBlock(blkID, nextHeight)
   126  	if err != nil {
   127  		return fmt.Errorf(
   128  			"failed to create abort block: %w",
   129  			err,
   130  		)
   131  	}
   132  	return nil
   133  }
   134  
   135  func (*options) ApricotStandardBlock(*block.ApricotStandardBlock) error {
   136  	return snowman.ErrNotOracle
   137  }
   138  
   139  func (*options) ApricotAtomicBlock(*block.ApricotAtomicBlock) error {
   140  	return snowman.ErrNotOracle
   141  }
   142  
   143  func (o *options) prefersCommit(tx *txs.Tx) (bool, error) {
   144  	unsignedTx, ok := tx.Unsigned.(*txs.RewardValidatorTx)
   145  	if !ok {
   146  		return false, fmt.Errorf("%w: %T", errUnexpectedProposalTxType, tx.Unsigned)
   147  	}
   148  
   149  	stakerTx, _, err := o.state.GetTx(unsignedTx.TxID)
   150  	if err != nil {
   151  		return false, fmt.Errorf("%w: %w", errFailedFetchingStakerTx, err)
   152  	}
   153  
   154  	staker, ok := stakerTx.Unsigned.(txs.Staker)
   155  	if !ok {
   156  		return false, fmt.Errorf("%w: %T", errUnexpectedStakerTxType, stakerTx.Unsigned)
   157  	}
   158  
   159  	nodeID := staker.NodeID()
   160  	primaryNetworkValidator, err := o.state.GetCurrentValidator(
   161  		constants.PrimaryNetworkID,
   162  		nodeID,
   163  	)
   164  	if err != nil {
   165  		return false, fmt.Errorf("%w: %w", errFailedFetchingPrimaryStaker, err)
   166  	}
   167  
   168  	expectedUptimePercentage := o.primaryUptimePercentage
   169  	if subnetID := staker.SubnetID(); subnetID != constants.PrimaryNetworkID {
   170  		transformSubnet, err := executor.GetTransformSubnetTx(o.state, subnetID)
   171  		if err != nil {
   172  			return false, fmt.Errorf("%w: %w", errFailedFetchingSubnetTransformation, err)
   173  		}
   174  
   175  		expectedUptimePercentage = float64(transformSubnet.UptimeRequirement) / reward.PercentDenominator
   176  	}
   177  
   178  	// TODO: calculate subnet uptimes
   179  	uptime, err := o.uptimes.CalculateUptimePercentFrom(
   180  		nodeID,
   181  		constants.PrimaryNetworkID,
   182  		primaryNetworkValidator.StartTime,
   183  	)
   184  	if err != nil {
   185  		return false, fmt.Errorf("%w: %w", errFailedCalculatingUptime, err)
   186  	}
   187  
   188  	return uptime >= expectedUptimePercentage, nil
   189  }