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 }