/
l2_client.go
167 lines (149 loc) · 6.13 KB
/
l2_client.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
package sources
import (
"context"
"fmt"
"strings"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/sliceledger-blockchain/slice-ledger/op-node/client"
"github.com/sliceledger-blockchain/slice-ledger/op-node/eth"
"github.com/sliceledger-blockchain/slice-ledger/op-node/rollup"
"github.com/sliceledger-blockchain/slice-ledger/op-node/rollup/derive"
"github.com/sliceledger-blockchain/slice-ledger/op-node/sources/caching"
)
type L2ClientConfig struct {
EthClientConfig
L2BlockRefsCacheSize int
L1ConfigsCacheSize int
RollupCfg *rollup.Config
}
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)
}
fullSpan := span
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,
RPCProviderKind: RPCKindBasic,
MethodResetDuration: time.Minute,
},
// Not bounded by span, to cover find-sync-start range fully for speedy recovery after errors.
L2BlockRefsCacheSize: fullSpan,
L1ConfigsCacheSize: span,
RollupCfg: config,
}
}
// L2Client extends EthClient with functions to fetch and cache eth.L2BlockRef values.
type L2Client struct {
*EthClient
rollupCfg *rollup.Config
// cache L2BlockRef by hash
// common.Hash -> eth.L2BlockRef
l2BlockRefsCache *caching.LRUCache
// cache SystemConfig by L2 hash
// common.Hash -> eth.SystemConfig
systemConfigsCache *caching.LRUCache
}
// NewL2Client constructs a new L2Client instance. The L2Client is a thin wrapper around the EthClient with added functions
// for fetching and caching eth.L2BlockRef values. This includes fetching an L2BlockRef by block number, label, or hash.
// See: [L2BlockRefByLabel], [L2BlockRefByNumber], [L2BlockRefByHash]
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,
rollupCfg: config.RollupCfg,
l2BlockRefsCache: caching.NewLRUCache(metrics, "blockrefs", config.L2BlockRefsCacheSize),
systemConfigsCache: caching.NewLRUCache(metrics, "systemconfigs", config.L1ConfigsCacheSize),
}, nil
}
// L2BlockRefByLabel returns the [eth.L2BlockRef] for the given block 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.rollupCfg.Genesis)
if err != nil {
return eth.L2BlockRef{}, err
}
s.l2BlockRefsCache.Add(ref.Hash, ref)
return ref, nil
}
// L2BlockRefByNumber returns the [eth.L2BlockRef] 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.rollupCfg.Genesis)
if err != nil {
return eth.L2BlockRef{}, err
}
s.l2BlockRefsCache.Add(ref.Hash, ref)
return ref, nil
}
// L2BlockRefByHash returns the [eth.L2BlockRef] 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.rollupCfg.Genesis)
if err != nil {
return eth.L2BlockRef{}, err
}
s.l2BlockRefsCache.Add(ref.Hash, ref)
return ref, nil
}
// SystemConfigByL2Hash returns the [eth.SystemConfig] (matching the config updates up to and including the L1 origin) for the given L2 block hash.
// The returned [eth.SystemConfig] may not be in the canonical chain when the hash is not canonical.
func (s *L2Client) SystemConfigByL2Hash(ctx context.Context, hash common.Hash) (eth.SystemConfig, error) {
if ref, ok := s.systemConfigsCache.Get(hash); ok {
return ref.(eth.SystemConfig), nil
}
payload, err := s.PayloadByHash(ctx, hash)
if err != nil {
// w%: wrap to preserve ethereum.NotFound case
return eth.SystemConfig{}, fmt.Errorf("failed to determine block-hash of hash %v, could not get payload: %w", hash, err)
}
cfg, err := derive.PayloadToSystemConfig(payload, s.rollupCfg)
if err != nil {
return eth.SystemConfig{}, err
}
s.systemConfigsCache.Add(hash, cfg)
return cfg, nil
}