diff --git a/app/app.go b/app/app.go index dfcdf4eac..dd8529be0 100644 --- a/app/app.go +++ b/app/app.go @@ -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 @@ -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 @@ -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) if err != nil { return err } @@ -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, peerIDs, sender, qbftDebug.AddInstance, seenPubkeysFunc, vapiCallsFunc) if err != nil { return err @@ -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 { @@ -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) if err != nil { return err } @@ -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) { pubkeys, err := eth2PubKeys(cluster) if err != nil { - return nil, err + return nil, nil, err } // Default to 1s slot duration if not set. @@ -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 } wrap, err := eth2wrap.Instrument(bmock) if err != nil { - return nil, err + return nil, nil, err } life.RegisterStop(lifecycle.StopBeaconMock, lifecycle.HookFuncErr(bmock.Close)) - return wrap, nil + return wrap, nil, nil } if conf.SimnetBMock { // Configure the beacon mock. genesisTime, err := eth2util.ForkVersionToGenesisTime(forkVersion) if err != nil { - return nil, err + return nil, nil, err } const dutyFactor = 100 // Duty factor spreads duties deterministically in an epoch. @@ -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 } wrap, err := eth2wrap.Instrument(bmock) if err != nil { - return nil, err + return nil, nil, err } if conf.SyntheticBlockProposals { @@ -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, wrap, nil } if len(conf.BeaconNodeAddrs) == 0 { - return nil, errors.New("beacon node endpoints empty") + return nil, nil, errors.New("beacon node endpoints empty") + } + + if conf.SyntheticBlockProposals { + log.Info(ctx, "Synthetic block proposals enabled") + } + + eth2Cl, err := configureEth2Client(ctx, forkVersion, conf.BeaconNodeAddrs, bnTimeout, conf.SyntheticBlockProposals) + if err != nil { + return nil, nil, errors.Wrap(err, "new eth2 http client") } - 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") + } + + return eth2Cl, submissionEth2Cl, nil +} + +// 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...) if err != nil { return nil, errors.Wrap(err, "new eth2 http client") } - if conf.SyntheticBlockProposals { - log.Info(ctx, "Synthetic block proposals enabled") + if syntheticBlockProposals { eth2Cl = eth2wrap.WithSyntheticDuties(eth2Cl) } diff --git a/cmd/cmd_internal_test.go b/cmd/cmd_internal_test.go index 20c188587..af7452edc 100644 --- a/cmd/cmd_internal_test.go +++ b/cmd/cmd_internal_test.go @@ -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", }, }, { @@ -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, }, diff --git a/cmd/run.go b/cmd/run.go index af1faa668..c79fbaab7 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -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 @@ -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 for the HTTP requests Charon makes to the configured beacon nodes.") + cmd.Flags().DurationVar(&config.BeaconNodeSubmitTimeout, "beacon-node-submit-timeout", eth2ClientTimeout, "Timeout for the submission-related HTTP requests Charon makes to the configured beacon nodes.") 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.") diff --git a/docs/configuration.md b/docs/configuration.md index 4f2bc0b75..2cd54603e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -149,44 +149,46 @@ Usage: charon run [flags] Flags: - --beacon-node-endpoints strings Comma separated list of one or more beacon node endpoint URLs. - --builder-api Enables the builder api. Will only produce builder blocks. Builder API must also be enabled on the validator client. Beacon node must be connected to a builder-relay to access the builder network. - --debug-address string Listening address (ip and port) for the pprof and QBFT debug API. It is not enabled by default. - --feature-set string Minimum feature set to enable by default: alpha, beta, or stable. Warning: modify at own risk. (default "stable") - --feature-set-disable strings Comma-separated list of features to disable, overriding the default minimum feature set. - --feature-set-enable strings Comma-separated list of features to enable, overriding the default minimum feature set. - -h, --help Help for run - --jaeger-address string Listening address for jaeger tracing. - --jaeger-service string Service name used for jaeger tracing. (default "charon") - --lock-file string 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. (default ".charon/cluster-lock.json") - --log-color string Log color; auto, force, disable. (default "auto") - --log-format string Log format; console, logfmt or json (default "console") - --log-level string Log level; debug, info, warn or error (default "info") - --log-output-path string Path in which to write on-disk logs. - --loki-addresses strings Enables sending of logfmt structured logs to these Loki log aggregation server addresses. This is in addition to normal stderr logs. - --loki-service string Service label sent with logs to Loki. (default "charon") - --manifest-file string The path to the cluster manifest file. If both cluster manifest and cluster lock files are provided, the cluster manifest file takes precedence. (default ".charon/cluster-manifest.pb") - --monitoring-address string Listening address (ip and port) for the monitoring API (prometheus). (default "127.0.0.1:3620") - --no-verify Disables cluster definition and lock file verification. - --p2p-disable-reuseport Disables TCP port reuse for outgoing libp2p connections. - --p2p-external-hostname string The DNS hostname advertised by libp2p. This may be used to advertise an external DNS. - --p2p-external-ip string The IP address advertised by libp2p. This may be used to advertise an external IP. - --p2p-relays strings Comma-separated list of libp2p relay URLs or multiaddrs. (default [https://0.relay.obol.tech,https://1.relay.obol.tech]) - --p2p-tcp-address strings Comma-separated list of listening TCP addresses (ip and port) for libP2P traffic. Empty default doesn't bind to local port therefore only supports outgoing connections. - --private-key-file string The path to the charon enr private key file. (default ".charon/charon-enr-private-key") - --private-key-file-lock Enables private key locking to prevent multiple instances using the same key. - --simnet-beacon-mock Enables an internal mock beacon node for running a simnet. - --simnet-beacon-mock-fuzz Configures simnet beaconmock to return fuzzed responses. - --simnet-slot-duration duration Configures slot duration in simnet beacon mock. (default 1s) - --simnet-validator-keys-dir string The directory containing the simnet validator key shares. (default ".charon/validator_keys") - --simnet-validator-mock Enables an internal mock validator client when running a simnet. Requires simnet-beacon-mock. - --synthetic-block-proposals Enables additional synthetic block proposal duties. Used for testing of rare duties. - --testnet-capella-hard-fork string Capella hard fork version of the custom test network. - --testnet-chain-id uint Chain ID of the custom test network. - --testnet-fork-version string Genesis fork version in hex of the custom test network. - --testnet-genesis-timestamp int Genesis timestamp of the custom test network. - --testnet-name string Name of the custom test network. - --validator-api-address string Listening address (ip and port) for validator-facing traffic proxying the beacon-node API. (default "127.0.0.1:3600") + --beacon-node-endpoints strings Comma separated list of one or more beacon node endpoint URLs. + --beacon-node-submit-timeout duration Timeout for the submission-related HTTP requests Charon makes to the configured beacon nodes. (default 2s) + --beacon-node-timeout duration Timeout for the HTTP requests Charon makes to the configured beacon nodes. (default 2s) + --builder-api Enables the builder api. Will only produce builder blocks. Builder API must also be enabled on the validator client. Beacon node must be connected to a builder-relay to access the builder network. + --debug-address string Listening address (ip and port) for the pprof and QBFT debug API. It is not enabled by default. + --feature-set string Minimum feature set to enable by default: alpha, beta, or stable. Warning: modify at own risk. (default "stable") + --feature-set-disable strings Comma-separated list of features to disable, overriding the default minimum feature set. + --feature-set-enable strings Comma-separated list of features to enable, overriding the default minimum feature set. + -h, --help Help for run + --jaeger-address string Listening address for jaeger tracing. + --jaeger-service string Service name used for jaeger tracing. (default "charon") + --lock-file string 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. (default ".charon/cluster-lock.json") + --log-color string Log color; auto, force, disable. (default "auto") + --log-format string Log format; console, logfmt or json (default "console") + --log-level string Log level; debug, info, warn or error (default "info") + --log-output-path string Path in which to write on-disk logs. + --loki-addresses strings Enables sending of logfmt structured logs to these Loki log aggregation server addresses. This is in addition to normal stderr logs. + --loki-service string Service label sent with logs to Loki. (default "charon") + --manifest-file string The path to the cluster manifest file. If both cluster manifest and cluster lock files are provided, the cluster manifest file takes precedence. (default ".charon/cluster-manifest.pb") + --monitoring-address string Listening address (ip and port) for the monitoring API (prometheus). (default "127.0.0.1:3620") + --no-verify Disables cluster definition and lock file verification. + --p2p-disable-reuseport Disables TCP port reuse for outgoing libp2p connections. + --p2p-external-hostname string The DNS hostname advertised by libp2p. This may be used to advertise an external DNS. + --p2p-external-ip string The IP address advertised by libp2p. This may be used to advertise an external IP. + --p2p-relays strings Comma-separated list of libp2p relay URLs or multiaddrs. (default [https://0.relay.obol.tech,https://1.relay.obol.tech]) + --p2p-tcp-address strings Comma-separated list of listening TCP addresses (ip and port) for libP2P traffic. Empty default doesn't bind to local port therefore only supports outgoing connections. + --private-key-file string The path to the charon enr private key file. (default ".charon/charon-enr-private-key") + --private-key-file-lock Enables private key locking to prevent multiple instances using the same key. + --simnet-beacon-mock Enables an internal mock beacon node for running a simnet. + --simnet-beacon-mock-fuzz Configures simnet beaconmock to return fuzzed responses. + --simnet-slot-duration duration Configures slot duration in simnet beacon mock. (default 1s) + --simnet-validator-keys-dir string The directory containing the simnet validator key shares. (default ".charon/validator_keys") + --simnet-validator-mock Enables an internal mock validator client when running a simnet. Requires simnet-beacon-mock. + --synthetic-block-proposals Enables additional synthetic block proposal duties. Used for testing of rare duties. + --testnet-capella-hard-fork string Capella hard fork version of the custom test network. + --testnet-chain-id uint Chain ID of the custom test network. + --testnet-fork-version string Genesis fork version in hex of the custom test network. + --testnet-genesis-timestamp int Genesis timestamp of the custom test network. + --testnet-name string Name of the custom test network. + --validator-api-address string Listening address (ip and port) for validator-facing traffic proxying the beacon-node API. (default "127.0.0.1:3600") ```` diff --git a/testutil/fuzz.go b/testutil/fuzz.go index 4550bdd33..8255e3a6b 100644 --- a/testutil/fuzz.go +++ b/testutil/fuzz.go @@ -119,19 +119,26 @@ func NewEth2Fuzzer(t *testing.T, seed int64) *fuzz.Fuzzer { // Limit length of KZGProofs and Blobs to 6 // See https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#execution - maxKZGProofs := 6 - if e.Version == eth2spec.DataVersionDeneb && len(e.Deneb.KZGProofs) > maxKZGProofs { - e.Deneb.KZGProofs = e.Deneb.KZGProofs[:maxKZGProofs] - } - - maxBlobs := 6 - if e.Version == eth2spec.DataVersionDeneb && len(e.Deneb.Blobs) > maxBlobs { - e.Deneb.Blobs = e.Deneb.Blobs[:maxBlobs] - } - - maxBlobCommitments := 4096 - if e.Version == eth2spec.DataVersionDeneb && len(e.DenebBlinded.Message.Body.BlobKZGCommitments) > maxBlobCommitments { - e.DenebBlinded.Message.Body.BlobKZGCommitments = e.DenebBlinded.Message.Body.BlobKZGCommitments[:maxBlobCommitments] + var ( + maxKZGProofs = 6 + maxBlobs = 6 + maxBlobCommitments = 4096 + ) + + if e.Version == eth2spec.DataVersionDeneb { + if e.Deneb != nil { + if len(e.Deneb.KZGProofs) > maxKZGProofs { + e.Deneb.KZGProofs = e.Deneb.KZGProofs[:maxKZGProofs] + } + + if len(e.Deneb.Blobs) > maxBlobs { + e.Deneb.Blobs = e.Deneb.Blobs[:maxBlobs] + } + } + + if e.DenebBlinded != nil && len(e.DenebBlinded.Message.Body.BlobKZGCommitments) > maxBlobCommitments { + e.DenebBlinded.Message.Body.BlobKZGCommitments = e.DenebBlinded.Message.Body.BlobKZGCommitments[:maxBlobCommitments] + } } }, func(e *core.VersionedProposal, c fuzz.Continue) {