github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/snowman/block/batched_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 block
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"time"
    10  
    11  	"go.uber.org/zap"
    12  
    13  	"github.com/MetalBlockchain/metalgo/database"
    14  	"github.com/MetalBlockchain/metalgo/ids"
    15  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowman"
    16  	"github.com/MetalBlockchain/metalgo/utils/logging"
    17  	"github.com/MetalBlockchain/metalgo/utils/wrappers"
    18  )
    19  
    20  var ErrRemoteVMNotImplemented = errors.New("vm does not implement RemoteVM interface")
    21  
    22  // BatchedChainVM extends the minimal functionalities exposed by ChainVM for VMs
    23  // communicating over network (gRPC in our case). This allows more efficient
    24  // operations since calls over network can be duly batched
    25  type BatchedChainVM interface {
    26  	GetAncestors(
    27  		ctx context.Context,
    28  		blkID ids.ID, // first requested block
    29  		maxBlocksNum int, // max number of blocks to be retrieved
    30  		maxBlocksSize int, // max cumulated byte size of retrieved blocks
    31  		maxBlocksRetrivalTime time.Duration, // max duration of retrival operation
    32  	) ([][]byte, error)
    33  
    34  	BatchedParseBlock(ctx context.Context, blks [][]byte) ([]snowman.Block, error)
    35  }
    36  
    37  func GetAncestors(
    38  	ctx context.Context,
    39  	log logging.Logger,
    40  	vm Getter, // fetch blocks
    41  	blkID ids.ID, // first requested block
    42  	maxBlocksNum int, // max number of blocks to be retrieved
    43  	maxBlocksSize int, // max cumulated byte size of retrieved blocks
    44  	maxBlocksRetrivalTime time.Duration, // max duration of retrival operation
    45  ) ([][]byte, error) {
    46  	// Try and batch GetBlock requests
    47  	if vm, ok := vm.(BatchedChainVM); ok {
    48  		blocks, err := vm.GetAncestors(
    49  			ctx,
    50  			blkID,
    51  			maxBlocksNum,
    52  			maxBlocksSize,
    53  			maxBlocksRetrivalTime,
    54  		)
    55  		if err == nil {
    56  			return blocks, nil
    57  		}
    58  		if err != ErrRemoteVMNotImplemented {
    59  			return nil, err
    60  		}
    61  	}
    62  
    63  	// RemoteVM did not work, try local logic
    64  	startTime := time.Now()
    65  	blk, err := vm.GetBlock(ctx, blkID)
    66  	if err == database.ErrNotFound {
    67  		// Special case ErrNotFound as an empty response: this signals
    68  		// the client to avoid contacting this node for further ancestors
    69  		// as they may have been pruned or unavailable due to state-sync.
    70  		return nil, nil
    71  	} else if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	// First elt is byte repr. of [blk], then its parent, then grandparent, etc.
    76  	ancestorsBytes := make([][]byte, 1, maxBlocksNum)
    77  	ancestorsBytes[0] = blk.Bytes()
    78  	ancestorsBytesLen := len(blk.Bytes()) + wrappers.IntLen // length, in bytes, of all elements of ancestors
    79  
    80  	for numFetched := 1; numFetched < maxBlocksNum && time.Since(startTime) < maxBlocksRetrivalTime; numFetched++ {
    81  		parentID := blk.Parent()
    82  		blk, err = vm.GetBlock(ctx, parentID)
    83  		if err == database.ErrNotFound {
    84  			// After state sync we may not have the full chain
    85  			break
    86  		}
    87  		if err != nil {
    88  			log.Error("failed to get block during ancestors lookup",
    89  				zap.String("parentID", parentID.String()),
    90  				zap.Error(err),
    91  			)
    92  			break
    93  		}
    94  		blkBytes := blk.Bytes()
    95  		// Ensure response size isn't too large. Include wrappers.IntLen because
    96  		// the size of the message is included with each container, and the size
    97  		// is repr. by an int.
    98  		newLen := ancestorsBytesLen + len(blkBytes) + wrappers.IntLen
    99  		if newLen > maxBlocksSize {
   100  			// Reached maximum response size
   101  			break
   102  		}
   103  		ancestorsBytes = append(ancestorsBytes, blkBytes)
   104  		ancestorsBytesLen = newLen
   105  	}
   106  
   107  	return ancestorsBytes, nil
   108  }
   109  
   110  func BatchedParseBlock(
   111  	ctx context.Context,
   112  	vm Parser,
   113  	blks [][]byte,
   114  ) ([]snowman.Block, error) {
   115  	// Try and batch ParseBlock requests
   116  	if vm, ok := vm.(BatchedChainVM); ok {
   117  		blocks, err := vm.BatchedParseBlock(ctx, blks)
   118  		if err == nil {
   119  			return blocks, nil
   120  		}
   121  		if err != ErrRemoteVMNotImplemented {
   122  			return nil, err
   123  		}
   124  	}
   125  
   126  	// We couldn't batch the ParseBlock requests, try to parse them one at a
   127  	// time.
   128  	blocks := make([]snowman.Block, len(blks))
   129  	for i, blockBytes := range blks {
   130  		block, err := vm.ParseBlock(ctx, blockBytes)
   131  		if err != nil {
   132  			return nil, err
   133  		}
   134  		blocks[i] = block
   135  	}
   136  	return blocks, nil
   137  }