Skip to content

Commit

Permalink
cmd: beacon node API timeouts
Browse files Browse the repository at this point in the history
Add two CLI flags for `run`:
 - `--beacon-node-timeout`, which allows users to specify a custom HTTP timeout used for beacon node API calls
 - `--beacon-node-submit-timeout`, that allows users to specify a custom HTTP timeout used for submission beacon node API calls, like block and attestation submission.

Both flag default to the previous 2 seconds timeout.
  • Loading branch information
gsora committed Jul 4, 2024
1 parent 82d6f70 commit 3e8d7e0
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 82 deletions.
60 changes: 38 additions & 22 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ import (
"github.com/obolnetwork/charon/testutil/beaconmock" // Allow testutil
)

const eth2ClientTimeout = time.Second * 2

type Config struct {
P2P p2p.Config
Log log.Config
Expand All @@ -80,6 +78,8 @@ type Config struct {
DebugAddr string
ValidatorAPIAddr string
BeaconNodeAddrs []string
BeaconNodeTimeout time.Duration
BeaconNodeSubmitTimeout time.Duration
JaegerAddr string
JaegerService string
SimnetBMock bool
Expand Down Expand Up @@ -225,7 +225,7 @@ func Run(ctx context.Context, conf Config) (err error) {

initStartupMetrics(p2p.PeerName(tcpNode.ID()), int(cluster.Threshold), len(cluster.Operators), len(cluster.Validators), network)

eth2Cl, err := newETH2Client(ctx, conf, life, cluster, cluster.ForkVersion)
eth2Cl, subEth2Cl, err := newETH2Client(ctx, conf, life, cluster, cluster.GetForkVersion(), conf.BeaconNodeTimeout, conf.BeaconNodeSubmitTimeout)

Check warning on line 228 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L228

Added line #L228 was not covered by tests
if err != nil {
return err
}
Expand Down Expand Up @@ -271,7 +271,7 @@ func Run(ctx context.Context, conf Config) (err error) {
wireMonitoringAPI(ctx, life, conf.MonitoringAddr, conf.DebugAddr, tcpNode, eth2Cl, peerIDs,
promRegistry, qbftDebug, pubkeys, seenPubkeys, vapiCalls, len(cluster.GetValidators()))

err = wireCoreWorkflow(ctx, life, conf, cluster, nodeIdx, tcpNode, p2pKey, eth2Cl,
err = wireCoreWorkflow(ctx, life, conf, cluster, nodeIdx, tcpNode, p2pKey, eth2Cl, subEth2Cl,

Check warning on line 274 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L274

Added line #L274 was not covered by tests
peerIDs, sender, qbftDebug.AddInstance, seenPubkeysFunc, vapiCallsFunc)
if err != nil {
return err
Expand Down Expand Up @@ -343,7 +343,7 @@ func wireP2P(ctx context.Context, life *lifecycle.Manager, conf Config,
// wireCoreWorkflow wires the core workflow components.
func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,
cluster *manifestpb.Cluster, nodeIdx cluster.NodeIdx, tcpNode host.Host, p2pKey *k1.PrivateKey,
eth2Cl eth2wrap.Client, peerIDs []peer.ID, sender *p2p.Sender,
eth2Cl, submissionEth2Cl eth2wrap.Client, peerIDs []peer.ID, sender *p2p.Sender,
qbftSniffer func(*pbv1.SniffedConsensusInstance), seenPubkeys func(core.PubKey),
vapiCalls func(),
) error {
Expand Down Expand Up @@ -509,7 +509,7 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,
aggSigDB = aggsigdb.NewMemDB(deadlinerFunc("aggsigdb"))
}

broadcaster, err := bcast.New(ctx, eth2Cl)
broadcaster, err := bcast.New(ctx, submissionEth2Cl)

Check warning on line 512 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L512

Added line #L512 was not covered by tests
if err != nil {
return err
}
Expand Down Expand Up @@ -776,14 +776,12 @@ func eth2PubKeys(cluster *manifestpb.Cluster) ([]eth2p0.BLSPubKey, error) {
return pubkeys, nil
}

// newETH2Client returns a new eth2client; it is either a beaconmock for
// newETH2Client returns a new eth2client for the configured timeouts; it is either a beaconmock for
// simnet or a multi http client to a real beacon node.
func newETH2Client(ctx context.Context, conf Config, life *lifecycle.Manager,
cluster *manifestpb.Cluster, forkVersion []byte,
) (eth2wrap.Client, error) {
func newETH2Client(ctx context.Context, conf Config, life *lifecycle.Manager, cluster *manifestpb.Cluster, forkVersion []byte, bnTimeout time.Duration, submissionBnTimeout time.Duration) (eth2wrap.Client, eth2wrap.Client, error) {

Check warning on line 781 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L781

Added line #L781 was not covered by tests
pubkeys, err := eth2PubKeys(cluster)
if err != nil {
return nil, err
return nil, nil, err

Check warning on line 784 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L784

Added line #L784 was not covered by tests
}

// Default to 1s slot duration if not set.
Expand All @@ -795,23 +793,23 @@ func newETH2Client(ctx context.Context, conf Config, life *lifecycle.Manager,
log.Info(ctx, "Beaconmock fuzz configured!")
bmock, err := beaconmock.New(beaconmock.WithBeaconMockFuzzer(), beaconmock.WithForkVersion([4]byte(forkVersion)))
if err != nil {
return nil, err
return nil, nil, err

Check warning on line 796 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L796

Added line #L796 was not covered by tests
}

wrap, err := eth2wrap.Instrument(bmock)
if err != nil {
return nil, err
return nil, nil, err

Check warning on line 801 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L801

Added line #L801 was not covered by tests
}

life.RegisterStop(lifecycle.StopBeaconMock, lifecycle.HookFuncErr(bmock.Close))

return wrap, nil
return wrap, nil, nil

Check warning on line 806 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L806

Added line #L806 was not covered by tests
}

if conf.SimnetBMock { // Configure the beacon mock.
genesisTime, err := eth2util.ForkVersionToGenesisTime(forkVersion)
if err != nil {
return nil, err
return nil, nil, err

Check warning on line 812 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L812

Added line #L812 was not covered by tests
}

const dutyFactor = 100 // Duty factor spreads duties deterministically in an epoch.
Expand All @@ -828,12 +826,12 @@ func newETH2Client(ctx context.Context, conf Config, life *lifecycle.Manager,
opts = append(opts, conf.TestConfig.SimnetBMockOpts...)
bmock, err := beaconmock.New(opts...)
if err != nil {
return nil, err
return nil, nil, err

Check warning on line 829 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L829

Added line #L829 was not covered by tests
}

wrap, err := eth2wrap.Instrument(bmock)
if err != nil {
return nil, err
return nil, nil, err

Check warning on line 834 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L834

Added line #L834 was not covered by tests
}

if conf.SyntheticBlockProposals {
Expand All @@ -843,20 +841,38 @@ func newETH2Client(ctx context.Context, conf Config, life *lifecycle.Manager,

life.RegisterStop(lifecycle.StopBeaconMock, lifecycle.HookFuncErr(bmock.Close))

return wrap, nil
return wrap, nil, nil

Check warning on line 844 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L844

Added line #L844 was not covered by tests
}

if len(conf.BeaconNodeAddrs) == 0 {
return nil, errors.New("beacon node endpoints empty")
return nil, nil, errors.New("beacon node endpoints empty")
}

Check warning on line 849 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L848-L849

Added lines #L848 - L849 were not covered by tests

if conf.SyntheticBlockProposals {
log.Info(ctx, "Synthetic block proposals enabled")
}

Check warning on line 853 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L851-L853

Added lines #L851 - L853 were not covered by tests

eth2Cl, err := configureEth2Client(ctx, forkVersion, conf.BeaconNodeAddrs, bnTimeout, conf.SyntheticBlockProposals)
if err != nil {
return nil, nil, errors.Wrap(err, "new eth2 http client")

Check warning on line 857 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L855-L857

Added lines #L855 - L857 were not covered by tests
}

eth2Cl, err := eth2wrap.NewMultiHTTP(eth2ClientTimeout, [4]byte(forkVersion), conf.BeaconNodeAddrs...)
submissionEth2Cl, err := configureEth2Client(ctx, forkVersion, conf.BeaconNodeAddrs, submissionBnTimeout, conf.SyntheticBlockProposals)
if err != nil {
return nil, nil, errors.Wrap(err, "new submission eth2 http client")
}

Check warning on line 863 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L860-L863

Added lines #L860 - L863 were not covered by tests

return eth2Cl, submissionEth2Cl, nil

Check warning on line 865 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L865

Added line #L865 was not covered by tests
}

// configureEth2Client configures a beacon node client with the provided settings.
func configureEth2Client(ctx context.Context, forkVersion []byte, addrs []string, timeout time.Duration, syntheticBlockProposals bool) (eth2wrap.Client, error) {
eth2Cl, err := eth2wrap.NewMultiHTTP(timeout, [4]byte(forkVersion), addrs...)

Check warning on line 870 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L869-L870

Added lines #L869 - L870 were not covered by tests
if err != nil {
return nil, errors.Wrap(err, "new eth2 http client")
}

if conf.SyntheticBlockProposals {
log.Info(ctx, "Synthetic block proposals enabled")
if syntheticBlockProposals {

Check warning on line 875 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L875

Added line #L875 was not covered by tests
eth2Cl = eth2wrap.WithSyntheticDuties(eth2Cl)
}

Expand Down
48 changes: 26 additions & 22 deletions cmd/cmd_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,19 @@ func TestCmdFlags(t *testing.T) {
Enabled: nil,
Disabled: nil,
},
LockFile: ".charon/cluster-lock.json",
ManifestFile: ".charon/cluster-manifest.pb",
PrivKeyFile: ".charon/charon-enr-private-key",
PrivKeyLocking: false,
SimnetValidatorKeysDir: ".charon/validator_keys",
SimnetSlotDuration: time.Second,
MonitoringAddr: "127.0.0.1:3620",
ValidatorAPIAddr: "127.0.0.1:3600",
BeaconNodeAddrs: []string{"http://beacon.node"},
JaegerAddr: "",
JaegerService: "charon",
LockFile: ".charon/cluster-lock.json",
ManifestFile: ".charon/cluster-manifest.pb",
PrivKeyFile: ".charon/charon-enr-private-key",
PrivKeyLocking: false,
SimnetValidatorKeysDir: ".charon/validator_keys",
SimnetSlotDuration: time.Second,
MonitoringAddr: "127.0.0.1:3620",
ValidatorAPIAddr: "127.0.0.1:3600",
BeaconNodeAddrs: []string{"http://beacon.node"},
BeaconNodeTimeout: 2 * time.Second,
BeaconNodeSubmitTimeout: 2 * time.Second,
JaegerAddr: "",
JaegerService: "charon",
},
},
{
Expand Down Expand Up @@ -119,17 +121,19 @@ func TestCmdFlags(t *testing.T) {
Enabled: nil,
Disabled: nil,
},
LockFile: ".charon/cluster-lock.json",
ManifestFile: ".charon/cluster-manifest.pb",
PrivKeyFile: ".charon/charon-enr-private-key",
PrivKeyLocking: false,
SimnetValidatorKeysDir: ".charon/validator_keys",
SimnetSlotDuration: time.Second,
MonitoringAddr: "127.0.0.1:3620",
ValidatorAPIAddr: "127.0.0.1:3600",
BeaconNodeAddrs: []string{"http://beacon.node"},
JaegerAddr: "",
JaegerService: "charon",
LockFile: ".charon/cluster-lock.json",
ManifestFile: ".charon/cluster-manifest.pb",
PrivKeyFile: ".charon/charon-enr-private-key",
PrivKeyLocking: false,
SimnetValidatorKeysDir: ".charon/validator_keys",
SimnetSlotDuration: time.Second,
MonitoringAddr: "127.0.0.1:3620",
ValidatorAPIAddr: "127.0.0.1:3600",
BeaconNodeAddrs: []string{"http://beacon.node"},
BeaconNodeTimeout: 2 * time.Second,
BeaconNodeSubmitTimeout: 2 * time.Second,
JaegerAddr: "",
JaegerService: "charon",
TestConfig: app.TestConfig{
P2PFuzz: true,
},
Expand Down
5 changes: 5 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import (
"github.com/obolnetwork/charon/p2p"
)

// eth2ClientTimeout is the default timeout for charon <> beacon node API interactions.
const eth2ClientTimeout = time.Second * 2

func newRunCmd(runFunc func(context.Context, app.Config) error, unsafe bool) *cobra.Command {
var conf app.Config

Expand Down Expand Up @@ -71,6 +74,8 @@ func bindRunFlags(cmd *cobra.Command, config *app.Config) {
cmd.Flags().StringVar(&config.LockFile, "lock-file", ".charon/cluster-lock.json", "The path to the cluster lock file defining the distributed validator cluster. If both cluster manifest and cluster lock files are provided, the cluster manifest file takes precedence.")
cmd.Flags().StringVar(&config.ManifestFile, "manifest-file", ".charon/cluster-manifest.pb", "The path to the cluster manifest file. If both cluster manifest and cluster lock files are provided, the cluster manifest file takes precedence.")
cmd.Flags().StringSliceVar(&config.BeaconNodeAddrs, "beacon-node-endpoints", nil, "Comma separated list of one or more beacon node endpoint URLs.")
cmd.Flags().DurationVar(&config.BeaconNodeTimeout, "beacon-node-timeout", eth2ClientTimeout, "Timeout of the HTTP requests Charon does to the configured beacon nodes.")
cmd.Flags().DurationVar(&config.BeaconNodeSubmitTimeout, "beacon-node-submit-timeout", eth2ClientTimeout, "Timeout of the HTTP requests Charon does to the configured beacon nodes, on submission endpoints.")
cmd.Flags().StringVar(&config.ValidatorAPIAddr, "validator-api-address", "127.0.0.1:3600", "Listening address (ip and port) for validator-facing traffic proxying the beacon-node API.")
cmd.Flags().StringVar(&config.JaegerAddr, "jaeger-address", "", "Listening address for jaeger tracing.")
cmd.Flags().StringVar(&config.JaegerService, "jaeger-service", "charon", "Service name used for jaeger tracing.")
Expand Down
Loading

0 comments on commit 3e8d7e0

Please sign in to comment.