github.com/MetalBlockchain/metalgo@v1.11.9/vms/proposervm/height_indexed_vm.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  	"fmt"
     9  
    10  	"go.uber.org/zap"
    11  
    12  	"github.com/MetalBlockchain/metalgo/database"
    13  	"github.com/MetalBlockchain/metalgo/ids"
    14  )
    15  
    16  const pruneCommitPeriod = 1024
    17  
    18  // vm.ctx.Lock should be held
    19  func (vm *VM) GetBlockIDAtHeight(ctx context.Context, height uint64) (ids.ID, error) {
    20  	switch forkHeight, err := vm.State.GetForkHeight(); err {
    21  	case nil:
    22  		if height < forkHeight {
    23  			return vm.ChainVM.GetBlockIDAtHeight(ctx, height)
    24  		}
    25  		return vm.State.GetBlockIDAtHeight(height)
    26  
    27  	case database.ErrNotFound:
    28  		// fork not reached yet. Block must be pre-fork
    29  		return vm.ChainVM.GetBlockIDAtHeight(ctx, height)
    30  
    31  	default:
    32  		return ids.Empty, err
    33  	}
    34  }
    35  
    36  func (vm *VM) updateHeightIndex(height uint64, blkID ids.ID) error {
    37  	forkHeight, err := vm.State.GetForkHeight()
    38  	switch err {
    39  	case nil:
    40  		// The fork was already reached. Just update the index.
    41  
    42  	case database.ErrNotFound:
    43  		// This is the first post fork block, store the fork height.
    44  		if err := vm.State.SetForkHeight(height); err != nil {
    45  			return fmt.Errorf("failed storing fork height: %w", err)
    46  		}
    47  		forkHeight = height
    48  
    49  	default:
    50  		return fmt.Errorf("failed to load fork height: %w", err)
    51  	}
    52  
    53  	if err := vm.State.SetBlockIDAtHeight(height, blkID); err != nil {
    54  		return err
    55  	}
    56  
    57  	vm.ctx.Log.Debug("indexed block",
    58  		zap.Stringer("blkID", blkID),
    59  		zap.Uint64("height", height),
    60  	)
    61  
    62  	if vm.NumHistoricalBlocks == 0 {
    63  		return nil
    64  	}
    65  
    66  	blocksSinceFork := height - forkHeight
    67  	// Note: The last accepted block is not considered a historical block. Which
    68  	// is why <= is used rather than <. This prevents the user from only storing
    69  	// the last accepted block, which can never be safe due to the non-atomic
    70  	// commits between the proposervm database and the innerVM's database.
    71  	if blocksSinceFork <= vm.NumHistoricalBlocks {
    72  		return nil
    73  	}
    74  
    75  	// Note: heightToDelete is >= forkHeight, so it is guaranteed not to
    76  	// underflow.
    77  	heightToDelete := height - vm.NumHistoricalBlocks - 1
    78  	blockToDelete, err := vm.State.GetBlockIDAtHeight(heightToDelete)
    79  	if err == database.ErrNotFound {
    80  		// Block may have already been deleted. This can happen due to a
    81  		// proposervm rollback, the node having recently state-synced, or the
    82  		// user reconfiguring the node to store more historical blocks than a
    83  		// prior run.
    84  		return nil
    85  	}
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	if err := vm.State.DeleteBlockIDAtHeight(heightToDelete); err != nil {
    91  		return err
    92  	}
    93  	if err := vm.State.DeleteBlock(blockToDelete); err != nil {
    94  		return err
    95  	}
    96  
    97  	vm.ctx.Log.Debug("deleted block",
    98  		zap.Stringer("blkID", blockToDelete),
    99  		zap.Uint64("height", heightToDelete),
   100  	)
   101  	return nil
   102  }
   103  
   104  // TODO: Support async deletion of old blocks.
   105  func (vm *VM) pruneOldBlocks() error {
   106  	if vm.NumHistoricalBlocks == 0 {
   107  		return nil
   108  	}
   109  
   110  	height, err := vm.State.GetMinimumHeight()
   111  	if err == database.ErrNotFound {
   112  		// Chain hasn't forked yet
   113  		return nil
   114  	}
   115  
   116  	// TODO: Refactor to use DB iterators.
   117  	//
   118  	// Note: vm.lastAcceptedHeight is guaranteed to be >= height, so the
   119  	// subtraction can never underflow.
   120  	for vm.lastAcceptedHeight-height > vm.NumHistoricalBlocks {
   121  		blockToDelete, err := vm.State.GetBlockIDAtHeight(height)
   122  		if err != nil {
   123  			return err
   124  		}
   125  
   126  		if err := vm.State.DeleteBlockIDAtHeight(height); err != nil {
   127  			return err
   128  		}
   129  		if err := vm.State.DeleteBlock(blockToDelete); err != nil {
   130  			return err
   131  		}
   132  
   133  		vm.ctx.Log.Debug("deleted block",
   134  			zap.Stringer("blkID", blockToDelete),
   135  			zap.Uint64("height", height),
   136  		)
   137  
   138  		// Note: height is < vm.lastAcceptedHeight, so it is guaranteed not to
   139  		// overflow.
   140  		height++
   141  		if height%pruneCommitPeriod != 0 {
   142  			continue
   143  		}
   144  
   145  		if err := vm.db.Commit(); err != nil {
   146  			return err
   147  		}
   148  	}
   149  	return vm.db.Commit()
   150  }