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  }