github.com/MetalBlockchain/metalgo@v1.11.9/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/MetalBlockchain/metalgo/snow/consensus/snowman" 13 "github.com/MetalBlockchain/metalgo/snow/uptime" 14 "github.com/MetalBlockchain/metalgo/utils/constants" 15 "github.com/MetalBlockchain/metalgo/utils/logging" 16 "github.com/MetalBlockchain/metalgo/vms/platformvm/block" 17 "github.com/MetalBlockchain/metalgo/vms/platformvm/reward" 18 "github.com/MetalBlockchain/metalgo/vms/platformvm/state" 19 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 20 "github.com/MetalBlockchain/metalgo/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 }