github.com/MetalBlockchain/metalgo@v1.11.9/vms/proposervm/pre_fork_block.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package proposervm 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "time" 11 12 "go.uber.org/zap" 13 14 "github.com/MetalBlockchain/metalgo/database" 15 "github.com/MetalBlockchain/metalgo/ids" 16 "github.com/MetalBlockchain/metalgo/snow/choices" 17 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 18 "github.com/MetalBlockchain/metalgo/vms/proposervm/block" 19 ) 20 21 var ( 22 _ Block = (*preForkBlock)(nil) 23 24 errChildOfPreForkBlockHasProposer = errors.New("child of pre-fork block has proposer") 25 ) 26 27 type preForkBlock struct { 28 snowman.Block 29 vm *VM 30 } 31 32 func (b *preForkBlock) Accept(ctx context.Context) error { 33 if err := b.acceptOuterBlk(); err != nil { 34 return err 35 } 36 return b.acceptInnerBlk(ctx) 37 } 38 39 func (*preForkBlock) acceptOuterBlk() error { 40 return nil 41 } 42 43 func (b *preForkBlock) acceptInnerBlk(ctx context.Context) error { 44 return b.Block.Accept(ctx) 45 } 46 47 func (b *preForkBlock) Status() choices.Status { 48 forkHeight, err := b.vm.GetForkHeight() 49 if err == database.ErrNotFound { 50 return b.Block.Status() 51 } 52 if err != nil { 53 // TODO: Once `Status()` can return an error, we should return the error 54 // here. 55 b.vm.ctx.Log.Error("unexpected error looking up fork height", 56 zap.Error(err), 57 ) 58 return b.Block.Status() 59 } 60 61 // The fork has occurred earlier than this block, so preForkBlocks are all 62 // invalid. 63 if b.Height() >= forkHeight { 64 return choices.Rejected 65 } 66 return b.Block.Status() 67 } 68 69 func (b *preForkBlock) Verify(ctx context.Context) error { 70 parent, err := b.vm.getPreForkBlock(ctx, b.Block.Parent()) 71 if err != nil { 72 return err 73 } 74 return parent.verifyPreForkChild(ctx, b) 75 } 76 77 func (b *preForkBlock) Options(ctx context.Context) ([2]snowman.Block, error) { 78 oracleBlk, ok := b.Block.(snowman.OracleBlock) 79 if !ok { 80 return [2]snowman.Block{}, snowman.ErrNotOracle 81 } 82 83 options, err := oracleBlk.Options(ctx) 84 if err != nil { 85 return [2]snowman.Block{}, err 86 } 87 // A pre-fork block's child options are always pre-fork blocks 88 return [2]snowman.Block{ 89 &preForkBlock{ 90 Block: options[0], 91 vm: b.vm, 92 }, 93 &preForkBlock{ 94 Block: options[1], 95 vm: b.vm, 96 }, 97 }, nil 98 } 99 100 func (b *preForkBlock) getInnerBlk() snowman.Block { 101 return b.Block 102 } 103 104 func (b *preForkBlock) verifyPreForkChild(ctx context.Context, child *preForkBlock) error { 105 parentTimestamp := b.Timestamp() 106 if !parentTimestamp.Before(b.vm.ActivationTime) { 107 if err := verifyIsOracleBlock(ctx, b.Block); err != nil { 108 return err 109 } 110 111 b.vm.ctx.Log.Debug("allowing pre-fork block after the fork time", 112 zap.String("reason", "parent is an oracle block"), 113 zap.Stringer("blkID", b.ID()), 114 ) 115 } 116 117 return child.Block.Verify(ctx) 118 } 119 120 // This method only returns nil once (during the transition) 121 func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkBlock) error { 122 if err := verifyIsNotOracleBlock(ctx, b.Block); err != nil { 123 return err 124 } 125 126 childID := child.ID() 127 childPChainHeight := child.PChainHeight() 128 currentPChainHeight, err := b.vm.ctx.ValidatorState.GetCurrentHeight(ctx) 129 if err != nil { 130 b.vm.ctx.Log.Error("block verification failed", 131 zap.String("reason", "failed to get current P-Chain height"), 132 zap.Stringer("blkID", childID), 133 zap.Error(err), 134 ) 135 return err 136 } 137 if childPChainHeight > currentPChainHeight { 138 return fmt.Errorf("%w: %d > %d", 139 errPChainHeightNotReached, 140 childPChainHeight, 141 currentPChainHeight, 142 ) 143 } 144 if childPChainHeight < b.vm.MinimumPChainHeight { 145 return errPChainHeightTooLow 146 } 147 148 // Make sure [b] is the parent of [child]'s inner block 149 expectedInnerParentID := b.ID() 150 innerParentID := child.innerBlk.Parent() 151 if innerParentID != expectedInnerParentID { 152 return errInnerParentMismatch 153 } 154 155 // A *preForkBlock can only have a *postForkBlock child 156 // if the *preForkBlock is the last *preForkBlock before activation takes effect 157 // (its timestamp is at or after the activation time) 158 parentTimestamp := b.Timestamp() 159 if parentTimestamp.Before(b.vm.ActivationTime) { 160 return errProposersNotActivated 161 } 162 163 // Child's timestamp must be at or after its parent's timestamp 164 childTimestamp := child.Timestamp() 165 if childTimestamp.Before(parentTimestamp) { 166 return errTimeNotMonotonic 167 } 168 169 // Child timestamp can't be too far in the future 170 maxTimestamp := b.vm.Time().Add(maxSkew) 171 if childTimestamp.After(maxTimestamp) { 172 return errTimeTooAdvanced 173 } 174 175 // Verify the lack of signature on the node 176 if child.SignedBlock.Proposer() != ids.EmptyNodeID { 177 return errChildOfPreForkBlockHasProposer 178 } 179 180 // Verify the inner block and track it as verified 181 return b.vm.verifyAndRecordInnerBlk(ctx, nil, child) 182 } 183 184 func (*preForkBlock) verifyPostForkOption(context.Context, *postForkOption) error { 185 return errUnexpectedBlockType 186 } 187 188 func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) { 189 parentTimestamp := b.Timestamp() 190 if parentTimestamp.Before(b.vm.ActivationTime) { 191 // The chain hasn't forked yet 192 innerBlock, err := b.vm.ChainVM.BuildBlock(ctx) 193 if err != nil { 194 return nil, err 195 } 196 197 b.vm.ctx.Log.Info("built block", 198 zap.Stringer("blkID", innerBlock.ID()), 199 zap.Uint64("height", innerBlock.Height()), 200 zap.Time("parentTimestamp", parentTimestamp), 201 ) 202 203 return &preForkBlock{ 204 Block: innerBlock, 205 vm: b.vm, 206 }, nil 207 } 208 209 // The chain is currently forking 210 211 parentID := b.ID() 212 newTimestamp := b.vm.Time().Truncate(time.Second) 213 if newTimestamp.Before(parentTimestamp) { 214 newTimestamp = parentTimestamp 215 } 216 217 // The child's P-Chain height is proposed as the optimal P-Chain height that 218 // is at least the minimum height 219 pChainHeight, err := b.vm.optimalPChainHeight(ctx, b.vm.MinimumPChainHeight) 220 if err != nil { 221 b.vm.ctx.Log.Error("unexpected build block failure", 222 zap.String("reason", "failed to calculate optimal P-chain height"), 223 zap.Stringer("parentID", parentID), 224 zap.Error(err), 225 ) 226 return nil, err 227 } 228 229 innerBlock, err := b.vm.ChainVM.BuildBlock(ctx) 230 if err != nil { 231 return nil, err 232 } 233 234 statelessBlock, err := block.BuildUnsigned( 235 parentID, 236 newTimestamp, 237 pChainHeight, 238 innerBlock.Bytes(), 239 ) 240 if err != nil { 241 return nil, err 242 } 243 244 blk := &postForkBlock{ 245 SignedBlock: statelessBlock, 246 postForkCommonComponents: postForkCommonComponents{ 247 vm: b.vm, 248 innerBlk: innerBlock, 249 status: choices.Processing, 250 }, 251 } 252 253 b.vm.ctx.Log.Info("built block", 254 zap.Stringer("blkID", blk.ID()), 255 zap.Stringer("innerBlkID", innerBlock.ID()), 256 zap.Uint64("height", blk.Height()), 257 zap.Uint64("pChainHeight", pChainHeight), 258 zap.Time("parentTimestamp", parentTimestamp), 259 zap.Time("blockTimestamp", newTimestamp)) 260 return blk, nil 261 } 262 263 func (*preForkBlock) pChainHeight(context.Context) (uint64, error) { 264 return 0, nil 265 }