-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
prefetcher.go
303 lines (280 loc) · 10.7 KB
/
prefetcher.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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package prefetcher
import (
"context"
"encoding/binary"
"errors"
"fmt"
"slices"
"strings"
preimage "github.com/ethereum-optimism/optimism/op-preimage"
"github.com/ethereum-optimism/optimism/op-program/client/l1"
"github.com/ethereum-optimism/optimism/op-program/client/l2"
"github.com/ethereum-optimism/optimism/op-program/client/mpt"
"github.com/ethereum-optimism/optimism/op-program/host/kvstore"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
var (
precompileSuccess = [1]byte{1}
precompileFailure = [1]byte{0}
)
var acceleratedPrecompiles = []common.Address{
common.BytesToAddress([]byte{0x1}), // ecrecover
common.BytesToAddress([]byte{0x8}), // bn256Pairing
common.BytesToAddress([]byte{0x0a}), // KZG Point Evaluation
}
type L1Source interface {
InfoByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, error)
InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error)
FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error)
}
type L1BlobSource interface {
GetBlobSidecars(ctx context.Context, ref eth.L1BlockRef, hashes []eth.IndexedBlobHash) ([]*eth.BlobSidecar, error)
GetBlobs(ctx context.Context, ref eth.L1BlockRef, hashes []eth.IndexedBlobHash) ([]*eth.Blob, error)
}
type L2Source interface {
InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error)
NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error)
CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error)
OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error)
}
type Prefetcher struct {
logger log.Logger
l1Fetcher L1Source
l1BlobFetcher L1BlobSource
l2Fetcher L2Source
lastHint string
kvStore kvstore.KV
}
func NewPrefetcher(logger log.Logger, l1Fetcher L1Source, l1BlobFetcher L1BlobSource, l2Fetcher L2Source, kvStore kvstore.KV) *Prefetcher {
return &Prefetcher{
logger: logger,
l1Fetcher: NewRetryingL1Source(logger, l1Fetcher),
l1BlobFetcher: NewRetryingL1BlobSource(logger, l1BlobFetcher),
l2Fetcher: NewRetryingL2Source(logger, l2Fetcher),
kvStore: kvStore,
}
}
func (p *Prefetcher) Hint(hint string) error {
p.logger.Trace("Received hint", "hint", hint)
p.lastHint = hint
return nil
}
func (p *Prefetcher) GetPreimage(ctx context.Context, key common.Hash) ([]byte, error) {
p.logger.Trace("Pre-image requested", "key", key)
pre, err := p.kvStore.Get(key)
// Use a loop to keep retrying the prefetch as long as the key is not found
// This handles the case where the prefetch downloads a preimage, but it is then deleted unexpectedly
// before we get to read it.
for errors.Is(err, kvstore.ErrNotFound) && p.lastHint != "" {
hint := p.lastHint
if err := p.prefetch(ctx, hint); err != nil {
return nil, fmt.Errorf("prefetch failed: %w", err)
}
pre, err = p.kvStore.Get(key)
if err != nil {
p.logger.Error("Fetched pre-images for last hint but did not find required key", "hint", hint, "key", key)
}
}
return pre, err
}
func (p *Prefetcher) prefetch(ctx context.Context, hint string) error {
hintType, hintBytes, err := parseHint(hint)
if err != nil {
return err
}
p.logger.Debug("Prefetching", "type", hintType, "bytes", hexutil.Bytes(hintBytes))
switch hintType {
case l1.HintL1BlockHeader:
if len(hintBytes) != 32 {
return fmt.Errorf("invalid L1 block hint: %x", hint)
}
hash := common.Hash(hintBytes)
header, err := p.l1Fetcher.InfoByHash(ctx, hash)
if err != nil {
return fmt.Errorf("failed to fetch L1 block %s header: %w", hash, err)
}
data, err := header.HeaderRLP()
if err != nil {
return fmt.Errorf("marshall header: %w", err)
}
return p.kvStore.Put(preimage.Keccak256Key(hash).PreimageKey(), data)
case l1.HintL1Transactions:
if len(hintBytes) != 32 {
return fmt.Errorf("invalid L1 transactions hint: %x", hint)
}
hash := common.Hash(hintBytes)
_, txs, err := p.l1Fetcher.InfoAndTxsByHash(ctx, hash)
if err != nil {
return fmt.Errorf("failed to fetch L1 block %s txs: %w", hash, err)
}
return p.storeTransactions(txs)
case l1.HintL1Receipts:
if len(hintBytes) != 32 {
return fmt.Errorf("invalid L1 receipts hint: %x", hint)
}
hash := common.Hash(hintBytes)
_, receipts, err := p.l1Fetcher.FetchReceipts(ctx, hash)
if err != nil {
return fmt.Errorf("failed to fetch L1 block %s receipts: %w", hash, err)
}
return p.storeReceipts(receipts)
case l1.HintL1Blob:
if len(hintBytes) != 48 {
return fmt.Errorf("invalid blob hint: %x", hint)
}
blobVersionHash := common.Hash(hintBytes[:32])
blobHashIndex := binary.BigEndian.Uint64(hintBytes[32:40])
refTimestamp := binary.BigEndian.Uint64(hintBytes[40:48])
// Fetch the blob sidecar for the indexed blob hash passed in the hint.
indexedBlobHash := eth.IndexedBlobHash{
Hash: blobVersionHash,
Index: blobHashIndex,
}
// We pass an `eth.L1BlockRef`, but `GetBlobSidecars` only uses the timestamp, which we received in the hint.
sidecars, err := p.l1BlobFetcher.GetBlobSidecars(ctx, eth.L1BlockRef{Time: refTimestamp}, []eth.IndexedBlobHash{indexedBlobHash})
if err != nil || len(sidecars) != 1 {
return fmt.Errorf("failed to fetch blob sidecars for %s %d: %w", blobVersionHash, blobHashIndex, err)
}
sidecar := sidecars[0]
// Put the preimage for the versioned hash into the kv store
if err = p.kvStore.Put(preimage.Sha256Key(blobVersionHash).PreimageKey(), sidecar.KZGCommitment[:]); err != nil {
return err
}
// Put all of the blob's field elements into the kv store. There should be 4096. The preimage oracle key for
// each field element is the keccak256 hash of `abi.encodePacked(sidecar.KZGCommitment, uint256(i))`
blobKey := make([]byte, 80)
copy(blobKey[:48], sidecar.KZGCommitment[:])
for i := 0; i < params.BlobTxFieldElementsPerBlob; i++ {
binary.BigEndian.PutUint64(blobKey[72:], uint64(i))
blobKeyHash := crypto.Keccak256Hash(blobKey)
if err := p.kvStore.Put(preimage.Keccak256Key(blobKeyHash).PreimageKey(), blobKey); err != nil {
return err
}
if err = p.kvStore.Put(preimage.BlobKey(blobKeyHash).PreimageKey(), sidecar.Blob[i<<5:(i+1)<<5]); err != nil {
return err
}
}
return nil
case l1.HintL1Precompile:
if len(hintBytes) < 20 {
return fmt.Errorf("invalid precompile hint: %x", hint)
}
precompileAddress := common.BytesToAddress(hintBytes[:20])
// For extra safety, avoid accelerating unexpected precompiles
if !slices.Contains(acceleratedPrecompiles, precompileAddress) {
return fmt.Errorf("unsupported precompile address: %s", precompileAddress)
}
// NOTE: We use the precompiled contracts from Cancun because it's the only set that contains the addresses of all accelerated precompiles
// We assume the precompile Run function behavior does not change across EVM upgrades.
// As such, we must not rely on upgrade-specific behavior such as precompile.RequiredGas.
precompile := getPrecompiledContract(precompileAddress)
// KZG Point Evaluation precompile also verifies its input
result, err := precompile.Run(hintBytes[20:])
if err == nil {
result = append(precompileSuccess[:], result...)
} else {
result = append(precompileFailure[:], result...)
}
inputHash := crypto.Keccak256Hash(hintBytes)
// Put the input preimage so it can be loaded later
if err := p.kvStore.Put(preimage.Keccak256Key(inputHash).PreimageKey(), hintBytes); err != nil {
return err
}
return p.kvStore.Put(preimage.PrecompileKey(inputHash).PreimageKey(), result)
case l2.HintL2BlockHeader, l2.HintL2Transactions:
if len(hintBytes) != 32 {
return fmt.Errorf("invalid L2 header/tx hint: %x", hint)
}
hash := common.Hash(hintBytes)
header, txs, err := p.l2Fetcher.InfoAndTxsByHash(ctx, hash)
if err != nil {
return fmt.Errorf("failed to fetch L2 block %s: %w", hash, err)
}
data, err := header.HeaderRLP()
if err != nil {
return fmt.Errorf("failed to encode header to RLP: %w", err)
}
err = p.kvStore.Put(preimage.Keccak256Key(hash).PreimageKey(), data)
if err != nil {
return err
}
return p.storeTransactions(txs)
case l2.HintL2StateNode:
if len(hintBytes) != 32 {
return fmt.Errorf("invalid L2 state node hint: %x", hint)
}
hash := common.Hash(hintBytes)
node, err := p.l2Fetcher.NodeByHash(ctx, hash)
if err != nil {
return fmt.Errorf("failed to fetch L2 state node %s: %w", hash, err)
}
return p.kvStore.Put(preimage.Keccak256Key(hash).PreimageKey(), node)
case l2.HintL2Code:
if len(hintBytes) != 32 {
return fmt.Errorf("invalid L2 code hint: %x", hint)
}
hash := common.Hash(hintBytes)
code, err := p.l2Fetcher.CodeByHash(ctx, hash)
if err != nil {
return fmt.Errorf("failed to fetch L2 contract code %s: %w", hash, err)
}
return p.kvStore.Put(preimage.Keccak256Key(hash).PreimageKey(), code)
case l2.HintL2Output:
if len(hintBytes) != 32 {
return fmt.Errorf("invalid L2 output hint: %x", hint)
}
hash := common.Hash(hintBytes)
output, err := p.l2Fetcher.OutputByRoot(ctx, hash)
if err != nil {
return fmt.Errorf("failed to fetch L2 output root %s: %w", hash, err)
}
return p.kvStore.Put(preimage.Keccak256Key(hash).PreimageKey(), output.Marshal())
}
return fmt.Errorf("unknown hint type: %v", hintType)
}
func (p *Prefetcher) storeReceipts(receipts types.Receipts) error {
opaqueReceipts, err := eth.EncodeReceipts(receipts)
if err != nil {
return err
}
return p.storeTrieNodes(opaqueReceipts)
}
func (p *Prefetcher) storeTransactions(txs types.Transactions) error {
opaqueTxs, err := eth.EncodeTransactions(txs)
if err != nil {
return err
}
return p.storeTrieNodes(opaqueTxs)
}
func (p *Prefetcher) storeTrieNodes(values []hexutil.Bytes) error {
_, nodes := mpt.WriteTrie(values)
for _, node := range nodes {
key := preimage.Keccak256Key(crypto.Keccak256Hash(node)).PreimageKey()
if err := p.kvStore.Put(key, node); err != nil {
return fmt.Errorf("failed to store node: %w", err)
}
}
return nil
}
// parseHint parses a hint string in wire protocol. Returns the hint type, requested hash and error (if any).
func parseHint(hint string) (string, []byte, error) {
hintType, bytesStr, found := strings.Cut(hint, " ")
if !found {
return "", nil, fmt.Errorf("unsupported hint: %s", hint)
}
hintBytes, err := hexutil.Decode(bytesStr)
if err != nil {
return "", make([]byte, 0), fmt.Errorf("invalid bytes: %s", bytesStr)
}
return hintType, hintBytes, nil
}
func getPrecompiledContract(address common.Address) vm.PrecompiledContract {
return vm.PrecompiledContractsCancun[address]
}