-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathchain.go
More file actions
272 lines (215 loc) · 6.66 KB
/
chain.go
File metadata and controls
272 lines (215 loc) · 6.66 KB
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
package ethfinalizer
import (
"context"
"fmt"
"log/slog"
"math/big"
"sync"
"github.com/0xsequence/ethkit/ethgas"
"github.com/0xsequence/ethkit/ethmonitor"
"github.com/0xsequence/ethkit/ethrpc"
"github.com/0xsequence/ethkit/go-ethereum/common"
"github.com/0xsequence/ethkit/go-ethereum/core/types"
)
// Chain is a provider for a chain where transactions will be sent.
type Chain interface {
ChainID() *big.Int
IsEIP1559() bool
LatestNonce(ctx context.Context, address common.Address) (uint64, error)
PendingNonce(ctx context.Context, address common.Address) (uint64, error)
Send(ctx context.Context, transaction *types.Transaction) error
GasPrice(ctx context.Context) (*big.Int, error)
BaseFee(ctx context.Context) (*big.Int, error)
PriorityFee(ctx context.Context) (*big.Int, error)
Subscribe(ctx context.Context) (<-chan Diff, error)
}
type Diff struct {
Removed, Added map[common.Hash]struct{}
}
type GasGaugeSpeed int
const (
GasGaugeSpeedDefault GasGaugeSpeed = iota
GasGaugeSpeedSlow
GasGaugeSpeedStandard
GasGaugeSpeedFast
GasGaugeSpeedInstant
)
type EthkitChainOptions struct {
ChainID *big.Int
IsEIP1559 bool
// Provider is an ethkit Provider, required.
Provider *ethrpc.Provider
// Monitor is a running ethkit Monitor, required.
Monitor *ethmonitor.Monitor
// GasGauge is a running ethkit GasGauge, required only for non-EIP-1559 chains.
GasGauge *ethgas.GasGauge
// GasGaugeSpeed defaults to GasGaugeSpeedDefault (GasGaugeSpeedFast), unused for EIP-1559 chains.
GasGaugeSpeed GasGaugeSpeed
// Logger is used to log chain behaviour, optional.
Logger *slog.Logger
PriorityFee *big.Int
}
func (o EthkitChainOptions) IsValid() error {
if o.ChainID == nil {
return fmt.Errorf("no chain id")
} else if o.ChainID.Sign() <= 0 {
return fmt.Errorf("non-positive chain id %v", o.ChainID)
}
if o.Provider == nil {
return fmt.Errorf("no provider")
}
if o.Monitor == nil {
return fmt.Errorf("no monitor")
}
if !o.IsEIP1559 && o.GasGauge == nil {
return fmt.Errorf("no gas gauge")
}
if o.GasGaugeSpeed < GasGaugeSpeedDefault || o.GasGaugeSpeed > GasGaugeSpeedInstant {
return fmt.Errorf("invalid gas gauge speed %v", o.GasGaugeSpeed)
}
if o.PriorityFee != nil && o.PriorityFee.Sign() < 0 {
return fmt.Errorf("negative priority fee %v", o.PriorityFee)
}
return nil
}
type ethkitChain struct {
EthkitChainOptions
baseFee, priorityFee *big.Int
mu sync.Mutex
}
// NewEthkitChain creates a Chain using ethkit components.
func NewEthkitChain(options EthkitChainOptions) (Chain, error) {
if err := options.IsValid(); err != nil {
return nil, err
}
if options.Logger == nil {
options.Logger = slog.New(slog.DiscardHandler)
}
return ðkitChain{EthkitChainOptions: options}, nil
}
func (c *ethkitChain) ChainID() *big.Int {
return new(big.Int).Set(c.EthkitChainOptions.ChainID)
}
func (c *ethkitChain) IsEIP1559() bool {
return c.EthkitChainOptions.IsEIP1559
}
func (c *ethkitChain) LatestNonce(ctx context.Context, address common.Address) (uint64, error) {
return c.Provider.NonceAt(ctx, address, nil)
}
func (c *ethkitChain) PendingNonce(ctx context.Context, address common.Address) (uint64, error) {
return c.Provider.PendingNonceAt(ctx, address)
}
func (c *ethkitChain) Send(ctx context.Context, transaction *types.Transaction) error {
return c.Provider.SendTransaction(ctx, transaction)
}
func (c *ethkitChain) GasPrice(ctx context.Context) (*big.Int, error) {
switch c.GasGaugeSpeed {
case GasGaugeSpeedSlow:
return new(big.Int).Set(c.GasGauge.SuggestedGasPrice().SlowWei), nil
case GasGaugeSpeedStandard:
return new(big.Int).Set(c.GasGauge.SuggestedGasPrice().StandardWei), nil
case GasGaugeSpeedFast:
return new(big.Int).Set(c.GasGauge.SuggestedGasPrice().FastWei), nil
case GasGaugeSpeedInstant:
return new(big.Int).Set(c.GasGauge.SuggestedGasPrice().InstantWei), nil
default:
return new(big.Int).Set(c.GasGauge.SuggestedGasPrice().FastWei), nil
}
}
func (c *ethkitChain) BaseFee(ctx context.Context) (*big.Int, error) {
var baseFee, number *big.Int
block := c.Monitor.LatestBlock()
if block == nil || block.BaseFee() == nil {
block, err := c.Provider.BlockByNumber(ctx, nil)
if err != nil {
return nil, fmt.Errorf("unable to get latest block: %w", err)
}
baseFee = block.BaseFee()
if baseFee == nil {
return nil, fmt.Errorf("no base fee")
}
number = block.Number()
} else {
baseFee = new(big.Int).Set(block.BaseFee())
number = block.Number()
}
c.mu.Lock()
defer c.mu.Unlock()
if c.baseFee == nil || baseFee.Cmp(c.baseFee) != 0 {
c.Logger.DebugContext(ctx, "base fee", slog.String("baseFee", baseFee.String()), slog.String("block", number.String()))
c.baseFee = new(big.Int).Set(baseFee)
}
return baseFee, nil
}
func (c *ethkitChain) PriorityFee(ctx context.Context) (*big.Int, error) {
priorityFee := new(big.Int)
if c.EthkitChainOptions.PriorityFee != nil {
priorityFee.Set(c.EthkitChainOptions.PriorityFee)
}
c.mu.Lock()
defer c.mu.Unlock()
if c.priorityFee == nil || priorityFee.Cmp(c.priorityFee) != 0 {
c.Logger.DebugContext(ctx, "priority fee", slog.String("priorityFee", priorityFee.String()))
c.priorityFee = new(big.Int).Set(priorityFee)
}
return priorityFee, nil
}
func (c *ethkitChain) Subscribe(ctx context.Context) (<-chan Diff, error) {
diffs := make(chan Diff)
go func() {
defer close(diffs)
subscription := c.Monitor.Subscribe()
for {
select {
case <-ctx.Done():
subscription.Unsubscribe()
return
case <-subscription.Done():
return
case blocks, ok := <-subscription.Blocks():
if !ok {
return
}
diff := Diff{
Removed: map[common.Hash]struct{}{},
Added: map[common.Hash]struct{}{},
}
for _, block := range blocks {
switch block.Event {
case ethmonitor.Added:
c.Logger.DebugContext(
ctx,
"block mined",
slog.String("block", block.Hash().String()),
slog.String("number", block.Number().String()),
slog.Int("transactions", block.Transactions().Len()),
)
for _, transaction := range block.Transactions() {
diff.Added[transaction.Hash()] = struct{}{}
}
case ethmonitor.Removed:
c.Logger.DebugContext(
ctx,
"block reorged",
slog.String("block", block.Hash().String()),
slog.String("number", block.Number().String()),
slog.Int("transactions", block.Transactions().Len()),
)
for _, transaction := range block.Transactions() {
diff.Removed[transaction.Hash()] = struct{}{}
}
}
}
select {
case <-ctx.Done():
subscription.Unsubscribe()
return
case <-subscription.Done():
return
case diffs <- diff:
}
}
}
}()
return diffs, nil
}