-
Notifications
You must be signed in to change notification settings - Fork 670
/
batched_vm.go
114 lines (101 loc) · 3.47 KB
/
batched_vm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright (C) 2019-2021, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package block
import (
"errors"
"time"
"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/consensus/snowman"
"github.com/ava-labs/avalanchego/utils/wrappers"
)
var ErrRemoteVMNotImplemented = errors.New("vm does not implement RemoteVM interface")
// BatchedChainVM extends the minimal functionalities exposed by ChainVM for VMs
// communicating over network (gRPC in our case). This allows more efficient
// operations since calls over network can be duly batched
type BatchedChainVM interface {
GetAncestors(
blkID ids.ID, // first requested block
maxBlocksNum int, // max number of blocks to be retrieved
maxBlocksSize int, // max cumulated byte size of retrieved blocks
maxBlocksRetrivalTime time.Duration, // max duration of retrival operation
) ([][]byte, error)
BatchedParseBlock(blks [][]byte) ([]snowman.Block, error)
}
func GetAncestors(
vm Getter, // fetch blocks
blkID ids.ID, // first requested block
maxBlocksNum int, // max number of blocks to be retrieved
maxBlocksSize int, // max cumulated byte size of retrieved blocks
maxBlocksRetrivalTime time.Duration, // max duration of retrival operation
) ([][]byte, error) {
// Try and batch GetBlock requests
if vm, ok := vm.(BatchedChainVM); ok {
blocks, err := vm.GetAncestors(
blkID,
maxBlocksNum,
maxBlocksSize,
maxBlocksRetrivalTime,
)
if err == nil {
return blocks, nil
}
if err != ErrRemoteVMNotImplemented {
return nil, err
}
}
// RemoteVM did not work, try local logic
startTime := time.Now()
blk, err := vm.GetBlock(blkID)
if err == database.ErrNotFound {
// special case ErrNotFound as an empty response: this signals
// the client to avoid contacting this node for further ancestors
// as they may have been pruned or unavailable due to state-sync.
return nil, nil
} else if err != nil {
return nil, err
}
// First elt is byte repr. of [blk], then its parent, then grandparent, etc.
ancestorsBytes := make([][]byte, 1, maxBlocksNum)
ancestorsBytes[0] = blk.Bytes()
ancestorsBytesLen := len(blk.Bytes()) + wrappers.IntLen // length, in bytes, of all elements of ancestors
for numFetched := 1; numFetched < maxBlocksNum && time.Since(startTime) < maxBlocksRetrivalTime; numFetched++ {
if blk, err = vm.GetBlock(blk.Parent()); err != nil {
break
}
blkBytes := blk.Bytes()
// Ensure response size isn't too large. Include wrappers.IntLen because
// the size of the message is included with each container, and the size
// is repr. by an int.
if newLen := ancestorsBytesLen + len(blkBytes) + wrappers.IntLen; newLen <= maxBlocksSize {
ancestorsBytes = append(ancestorsBytes, blkBytes)
ancestorsBytesLen = newLen
} else { // reached maximum response size
break
}
}
return ancestorsBytes, nil
}
func BatchedParseBlock(vm Parser, blks [][]byte) ([]snowman.Block, error) {
// Try and batch ParseBlock requests
if vm, ok := vm.(BatchedChainVM); ok {
blocks, err := vm.BatchedParseBlock(blks)
if err == nil {
return blocks, nil
}
if err != ErrRemoteVMNotImplemented {
return nil, err
}
}
// We couldn't batch the ParseBlock requests, try to parse them one at a
// time.
blocks := make([]snowman.Block, len(blks))
for i, blockBytes := range blks {
block, err := vm.ParseBlock(blockBytes)
if err != nil {
return nil, err
}
blocks[i] = block
}
return blocks, nil
}