Skip to content

Commit da05acd

Browse files
committed
polish Seth hooks and update docs
1 parent bf668e1 commit da05acd

File tree

9 files changed

+328
-56
lines changed

9 files changed

+328
-56
lines changed

book/src/libs/seth.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Reliable and debug-friendly Ethereum client
6565
- [x] CLI to manipulate test keys
6666
- [x] Simple manual gas price estimation
6767
- [ ] Fail over client logic
68-
- [ ] Decode collided event hashes
68+
- [x] Decode collided event hashes
6969
- [x] Tracing support (4byte)
7070
- [x] Tracing support (callTracer)
7171
- [ ] Tracing support (prestate)
@@ -262,7 +262,53 @@ if err != nil {
262262
log.Fatal(err)
263263
}
264264
```
265-
This can be useful if you already have a config, but want to modify it slightly. It can also be useful if you read TOML config with multiple `Networks` and you want to specify which one you want to use.
265+
This can be useful if you already have a config, but want to modify it slightly. This approach will only work if you pass it the full config, since it will not apply any defaults. It can also be useful if you read TOML config with multiple `Networks` and you want to specify which one you want to use. Although for that use case it's better to make use of the following approach:
266+
```go
267+
// assuming that "readSethNetworks" knows how to read the TOML file and convert it to []*seth.Network
268+
/* example content of networks.toml:
269+
[[networks]]
270+
name = "Anvil"
271+
dial_timeout = "1m"
272+
transaction_timeout = "30s"
273+
urls_secret = ["ws://localhost:8545"]
274+
transfer_gas_fee = 21_000
275+
gas_limit = 10_000_000
276+
# legacy transactions
277+
gas_price = 1_000_000_000
278+
# EIP-1559 transactions
279+
# disabled as it makes some of our tests fail
280+
# eip_1559_dynamic_fees = true
281+
gas_fee_cap = 1_000_000_000
282+
gas_tip_cap = 1_000_000_000
283+
284+
[[networks]]
285+
name = "Geth"
286+
dial_timeout = "1m"
287+
transaction_timeout = "30s"
288+
urls_secret = ["ws://localhost:8546"]
289+
transfer_gas_fee = 21_000
290+
gas_limit = 8_000_000
291+
# legacy transactions
292+
gas_price = 1_000_000_000
293+
# EIP-1559 transactions
294+
# disabled as it makes some of our tests fail
295+
# eip_1559_dynamic_fees = true
296+
gas_fee_cap = 10_000_000_000
297+
gas_tip_cap = 3_000_000_000
298+
*/
299+
networks, err := readSethNetworks("networks.toml")
300+
if err != nil {
301+
log.Fatal(err)
302+
}
303+
304+
client, err := NewClientBuilderWithConfig(&existingConfig).
305+
UseNetworkWithName("Anvil"). // or alternatively use UseNetworkWithChainId (if you defined it for you network)
306+
Build()
307+
308+
if err != nil {
309+
log.Fatal(err)
310+
}
311+
```
266312

267313
### Simulated Backend
268314

@@ -325,6 +371,37 @@ _ = client
325371
> to mine a new block and have your transactions processed. The best way to do it is having a goroutine running in the background
326372
> that either mines at specific intervals or when it receives a message on channel.
327373
374+
### Hooks
375+
Seth supports pre/post operation hooks for two functions:
376+
* `DeployContract()`
377+
* `Decode` (also `DecodeTx`)
378+
379+
As the name suggest each will be executed before and after mentioned operation. By default, no hooks are set. Adding hooks doesn't influence retry/gas bumping logic.
380+
381+
You can either set hooks directly on the `seth.Config` object (not recommended) or pass them to the `ClientBuilder`:
382+
```go
383+
// assuming backend is the simulated backend, to show how we can speed up contract deployments by calling `Commit()` right after deploying the contract
384+
hooks := seth.Hooks{
385+
ContractDeployment: seth.ContractDeploymentHooks{
386+
Post: func(client *seth.Client, tx *types.Transaction) error {
387+
backend.Commit()
388+
return nil
389+
},
390+
},
391+
}
392+
393+
client, err := builder.
394+
WithNetworkName("simulated").
395+
WithHooks(hooks).
396+
WithEthClient(backend.Client()).
397+
WithPrivateKeys([]string{"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"}).
398+
Build()
399+
400+
if err != nil {
401+
log.Fatal(err)
402+
}
403+
```
404+
328405
### Supported env vars
329406

330407
Some crucial data is stored in env vars, create `.envrc` and use `source .envrc`, or use `direnv`

seth/client.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const (
3939
// unused by Seth, but used by upstream
4040
ErrNoKeyLoaded = "failed to load private key"
4141

42+
ErrSethConfigIsNil = "seth config is nil"
43+
ErrNetworkIsNil = "no Network is set in the Seth config"
44+
ErrNonceManagerConfigIsNil = "nonce manager config is nil"
4245
ErrReadOnlyWithPrivateKeys = "read-only mode is enabled, but you tried to load private keys"
4346
ErrReadOnlyEphemeralKeys = "ephemeral mode is not supported in read-only mode"
4447
ErrReadOnlyGasBumping = "gas bumping is not supported in read-only mode"
@@ -86,9 +89,8 @@ func NewClientWithConfig(cfg *Config) (*Client, error) {
8689
initDefaultLogging()
8790

8891
if cfg == nil {
89-
return nil, errors.New("seth config cannot be nil")
92+
return nil, errors.New(ErrSethConfigIsNil)
9093
}
91-
9294
if cfgErr := cfg.Validate(); cfgErr != nil {
9395
return nil, cfgErr
9496
}
@@ -179,6 +181,12 @@ func NewClientRaw(
179181
pkeys []*ecdsa.PrivateKey,
180182
opts ...ClientOpt,
181183
) (*Client, error) {
184+
if cfg == nil {
185+
return nil, errors.New(ErrSethConfigIsNil)
186+
}
187+
if cfgErr := cfg.Validate(); cfgErr != nil {
188+
return nil, cfgErr
189+
}
182190
if cfg.ReadOnly && (len(addrs) > 0 || len(pkeys) > 0) {
183191
return nil, errors.New(ErrReadOnlyWithPrivateKeys)
184192
}

seth/client_builder.go

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
)
1010

1111
const (
12+
NoNetworkErr = "you need to set the Network"
1213
NoPkForRpcHealthCheckErr = "you need to provide at least one private key to check the RPC health"
1314
NoPkForNonceProtection = "you need to provide at least one private key to enable nonce protection"
1415
NoPkForEphemeralKeys = "you need to provide at least one private key to generate and fund ephemeral addresses"
@@ -94,14 +95,6 @@ func (c *ClientBuilder) UseNetworkWithName(name string) *ClientBuilder {
9495
}
9596
}
9697

97-
// if the network is not found, we will try to use the default network
98-
for _, network := range c.config.Networks {
99-
if network.Name == DefaultNetworkName {
100-
c.config.Network = network
101-
return c
102-
}
103-
}
104-
10598
c.errors = append(c.errors, fmt.Errorf("network with name '%s' not found", name))
10699
return c
107100
}
@@ -116,14 +109,6 @@ func (c *ClientBuilder) UseNetworkWithChainId(chainId uint64) *ClientBuilder {
116109
}
117110
}
118111

119-
// if the network is not found, we will try to use the default network
120-
for _, network := range c.config.Networks {
121-
if network.Name == DefaultNetworkName {
122-
c.config.Network = network
123-
return c
124-
}
125-
}
126-
127112
c.errors = append(c.errors, fmt.Errorf("network with chainId '%d' not found", chainId))
128113
return c
129114
}
@@ -144,6 +129,14 @@ func (c *ClientBuilder) WithPrivateKeys(pks []string) *ClientBuilder {
144129
return c
145130
}
146131

132+
// WithNetworks sets the networks for the config. At least one is required to build a valid config.
133+
// It overrides the default network.
134+
// In order to use one of providers networks, you need to call `UseNetworkWithName(network-name)` or `UseNetworkWithChainId(networks-chain-id)` after calling this method.
135+
func (c *ClientBuilder) WithNetworks(networks []*Network) *ClientBuilder {
136+
c.config.Networks = networks
137+
return c
138+
}
139+
147140
// WithNetworkName sets the network name, useful mostly for debugging and logging.
148141
// Default value is "default".
149142
func (c *ClientBuilder) WithNetworkName(name string) *ClientBuilder {
@@ -425,22 +418,25 @@ func (c *ClientBuilder) handleReadOnlyMode() {
425418
}
426419

427420
func (c *ClientBuilder) validateConfig() {
428-
if c.config.Network != nil {
429-
if len(c.config.Network.PrivateKeys) == 0 && c.config.CheckRpcHealthOnStart {
430-
c.errors = append(c.errors, errors.New(NoPkForRpcHealthCheckErr))
431-
}
432-
if len(c.config.Network.PrivateKeys) == 0 && c.config.PendingNonceProtectionEnabled {
433-
c.errors = append(c.errors, errors.New(NoPkForNonceProtection))
434-
}
435-
if len(c.config.Network.PrivateKeys) == 0 && c.config.EphemeralAddrs != nil && *c.config.EphemeralAddrs > 0 {
436-
c.errors = append(c.errors, errors.New(NoPkForEphemeralKeys))
437-
}
438-
if len(c.config.Network.PrivateKeys) == 0 && c.config.Network.GasPriceEstimationEnabled {
439-
c.errors = append(c.errors, errors.New(NoPkForGasPriceEstimation))
440-
}
441-
if len(c.config.Network.URLs) > 0 && c.config.ethclient != nil {
442-
c.errors = append(c.errors, errors.New(EthClientAndUrlsSet))
443-
}
421+
if c.config.Network == nil {
422+
c.errors = append(c.errors, errors.New(NoNetworkErr))
423+
return
424+
}
425+
426+
if len(c.config.Network.PrivateKeys) == 0 && c.config.CheckRpcHealthOnStart {
427+
c.errors = append(c.errors, errors.New(NoPkForRpcHealthCheckErr))
428+
}
429+
if len(c.config.Network.PrivateKeys) == 0 && c.config.PendingNonceProtectionEnabled {
430+
c.errors = append(c.errors, errors.New(NoPkForNonceProtection))
431+
}
432+
if len(c.config.Network.PrivateKeys) == 0 && c.config.EphemeralAddrs != nil && *c.config.EphemeralAddrs > 0 {
433+
c.errors = append(c.errors, errors.New(NoPkForEphemeralKeys))
434+
}
435+
if len(c.config.Network.PrivateKeys) == 0 && c.config.Network.GasPriceEstimationEnabled {
436+
c.errors = append(c.errors, errors.New(NoPkForGasPriceEstimation))
437+
}
438+
if len(c.config.Network.URLs) > 0 && c.config.ethclient != nil {
439+
c.errors = append(c.errors, errors.New(EthClientAndUrlsSet))
444440
}
445441
}
446442

0 commit comments

Comments
 (0)