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
package sources
import (
"context"
"fmt"
"strings"
"github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/sources/caching"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
type L2ClientConfig struct {
EthClientConfig
L2BlockRefsCacheSize int
Genesis rollup.Genesis
}
func L2ClientDefaultConfig(config *rollup.Config, trustRPC bool) *L2ClientConfig {
// Cache 3/2 worth of sequencing window of payloads, block references, receipts and txs
span := int(config.SeqWindowSize) * 3 / 2
// Estimate number of L2 blocks in this span of L1 blocks
// (there's always one L2 block per L1 block, L1 is thus the minimum, even if block time is very high)
if config.BlockTime < 12 && config.BlockTime > 0 {
span *= 12
span /= int(config.BlockTime)
}
if span > 1000 { // sanity cap. If a large sequencing window is configured, do not make the cache too large
span = 1000
}
return &L2ClientConfig{
EthClientConfig: EthClientConfig{
// receipts and transactions are cached per block
ReceiptsCacheSize: span,
TransactionsCacheSize: span,
HeadersCacheSize: span,
PayloadsCacheSize: span,
MaxRequestsPerBatch: 20, // TODO: tune batch param
MaxConcurrentRequests: 10,
TrustRPC: trustRPC,
MustBePostMerge: true,
},
L2BlockRefsCacheSize: span,
Genesis: config.Genesis,
}
}
// L2Client extends EthClient with functions to fetch and cache eth.L2BlockRef values.
type L2Client struct {
*EthClient
genesis *rollup.Genesis
// cache L2BlockRef by hash
// common.Hash -> eth.L2BlockRef
l2BlockRefsCache *caching.LRUCache
}
func NewL2Client(client client.RPC, log log.Logger, metrics caching.Metrics, config *L2ClientConfig) (*L2Client, error) {
ethClient, err := NewEthClient(client, log, metrics, &config.EthClientConfig)
if err != nil {
return nil, err
}
return &L2Client{
EthClient: ethClient,
genesis: &config.Genesis,
l2BlockRefsCache: caching.NewLRUCache(metrics, "blockrefs", config.L2BlockRefsCacheSize),
}, nil
}
// L2BlockRefByLabel returns the L2 block reference for the given label.
func (s *L2Client) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) {
payload, err := s.PayloadByLabel(ctx, label)
if err != nil {
// Both geth and erigon like to serve non-standard errors for the safe and finalized heads, correct that.
// This happens when the chain just started and nothing is marked as safe/finalized yet.
if strings.Contains(err.Error(), "block not found") || strings.Contains(err.Error(), "Unknown block") {
err = ethereum.NotFound
}
// w%: wrap to preserve ethereum.NotFound case
return eth.L2BlockRef{}, fmt.Errorf("failed to determine L2BlockRef of %s, could not get payload: %w", label, err)
}
ref, err := derive.PayloadToBlockRef(payload, s.genesis)
if err != nil {
return eth.L2BlockRef{}, err
}
s.l2BlockRefsCache.Add(ref.Hash, ref)
return ref, nil
}
// L2BlockRefByNumber returns the L2 block reference for the given block number.
func (s *L2Client) L2BlockRefByNumber(ctx context.Context, num uint64) (eth.L2BlockRef, error) {
payload, err := s.PayloadByNumber(ctx, num)
if err != nil {
// w%: wrap to preserve ethereum.NotFound case
return eth.L2BlockRef{}, fmt.Errorf("failed to determine L2BlockRef of height %v, could not get payload: %w", num, err)
}
ref, err := derive.PayloadToBlockRef(payload, s.genesis)
if err != nil {
return eth.L2BlockRef{}, err
}
s.l2BlockRefsCache.Add(ref.Hash, ref)
return ref, nil
}
// L2BlockRefByHash returns the L2 block reference for the given block hash.
// The returned BlockRef may not be in the canonical chain.
func (s *L2Client) L2BlockRefByHash(ctx context.Context, hash common.Hash) (eth.L2BlockRef, error) {
if ref, ok := s.l2BlockRefsCache.Get(hash); ok {
return ref.(eth.L2BlockRef), nil
}
payload, err := s.PayloadByHash(ctx, hash)
if err != nil {
// w%: wrap to preserve ethereum.NotFound case
return eth.L2BlockRef{}, fmt.Errorf("failed to determine block-hash of hash %v, could not get payload: %w", hash, err)
}
ref, err := derive.PayloadToBlockRef(payload, s.genesis)
if err != nil {
return eth.L2BlockRef{}, err
}
s.l2BlockRefsCache.Add(ref.Hash, ref)
return ref, nil
}