From f61f0b654cb2012137382680af471e4a3ed02afc Mon Sep 17 00:00:00 2001 From: Daniel Knopik Date: Thu, 13 Mar 2025 12:50:29 +0100 Subject: [PATCH] Squashed reset to unstable --- .github/CODEOWNERS | 4 +- .github/workflows/test-suite.yml | 6 +- Cargo.lock | 1843 ++---- Cargo.toml | 35 +- Makefile | 2 +- beacon_node/Cargo.toml | 4 +- beacon_node/beacon_chain/Cargo.toml | 5 +- .../beacon_chain/src/attestation_rewards.rs | 15 +- .../beacon_chain/src/attestation_simulator.rs | 21 +- .../src/attestation_verification.rs | 40 +- .../beacon_chain/src/beacon_block_reward.rs | 22 +- .../beacon_chain/src/beacon_block_streamer.rs | 102 +- beacon_node/beacon_chain/src/beacon_chain.rs | 848 ++- .../beacon_chain/src/bellatrix_readiness.rs | 2 +- .../beacon_chain/src/blob_verification.rs | 9 +- .../beacon_chain/src/block_times_cache.rs | 4 +- .../beacon_chain/src/block_verification.rs | 77 +- .../src/block_verification_types.rs | 16 +- beacon_node/beacon_chain/src/builder.rs | 117 +- .../beacon_chain/src/canonical_head.rs | 193 +- beacon_node/beacon_chain/src/chain_config.rs | 13 + .../src/data_availability_checker.rs | 220 +- .../src/data_availability_checker/error.rs | 4 +- .../overflow_lru_cache.rs | 479 +- .../state_lru_cache.rs | 1 - .../src/data_column_verification.rs | 9 +- .../beacon_chain/src/early_attester_cache.rs | 17 +- beacon_node/beacon_chain/src/eth1_chain.rs | 70 +- .../src/eth1_finalization_cache.rs | 48 +- beacon_node/beacon_chain/src/events.rs | 22 +- .../beacon_chain/src/execution_payload.rs | 54 +- beacon_node/beacon_chain/src/fetch_blobs.rs | 67 +- beacon_node/beacon_chain/src/fork_revert.rs | 17 +- .../beacon_chain/src/graffiti_calculator.rs | 80 +- .../beacon_chain/src/historical_blocks.rs | 49 +- .../src/light_client_server_cache.rs | 8 +- beacon_node/beacon_chain/src/migrate.rs | 156 +- .../src/otb_verification_service.rs | 369 ++ .../src/pre_finalization_cache.rs | 7 +- .../beacon_chain/src/proposer_prep_service.rs | 16 +- beacon_node/beacon_chain/src/schema_change.rs | 20 +- .../src/schema_change/migration_schema_v20.rs | 22 +- .../src/schema_change/migration_schema_v21.rs | 23 +- .../src/schema_change/migration_schema_v22.rs | 33 +- .../beacon_chain/src/shuffling_cache.rs | 24 +- .../beacon_chain/src/state_advance_timer.rs | 120 +- .../src/sync_committee_rewards.rs | 6 +- beacon_node/beacon_chain/src/test_utils.rs | 126 +- .../beacon_chain/src/validator_monitor.rs | 655 +- .../src/validator_pubkey_cache.rs | 8 +- .../tests/attestation_production.rs | 4 +- beacon_node/beacon_chain/tests/bellatrix.rs | 2 - .../beacon_chain/tests/block_verification.rs | 10 +- beacon_node/beacon_chain/tests/capella.rs | 1 - .../beacon_chain/tests/op_verification.rs | 3 - .../tests/payload_invalidation.rs | 2 - beacon_node/beacon_chain/tests/rewards.rs | 6 +- beacon_node/beacon_chain/tests/store_tests.rs | 38 +- .../beacon_chain/tests/validator_monitor.rs | 2 - beacon_node/beacon_processor/Cargo.toml | 2 +- beacon_node/beacon_processor/src/lib.rs | 659 +- .../src/work_reprocessing_queue.rs | 175 +- beacon_node/builder_client/src/lib.rs | 106 +- beacon_node/client/Cargo.toml | 4 +- beacon_node/client/src/builder.rs | 178 +- .../src/compute_light_client_updates.rs | 7 +- beacon_node/client/src/notifier.rs | 391 +- beacon_node/eth1/Cargo.toml | 3 +- beacon_node/eth1/src/service.rs | 176 +- beacon_node/eth1/tests/test.rs | 52 +- beacon_node/execution_layer/Cargo.toml | 3 +- beacon_node/execution_layer/src/engine_api.rs | 9 +- beacon_node/execution_layer/src/engines.rs | 72 +- beacon_node/execution_layer/src/lib.rs | 377 +- .../execution_layer/src/payload_status.rs | 30 +- .../test_utils/execution_block_generator.rs | 6 +- .../src/test_utils/mock_builder.rs | 72 +- .../src/test_utils/mock_execution_layer.rs | 3 +- .../execution_layer/src/test_utils/mod.rs | 15 +- beacon_node/genesis/Cargo.toml | 3 +- .../genesis/src/eth1_genesis_service.rs | 108 +- beacon_node/genesis/tests/tests.rs | 6 +- beacon_node/http_api/Cargo.toml | 4 +- .../http_api/src/aggregate_attestation.rs | 37 +- beacon_node/http_api/src/block_rewards.rs | 32 +- beacon_node/http_api/src/database.rs | 12 +- beacon_node/http_api/src/lib.rs | 444 +- beacon_node/http_api/src/produce_block.rs | 4 +- beacon_node/http_api/src/proposer_duties.rs | 9 +- .../http_api/src/publish_attestations.rs | 68 +- beacon_node/http_api/src/publish_blocks.rs | 228 +- .../http_api/src/sync_committee_rewards.rs | 5 +- beacon_node/http_api/src/sync_committees.rs | 62 +- beacon_node/http_api/src/test_utils.rs | 24 +- beacon_node/http_api/src/validators.rs | 6 +- .../tests/broadcast_validation_tests.rs | 8 - beacon_node/http_api/tests/tests.rs | 120 +- beacon_node/http_metrics/Cargo.toml | 3 +- beacon_node/http_metrics/src/lib.rs | 12 +- beacon_node/http_metrics/tests/tests.rs | 6 +- beacon_node/lighthouse_network/Cargo.toml | 12 +- .../lighthouse_network/gossipsub/CHANGELOG.md | 386 -- .../lighthouse_network/gossipsub/Cargo.toml | 49 - .../gossipsub/src/backoff.rs | 174 - .../gossipsub/src/behaviour.rs | 3672 ----------- .../gossipsub/src/behaviour/tests.rs | 5486 ----------------- .../gossipsub/src/config.rs | 1051 ---- .../lighthouse_network/gossipsub/src/error.rs | 159 - .../gossipsub/src/generated/compat.proto | 12 - .../gossipsub/src/generated/compat/mod.rs | 2 - .../gossipsub/src/generated/compat/pb.rs | 67 - .../gossipsub/src/generated/gossipsub/mod.rs | 2 - .../gossipsub/src/generated/gossipsub/pb.rs | 603 -- .../gossipsub/src/generated/mod.rs | 3 - .../gossipsub/src/generated/rpc.proto | 89 - .../gossipsub/src/gossip_promises.rs | 116 - .../gossipsub/src/handler.rs | 558 -- .../lighthouse_network/gossipsub/src/lib.rs | 134 - .../gossipsub/src/mcache.rs | 385 -- .../gossipsub/src/metrics.rs | 800 --- .../lighthouse_network/gossipsub/src/mod.rs | 111 - .../gossipsub/src/peer_score.rs | 937 --- .../gossipsub/src/peer_score/params.rs | 404 -- .../gossipsub/src/peer_score/tests.rs | 978 --- .../gossipsub/src/protocol.rs | 646 -- .../gossipsub/src/rpc_proto.rs | 92 - .../gossipsub/src/subscription_filter.rs | 435 -- .../gossipsub/src/time_cache.rs | 219 - .../lighthouse_network/gossipsub/src/topic.rs | 123 - .../gossipsub/src/transform.rs | 72 - .../lighthouse_network/gossipsub/src/types.rs | 882 --- .../lighthouse_network/src/discovery/enr.rs | 25 +- .../lighthouse_network/src/discovery/mod.rs | 228 +- .../src/discovery/subnet_predicate.rs | 10 +- .../lighthouse_network/src/listen_addr.rs | 22 - .../src/peer_manager/mod.rs | 125 +- .../src/peer_manager/network_behaviour.rs | 38 +- .../src/peer_manager/peerdb.rs | 151 +- .../src/peer_manager/peerdb/peer_info.rs | 26 +- .../lighthouse_network/src/rpc/codec.rs | 23 +- .../lighthouse_network/src/rpc/handler.rs | 110 +- .../lighthouse_network/src/rpc/methods.rs | 18 +- beacon_node/lighthouse_network/src/rpc/mod.rs | 136 +- .../src/rpc/self_limiter.rs | 55 +- .../src/service/api_types.rs | 16 - .../lighthouse_network/src/service/mod.rs | 617 +- .../lighthouse_network/src/service/utils.rs | 45 +- .../lighthouse_network/src/types/globals.rs | 25 +- .../lighthouse_network/src/types/mod.rs | 8 +- .../src/types}/sync_state.rs | 0 .../lighthouse_network/src/types/topics.rs | 288 +- .../lighthouse_network/tests/common.rs | 62 +- .../lighthouse_network/tests/rpc_tests.rs | 160 +- beacon_node/network/Cargo.toml | 10 +- beacon_node/network/src/metrics.rs | 48 +- beacon_node/network/src/nat.rs | 10 +- .../gossip_methods.rs | 911 ++- .../src/network_beacon_processor/mod.rs | 156 +- .../network_beacon_processor/rpc_methods.rs | 368 +- .../network_beacon_processor/sync_methods.rs | 277 +- .../src/network_beacon_processor/tests.rs | 5 - beacon_node/network/src/persisted_dht.rs | 5 +- beacon_node/network/src/router.rs | 137 +- beacon_node/network/src/service.rs | 172 +- beacon_node/network/src/service/tests.rs | 32 +- .../src/subnet_service/attestation_subnets.rs | 681 ++ beacon_node/network/src/subnet_service/mod.rs | 152 +- .../src/subnet_service/sync_subnets.rs | 345 ++ .../network/src/subnet_service/tests/mod.rs | 21 +- .../network/src/sync/backfill_sync/mod.rs | 257 +- .../network/src/sync/block_lookups/mod.rs | 326 +- beacon_node/network/src/sync/manager.rs | 126 +- .../network/src/sync/network_context.rs | 322 +- .../src/sync/network_context/custody.rs | 93 +- beacon_node/network/src/sync/peer_sampling.rs | 167 +- .../network/src/sync/range_sync/batch.rs | 56 +- .../network/src/sync/range_sync/chain.rs | 223 +- .../src/sync/range_sync/chain_collection.rs | 40 +- .../network/src/sync/range_sync/range.rs | 99 +- beacon_node/network/src/sync/tests/lookups.rs | 61 +- beacon_node/network/src/sync/tests/mod.rs | 7 +- beacon_node/network/src/sync/tests/range.rs | 196 +- .../src/bls_to_execution_changes.rs | 2 +- beacon_node/operation_pool/src/lib.rs | 2 +- .../operation_pool/src/reward_cache.rs | 2 +- beacon_node/src/cli.rs | 47 + beacon_node/src/config.rs | 103 +- beacon_node/src/lib.rs | 63 +- beacon_node/store/Cargo.toml | 4 +- beacon_node/store/src/chunked_iter.rs | 19 +- beacon_node/store/src/garbage_collection.rs | 8 +- beacon_node/store/src/hot_cold_store.rs | 327 +- beacon_node/store/src/iter.rs | 9 +- beacon_node/store/src/lib.rs | 2 - beacon_node/store/src/reconstruct.rs | 21 +- beacon_node/tests/test.rs | 2 - beacon_node/timer/Cargo.toml | 2 +- beacon_node/timer/src/lib.rs | 7 +- book/src/help_bn.md | 19 +- book/src/help_general.md | 17 +- book/src/help_vc.md | 28 +- book/src/help_vm.md | 17 +- book/src/help_vm_create.md | 17 +- book/src/help_vm_import.md | 17 +- book/src/help_vm_move.md | 17 +- book/src/setup.md | 3 - boot_node/Cargo.toml | 8 +- boot_node/src/config.rs | 17 +- boot_node/src/lib.rs | 48 +- boot_node/src/server.rs | 54 +- common/account_utils/Cargo.toml | 2 +- .../src/validator_definitions.rs | 16 +- common/eth2/Cargo.toml | 5 +- common/eth2/src/lib.rs | 4 +- common/eth2/src/lighthouse.rs | 27 +- common/eth2/src/types.rs | 27 +- common/eth2_network_config/Cargo.toml | 3 +- .../chiado/config.yaml | 14 +- common/eth2_network_config/src/lib.rs | 26 +- common/lighthouse_version/Cargo.toml | 2 +- common/lighthouse_version/src/lib.rs | 13 +- common/logging/Cargo.toml | 6 +- common/logging/src/async_record.rs | 307 - common/logging/src/lib.rs | 377 +- common/logging/src/sse_logging_components.rs | 98 +- common/logging/src/tracing_logging_layer.rs | 521 +- common/logging/tests/test.rs | 51 - common/malloc_utils/src/jemalloc.rs | 72 +- common/monitoring_api/Cargo.toml | 2 +- common/monitoring_api/src/lib.rs | 29 +- common/task_executor/Cargo.toml | 10 +- common/task_executor/src/lib.rs | 104 +- common/task_executor/src/test_utils.rs | 14 +- consensus/fork_choice/Cargo.toml | 3 +- consensus/fork_choice/src/fork_choice.rs | 32 +- consensus/merkle_proof/src/lib.rs | 15 +- .../block_signature_verifier.rs | 1 - .../src/per_block_processing/errors.rs | 11 + .../src/per_epoch_processing/errors.rs | 9 +- consensus/types/Cargo.toml | 3 +- consensus/types/src/attestation.rs | 25 +- consensus/types/src/beacon_block_body.rs | 5 +- consensus/types/src/beacon_state.rs | 30 +- consensus/types/src/chain_spec.rs | 4 + .../types/src/data_column_custody_group.rs | 2 + consensus/types/src/payload.rs | 16 +- consensus/types/src/runtime_var_list.rs | 10 +- consensus/types/src/slot_epoch_macros.rs | 11 - consensus/types/src/sync_aggregate.rs | 3 +- .../types/src/sync_committee_contribution.rs | 3 +- .../generate_deterministic_keypairs.rs | 2 +- .../types/src/validator_registration_data.rs | 2 +- crypto/bls/src/generic_secret_key.rs | 26 +- crypto/bls/src/generic_signature.rs | 14 +- crypto/bls/src/lib.rs | 5 +- crypto/bls/tests/tests.rs | 17 +- database_manager/Cargo.toml | 2 +- database_manager/src/lib.rs | 68 +- lcli/Cargo.toml | 5 +- lcli/src/block_root.rs | 4 +- lcli/src/main.rs | 42 +- lcli/src/parse_ssz.rs | 4 +- lcli/src/skip_slots.rs | 2 +- lcli/src/state_root.rs | 2 +- lcli/src/transition_blocks.rs | 13 +- lighthouse/Cargo.toml | 5 +- lighthouse/environment/Cargo.toml | 11 +- lighthouse/environment/src/lib.rs | 352 +- lighthouse/environment/src/tracing_common.rs | 78 + .../environment/tests/environment_builder.rs | 2 - lighthouse/src/main.rs | 198 +- lighthouse/src/metrics.rs | 9 +- lighthouse/tests/beacon_node.rs | 98 +- lighthouse/tests/validator_client.rs | 16 + scripts/local_testnet/network_params.yaml | 1 + scripts/local_testnet/network_params_das.yaml | 4 + scripts/tests/doppelganger_protection.sh | 2 +- slasher/Cargo.toml | 4 +- slasher/service/Cargo.toml | 2 +- slasher/service/src/service.rs | 82 +- slasher/src/database.rs | 8 +- slasher/src/slasher.rs | 72 +- slasher/tests/attester_slashings.rs | 5 +- slasher/tests/proposer_slashings.rs | 5 +- slasher/tests/random.rs | 6 +- slasher/tests/wrap_around.rs | 3 +- testing/ef_tests/check_all_files_accessed.py | 4 +- testing/ef_tests/src/cases/fork_choice.rs | 1 - .../src/cases/merkle_proof_validity.rs | 57 +- testing/ef_tests/src/handler.rs | 34 +- testing/ef_tests/tests/tests.rs | 10 +- .../src/test_rig.rs | 14 +- testing/simulator/Cargo.toml | 3 + testing/simulator/src/basic_sim.rs | 46 +- testing/simulator/src/fallback_sim.rs | 47 +- testing/simulator/src/local_network.rs | 1 - testing/test-test_logger/Cargo.toml | 9 - testing/test-test_logger/src/lib.rs | 22 - testing/validator_test_rig/Cargo.toml | 2 +- .../src/mock_beacon_node.rs | 8 +- testing/web3signer_tests/Cargo.toml | 1 - .../web3signer_tests/src/get_web3signer.rs | 95 +- testing/web3signer_tests/src/lib.rs | 59 +- validator_client/Cargo.toml | 3 +- .../beacon_node_fallback/Cargo.toml | 2 +- .../beacon_node_fallback/src/lib.rs | 64 +- .../doppelganger_service/Cargo.toml | 4 +- .../doppelganger_service/src/lib.rs | 208 +- validator_client/graffiti_file/Cargo.toml | 2 +- validator_client/graffiti_file/src/lib.rs | 14 +- validator_client/http_api/Cargo.toml | 32 +- .../src/create_signed_voluntary_exit.rs | 13 +- .../http_api/src/create_validator.rs | 9 +- validator_client/http_api/src/graffiti.rs | 8 +- validator_client/http_api/src/keystores.rs | 53 +- validator_client/http_api/src/lib.rs | 166 +- validator_client/http_api/src/remotekeys.rs | 46 +- validator_client/http_api/src/test_utils.rs | 21 +- validator_client/http_api/src/tests.rs | 27 +- .../http_api/src/tests/keystores.rs | 3 +- validator_client/http_metrics/Cargo.toml | 5 +- validator_client/http_metrics/src/lib.rs | 26 +- .../initialized_validators/Cargo.toml | 2 +- .../initialized_validators/src/lib.rs | 70 +- .../lighthouse_validator_store/Cargo.toml | 29 - .../lighthouse_validator_store/src/lib.rs | 1116 ---- validator_client/signing_method/src/lib.rs | 17 +- .../slashing_protection/Cargo.toml | 1 + .../slashing_protection/src/lib.rs | 2 +- .../src/signed_attestation.rs | 2 +- .../slashing_protection/src/signed_block.rs | 2 +- .../src/slashing_database.rs | 2 +- validator_client/src/check_synced.rs | 25 + validator_client/src/cli.rs | 23 + validator_client/src/config.rs | 25 +- validator_client/src/latency.rs | 13 +- validator_client/src/lib.rs | 261 +- validator_client/src/notifier.rs | 107 +- .../validator_services/Cargo.toml | 5 +- .../src/attestation_service.rs | 113 +- .../validator_services/src/block_service.rs | 134 +- .../validator_services/src/duties_service.rs | 250 +- .../src/preparation_service.rs | 98 +- .../validator_services/src/sync.rs | 68 +- .../src/sync_committee_service.rs | 65 +- validator_client/validator_store/Cargo.toml | 15 + validator_client/validator_store/src/lib.rs | 1131 +++- watch/.gitignore | 1 - watch/Cargo.toml | 45 - watch/README.md | 458 -- watch/config.yaml.default | 49 - watch/diesel.toml | 5 - watch/migrations/.gitkeep | 0 .../down.sql | 6 - .../up.sql | 36 - .../down.sql | 1 - .../2022-01-01-000000_canonical_slots/up.sql | 6 - .../2022-01-01-000001_beacon_blocks/down.sql | 1 - .../2022-01-01-000001_beacon_blocks/up.sql | 7 - .../2022-01-01-000002_validators/down.sql | 1 - .../2022-01-01-000002_validators/up.sql | 7 - .../2022-01-01-000003_proposer_info/down.sql | 1 - .../2022-01-01-000003_proposer_info/up.sql | 5 - .../2022-01-01-000004_active_config/down.sql | 1 - .../2022-01-01-000004_active_config/up.sql | 5 - .../2022-01-01-000010_blockprint/down.sql | 1 - .../2022-01-01-000010_blockprint/up.sql | 4 - .../2022-01-01-000011_block_rewards/down.sql | 1 - .../2022-01-01-000011_block_rewards/up.sql | 6 - .../2022-01-01-000012_block_packing/down.sql | 1 - .../2022-01-01-000012_block_packing/up.sql | 6 - .../down.sql | 1 - .../up.sql | 8 - .../2022-01-01-000020_capella/down.sql | 2 - .../2022-01-01-000020_capella/up.sql | 3 - watch/postgres_docker_compose/compose.yml | 16 - watch/src/block_packing/database.rs | 140 - watch/src/block_packing/mod.rs | 38 - watch/src/block_packing/server.rs | 31 - watch/src/block_packing/updater.rs | 211 - watch/src/block_rewards/database.rs | 137 - watch/src/block_rewards/mod.rs | 38 - watch/src/block_rewards/server.rs | 31 - watch/src/block_rewards/updater.rs | 157 - watch/src/blockprint/config.rs | 40 - watch/src/blockprint/database.rs | 225 - watch/src/blockprint/mod.rs | 150 - watch/src/blockprint/server.rs | 31 - watch/src/blockprint/updater.rs | 172 - watch/src/cli.rs | 52 - watch/src/client.rs | 178 - watch/src/config.rs | 50 - watch/src/database/compat.rs | 47 - watch/src/database/config.rs | 74 - watch/src/database/error.rs | 55 - watch/src/database/mod.rs | 786 --- watch/src/database/models.rs | 67 - watch/src/database/schema.rs | 102 - watch/src/database/utils.rs | 28 - watch/src/database/watch_types.rs | 119 - watch/src/lib.rs | 12 - watch/src/logger.rs | 24 - watch/src/main.rs | 41 - watch/src/server/config.rs | 28 - watch/src/server/error.rs | 59 - watch/src/server/handler.rs | 266 - watch/src/server/mod.rs | 136 - watch/src/suboptimal_attestations/database.rs | 224 - watch/src/suboptimal_attestations/mod.rs | 56 - watch/src/suboptimal_attestations/server.rs | 299 - watch/src/suboptimal_attestations/updater.rs | 236 - watch/src/updater/config.rs | 65 - watch/src/updater/error.rs | 57 - watch/src/updater/handler.rs | 471 -- watch/src/updater/mod.rs | 234 - watch/tests/tests.rs | 1294 ---- 416 files changed, 13195 insertions(+), 38478 deletions(-) create mode 100644 beacon_node/beacon_chain/src/otb_verification_service.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/CHANGELOG.md delete mode 100644 beacon_node/lighthouse_network/gossipsub/Cargo.toml delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/backoff.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/behaviour.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/behaviour/tests.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/config.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/error.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/generated/compat.proto delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/generated/compat/mod.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/generated/compat/pb.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/mod.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/pb.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/generated/mod.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/generated/rpc.proto delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/gossip_promises.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/handler.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/lib.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/mcache.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/metrics.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/mod.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/peer_score.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/peer_score/params.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/peer_score/tests.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/protocol.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/rpc_proto.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/subscription_filter.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/time_cache.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/topic.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/transform.rs delete mode 100644 beacon_node/lighthouse_network/gossipsub/src/types.rs rename {common/eth2/src/lighthouse => beacon_node/lighthouse_network/src/types}/sync_state.rs (100%) create mode 100644 beacon_node/network/src/subnet_service/attestation_subnets.rs create mode 100644 beacon_node/network/src/subnet_service/sync_subnets.rs delete mode 100644 common/logging/src/async_record.rs delete mode 100644 common/logging/tests/test.rs create mode 100644 lighthouse/environment/src/tracing_common.rs delete mode 100644 testing/test-test_logger/Cargo.toml delete mode 100644 testing/test-test_logger/src/lib.rs delete mode 100644 validator_client/lighthouse_validator_store/Cargo.toml delete mode 100644 validator_client/lighthouse_validator_store/src/lib.rs create mode 100644 validator_client/src/check_synced.rs delete mode 100644 watch/.gitignore delete mode 100644 watch/Cargo.toml delete mode 100644 watch/README.md delete mode 100644 watch/config.yaml.default delete mode 100644 watch/diesel.toml delete mode 100644 watch/migrations/.gitkeep delete mode 100644 watch/migrations/00000000000000_diesel_initial_setup/down.sql delete mode 100644 watch/migrations/00000000000000_diesel_initial_setup/up.sql delete mode 100644 watch/migrations/2022-01-01-000000_canonical_slots/down.sql delete mode 100644 watch/migrations/2022-01-01-000000_canonical_slots/up.sql delete mode 100644 watch/migrations/2022-01-01-000001_beacon_blocks/down.sql delete mode 100644 watch/migrations/2022-01-01-000001_beacon_blocks/up.sql delete mode 100644 watch/migrations/2022-01-01-000002_validators/down.sql delete mode 100644 watch/migrations/2022-01-01-000002_validators/up.sql delete mode 100644 watch/migrations/2022-01-01-000003_proposer_info/down.sql delete mode 100644 watch/migrations/2022-01-01-000003_proposer_info/up.sql delete mode 100644 watch/migrations/2022-01-01-000004_active_config/down.sql delete mode 100644 watch/migrations/2022-01-01-000004_active_config/up.sql delete mode 100644 watch/migrations/2022-01-01-000010_blockprint/down.sql delete mode 100644 watch/migrations/2022-01-01-000010_blockprint/up.sql delete mode 100644 watch/migrations/2022-01-01-000011_block_rewards/down.sql delete mode 100644 watch/migrations/2022-01-01-000011_block_rewards/up.sql delete mode 100644 watch/migrations/2022-01-01-000012_block_packing/down.sql delete mode 100644 watch/migrations/2022-01-01-000012_block_packing/up.sql delete mode 100644 watch/migrations/2022-01-01-000013_suboptimal_attestations/down.sql delete mode 100644 watch/migrations/2022-01-01-000013_suboptimal_attestations/up.sql delete mode 100644 watch/migrations/2022-01-01-000020_capella/down.sql delete mode 100644 watch/migrations/2022-01-01-000020_capella/up.sql delete mode 100644 watch/postgres_docker_compose/compose.yml delete mode 100644 watch/src/block_packing/database.rs delete mode 100644 watch/src/block_packing/mod.rs delete mode 100644 watch/src/block_packing/server.rs delete mode 100644 watch/src/block_packing/updater.rs delete mode 100644 watch/src/block_rewards/database.rs delete mode 100644 watch/src/block_rewards/mod.rs delete mode 100644 watch/src/block_rewards/server.rs delete mode 100644 watch/src/block_rewards/updater.rs delete mode 100644 watch/src/blockprint/config.rs delete mode 100644 watch/src/blockprint/database.rs delete mode 100644 watch/src/blockprint/mod.rs delete mode 100644 watch/src/blockprint/server.rs delete mode 100644 watch/src/blockprint/updater.rs delete mode 100644 watch/src/cli.rs delete mode 100644 watch/src/client.rs delete mode 100644 watch/src/config.rs delete mode 100644 watch/src/database/compat.rs delete mode 100644 watch/src/database/config.rs delete mode 100644 watch/src/database/error.rs delete mode 100644 watch/src/database/mod.rs delete mode 100644 watch/src/database/models.rs delete mode 100644 watch/src/database/schema.rs delete mode 100644 watch/src/database/utils.rs delete mode 100644 watch/src/database/watch_types.rs delete mode 100644 watch/src/lib.rs delete mode 100644 watch/src/logger.rs delete mode 100644 watch/src/main.rs delete mode 100644 watch/src/server/config.rs delete mode 100644 watch/src/server/error.rs delete mode 100644 watch/src/server/handler.rs delete mode 100644 watch/src/server/mod.rs delete mode 100644 watch/src/suboptimal_attestations/database.rs delete mode 100644 watch/src/suboptimal_attestations/mod.rs delete mode 100644 watch/src/suboptimal_attestations/server.rs delete mode 100644 watch/src/suboptimal_attestations/updater.rs delete mode 100644 watch/src/updater/config.rs delete mode 100644 watch/src/updater/error.rs delete mode 100644 watch/src/updater/handler.rs delete mode 100644 watch/src/updater/mod.rs delete mode 100644 watch/tests/tests.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f9478d1369..a8919337a9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ -beacon_node/network/ @jxs -beacon_node/lighthouse_network/ @jxs +/beacon_node/network/ @jxs +/beacon_node/lighthouse_network/ @jxs diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 0f91c86617..817fd9524d 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -19,11 +19,11 @@ env: # Disable debug info (see https://github.com/sigp/lighthouse/issues/4005) RUSTFLAGS: "-D warnings -C debuginfo=0" # Prevent Github API rate limiting. - LIGHTHOUSE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # NOTE: this token is a personal access token on Jimmy's account due to the default GITHUB_TOKEN + # not having access to other repositories. We should eventually devise a better solution here. + LIGHTHOUSE_GITHUB_TOKEN: ${{ secrets.LIGHTHOUSE_GITHUB_TOKEN }} # Enable self-hosted runners for the sigp repo only. SELF_HOSTED_RUNNERS: ${{ github.repository == 'sigp/lighthouse' }} - # Self-hosted runners need to reference a different host for `./watch` tests. - WATCH_HOST: ${{ github.repository == 'sigp/lighthouse' && 'host.docker.internal' || 'localhost' }} # Disable incremental compilation CARGO_INCREMENTAL: 0 # Enable portable to prevent issues with caching `blst` for the wrong CPU type diff --git a/Cargo.lock b/Cargo.lock index a50141e193..d559c2f1fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,7 +51,7 @@ dependencies = [ "rpassword", "serde", "serde_yaml", - "slog", + "tracing", "types", "validator_dir", "zeroize", @@ -72,12 +72,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - [[package]] name = "aead" version = "0.5.2" @@ -204,9 +198,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc1360603efdfba91151e623f13a4f4d3dc4af4adc1cbd90bf37c81e84db4c77" +checksum = "8c66bb6715b7499ea755bde4c96223ae8eb74e05c014ab38b9db602879ffb825" dependencies = [ "alloy-rlp", "arbitrary", @@ -214,11 +208,11 @@ dependencies = [ "cfg-if", "const-hex", "derive_arbitrary", - "derive_more 1.0.0", + "derive_more 2.0.1", "foldhash", "getrandom 0.2.15", "hashbrown 0.15.2", - "indexmap 2.7.1", + "indexmap 2.8.0", "itoa", "k256 0.13.4", "keccak-asm", @@ -227,7 +221,7 @@ dependencies = [ "proptest-derive", "rand 0.8.5", "ruint", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "serde", "sha3 0.10.8", "tiny-keccak", @@ -252,7 +246,7 @@ checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -328,9 +322,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "arbitrary" @@ -522,7 +516,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -534,7 +528,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -564,6 +558,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-io" version = "2.4.0" @@ -602,18 +608,18 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -676,7 +682,7 @@ checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -685,61 +691,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core", - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.6.0", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper 1.0.2", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper 1.0.2", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.74" @@ -793,9 +744,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d" [[package]] name = "beacon_chain" @@ -838,10 +789,6 @@ dependencies = [ "serde", "serde_json", "slasher", - "slog", - "slog-async", - "slog-term", - "sloggers", "slot_clock", "smallvec", "ssz_types", @@ -853,6 +800,7 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "tracing", "tree_hash", "tree_hash_derive", "types", @@ -860,7 +808,7 @@ dependencies = [ [[package]] name = "beacon_node" -version = "6.0.1" +version = "7.0.0-beta.0" dependencies = [ "account_utils", "beacon_chain", @@ -882,10 +830,10 @@ dependencies = [ "sensitive_url", "serde_json", "slasher", - "slog", "store", "strum", "task_executor", + "tracing", "types", "unused_port", ] @@ -895,6 +843,7 @@ name = "beacon_node_fallback" version = "0.1.0" dependencies = [ "clap", + "environment", "eth2", "futures", "itertools 0.10.5", @@ -902,7 +851,6 @@ dependencies = [ "serde", "slot_clock", "strum", - "task_executor", "tokio", "tracing", "types", @@ -923,12 +871,12 @@ dependencies = [ "num_cpus", "parking_lot 0.12.3", "serde", - "slog", "slot_clock", "strum", "task_executor", "tokio", "tokio-util", + "tracing", "types", ] @@ -947,7 +895,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -960,24 +908,24 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.98", + "syn 2.0.100", "which", ] [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -987,9 +935,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitvec" @@ -1070,9 +1018,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" dependencies = [ "cc", "glob", @@ -1088,7 +1036,7 @@ checksum = "7a8a8ed6fefbeef4a8c7b460e4110e12c5e22a5b7cf32621aae6ad650c4dcf29" dependencies = [ "blst", "byte-slice-cast", - "ff 0.13.0", + "ff 0.13.1", "group 0.13.0", "pairing", "rand_core 0.6.4", @@ -1096,19 +1044,9 @@ dependencies = [ "subtle", ] -[[package]] -name = "bollard-stubs" -version = "1.42.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" -dependencies = [ - "serde", - "serde_with", -] - [[package]] name = "boot_node" -version = "6.0.1" +version = "7.0.0-beta.0" dependencies = [ "beacon_node", "bytes", @@ -1121,11 +1059,9 @@ dependencies = [ "log", "logging", "serde", - "slog", - "slog-async", - "slog-scope", - "slog-term", "tokio", + "tracing", + "tracing-subscriber", "types", ] @@ -1165,9 +1101,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-slice-cast" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "byteorder" @@ -1177,9 +1113,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" dependencies = [ "serde", ] @@ -1196,12 +1132,11 @@ dependencies = [ [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -1246,7 +1181,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.25", + "semver 1.0.26", "serde", "serde_json", "thiserror 1.0.69", @@ -1260,9 +1195,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.11" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "jobserver", "libc", @@ -1316,14 +1251,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", - "windows-targets 0.52.6", + "wasm-bindgen", + "windows-link", ] [[package]] @@ -1386,9 +1323,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.27" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -1396,9 +1333,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.27" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", @@ -1409,14 +1346,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1461,6 +1398,7 @@ dependencies = [ "http_metrics", "kzg", "lighthouse_network", + "logging", "metrics", "monitoring_api", "network", @@ -1471,7 +1409,6 @@ dependencies = [ "serde_yaml", "slasher", "slasher_service", - "slog", "slot_clock", "state_processing", "store", @@ -1479,14 +1416,16 @@ dependencies = [ "time", "timer", "tokio", + "tracing", + "tracing-subscriber", "types", ] [[package]] name = "cmake" -version = "0.1.53" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] @@ -1499,11 +1438,10 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "lazy_static", "windows-sys 0.59.0", ] @@ -1551,6 +1489,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -1599,13 +1557,13 @@ dependencies = [ [[package]] name = "crate_crypto_internal_eth_kzg_bls12_381" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48603155907d588e487aea229f61a28d9a918c95c9aa987055ba29502225810b" +checksum = "76f9cdad245e39a3659bc4c8958e93de34bd31ba3131ead14ccfb4b2cd60e52d" dependencies = [ "blst", "blstrs", - "ff 0.13.0", + "ff 0.13.1", "group 0.13.0", "pairing", "subtle", @@ -1613,9 +1571,9 @@ dependencies = [ [[package]] name = "crate_crypto_internal_eth_kzg_erasure_codes" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf616e4b4f1799191bb1e70b8a29f65e95ab5d74c59972a34998de488d01efd" +checksum = "581d28bcc93eecd97a04cebc5293271e0f41650f03c102f24d6cd784cbedb9f2" dependencies = [ "crate_crypto_internal_eth_kzg_bls12_381", "crate_crypto_internal_eth_kzg_polynomial", @@ -1623,24 +1581,24 @@ dependencies = [ [[package]] name = "crate_crypto_internal_eth_kzg_maybe_rayon" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ddd0330f34f0b92a9f0b29bc3f8494b30d596ab8b951233ec90b2d72ab132c" +checksum = "06fc0f984e585ea984a766c5b58d6bf6c51e463b0a0835b0dd4652d358b506b3" [[package]] name = "crate_crypto_internal_eth_kzg_polynomial" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7488314261926373e1c20121c404fabf5b57ca09f48eddc7fef38be1df79a006" +checksum = "56dff7a45e2d80308b21abdbc5520ec23c3ebfb3a94fafc02edfa7f356af6d7f" dependencies = [ "crate_crypto_internal_eth_kzg_bls12_381", ] [[package]] name = "crate_crypto_kzg_multi_open_fk20" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24efdb64e7518848f11069dd9de23bd04455146a9fd5486345d99ed8bfdb049" +checksum = "1a0c2f82695a88809e713e1ff9534cb90ceffab0a08f4bd33245db711f9d356f" dependencies = [ "crate_crypto_internal_eth_kzg_bls12_381", "crate_crypto_internal_eth_kzg_maybe_rayon", @@ -1841,7 +1799,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1889,7 +1847,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1911,7 +1869,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1934,23 +1892,17 @@ dependencies = [ "libc", ] -[[package]] -name = "dary_heap" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" - [[package]] name = "data-encoding" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" [[package]] name = "data-encoding-macro" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b16d9d0d88a5273d830dac8b78ceb217ffc9b1d5404e5597a3542515329405b" +checksum = "9f9724adfcf41f45bf652b3995837669d73c4d49a1b5ac1ff82905ac7d9b5558" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1958,12 +1910,12 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145d32e826a7748b69ee8fc62d3e6355ff7f1051df53141e7048162fc90481b" +checksum = "18e4fdb82bd54a12e42fb58a800dcae6b9e13982238ce2296dc3570b92148e1f" dependencies = [ "data-encoding", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1977,9 +1929,9 @@ dependencies = [ "environment", "hex", "serde", - "slog", "store", "strum", + "tracing", "types", ] @@ -2077,20 +2029,20 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "derive_more" -version = "0.99.18" +version = "0.99.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2099,7 +2051,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -2110,55 +2071,19 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", - "unicode-xid", + "syn 2.0.100", ] [[package]] -name = "diesel" -version = "2.2.7" +name = "derive_more-impl" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04001f23ba8843dc315804fa324000376084dfb1c30794ff68dd279e6e5696d5" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "bitflags 2.8.0", - "byteorder", - "diesel_derives", - "itoa", - "pq-sys", - "r2d2", -] - -[[package]] -name = "diesel_derives" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" -dependencies = [ - "diesel_table_macro_syntax", - "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "diesel_migrations" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" -dependencies = [ - "diesel", - "migrations_internals", - "migrations_macros", -] - -[[package]] -name = "diesel_table_macro_syntax" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" -dependencies = [ - "syn 2.0.98", + "syn 2.0.100", + "unicode-xid", ] [[package]] @@ -2200,16 +2125,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" version = "0.3.7" @@ -2221,17 +2136,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "discv5" version = "0.9.1" @@ -2273,7 +2177,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2286,33 +2190,18 @@ dependencies = [ "futures", "logging", "parking_lot 0.12.3", - "slog", "slot_clock", "task_executor", "tokio", + "tracing", "types", - "validator_store", -] - -[[package]] -name = "dsl_auto_type" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b" -dependencies = [ - "darling 0.20.10", - "either", - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.98", ] [[package]] name = "dtoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" [[package]] name = "dunce" @@ -2371,6 +2260,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "ef_tests" version = "0.2.0" @@ -2405,9 +2306,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" @@ -2437,7 +2338,7 @@ dependencies = [ "base16ct 0.2.0", "crypto-bigint 0.5.5", "digest 0.10.7", - "ff 0.13.0", + "ff 0.13.1", "generic-array", "group 0.13.0", "pem-rfc7468", @@ -2485,7 +2386,27 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -2515,37 +2436,29 @@ dependencies = [ name = "environment" version = "0.1.2" dependencies = [ - "async-channel", + "async-channel 1.9.0", + "clap", "ctrlc", "eth2_config", "eth2_network_config", "futures", "logging", + "logroller", "serde", - "slog", - "slog-async", - "slog-json", - "slog-term", - "sloggers", "task_executor", "tokio", + "tracing", + "tracing-appender", + "tracing-log", + "tracing-subscriber", "types", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "erased-serde" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" -dependencies = [ - "serde", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -2575,12 +2488,11 @@ dependencies = [ "sensitive_url", "serde", "serde_yaml", - "slog", - "sloggers", "state_processing", "superstruct", "task_executor", "tokio", + "tracing", "tree_hash", "types", ] @@ -2606,16 +2518,14 @@ version = "0.1.0" dependencies = [ "derivative", "either", - "enr", "eth2_keystore", "ethereum_serde_utils", "ethereum_ssz", "ethereum_ssz_derive", "futures", "futures-util", - "libp2p-identity", + "lighthouse_network", "mediatype", - "multiaddr", "pretty_reqwest_error", "proto_array", "reqwest", @@ -2625,6 +2535,7 @@ dependencies = [ "serde_json", "slashing_protection", "ssz_types", + "store", "tokio", "types", "zeroize", @@ -2694,15 +2605,14 @@ dependencies = [ "eth2_config", "ethereum_ssz", "kzg", - "logging", "pretty_reqwest_error", "reqwest", "sensitive_url", "serde_yaml", "sha2 0.9.9", - "slog", "tempfile", "tokio", + "tracing", "types", "url", "zip", @@ -2830,7 +2740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" dependencies = [ "cpufeatures", - "ring 0.17.8", + "ring 0.17.14", "sha2 0.10.8", ] @@ -2849,25 +2759,30 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e999563461faea0ab9bc0024e5e66adcee35881f3d5062f52f31a4070fe1522" +checksum = "86da3096d1304f5f28476ce383005385459afeaf0eea08592b65ddbc9b258d16" dependencies = [ "alloy-primitives", + "arbitrary", + "ethereum_serde_utils", "itertools 0.13.0", + "serde", + "serde_derive", "smallvec", + "typenum", ] [[package]] name = "ethereum_ssz_derive" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3deae99c8e74829a00ba7a92d49055732b3c1f093f2ccfa3cbc621679b6fa91" +checksum = "d832a5c38eba0e7ad92592f7a22d693954637fbb332b4f669590d66a5c3183e5" dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2909,7 +2824,7 @@ dependencies = [ "serde", "serde_json", "syn 1.0.109", - "toml 0.5.11", + "toml", "url", "walkdir", ] @@ -3035,7 +2950,7 @@ dependencies = [ name = "execution_engine_integration" version = "0.1.0" dependencies = [ - "async-channel", + "async-channel 1.9.0", "deposit_contract", "ethers-core", "ethers-providers", @@ -3065,6 +2980,7 @@ dependencies = [ "builder_client", "bytes", "eth2", + "eth2_network_config", "ethereum_serde_utils", "ethereum_ssz", "ethers-core", @@ -3088,7 +3004,6 @@ dependencies = [ "serde", "serde_json", "sha2 0.9.9", - "slog", "slot_clock", "ssz_types", "state_processing", @@ -3098,6 +3013,7 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "tracing", "tree_hash", "tree_hash_derive", "triehash", @@ -3178,9 +3094,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "bitvec 1.0.1", "rand_core 0.6.4", @@ -3251,9 +3167,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "libz-sys", @@ -3294,12 +3210,13 @@ dependencies = [ "beacon_chain", "ethereum_ssz", "ethereum_ssz_derive", + "logging", "metrics", "proto_array", - "slog", "state_processing", "store", "tokio", + "tracing", "types", ] @@ -3411,7 +3328,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3421,7 +3338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.22", + "rustls 0.23.23", "rustls-pki-types", ] @@ -3442,10 +3359,6 @@ name = "futures-timer" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" -dependencies = [ - "gloo-timers", - "send_wrapper 0.4.0", -] [[package]] name = "futures-util" @@ -3509,12 +3422,13 @@ dependencies = [ "ethereum_ssz", "futures", "int_to_bytes", + "logging", "merkle_proof", "rayon", "sensitive_url", - "slog", "state_processing", "tokio", + "tracing", "tree_hash", "types", ] @@ -3577,7 +3491,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3586,48 +3500,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "gossipsub" -version = "0.5.0" -dependencies = [ - "async-channel", - "asynchronous-codec", - "base64 0.21.7", - "byteorder", - "bytes", - "either", - "fnv", - "futures", - "futures-timer", - "getrandom 0.2.15", - "hashlink 0.9.1", - "hex_fmt", - "libp2p", - "prometheus-client", - "quick-protobuf", - "quick-protobuf-codec", - "quickcheck", - "rand 0.8.5", - "regex", - "serde", - "sha2 0.10.8", - "tracing", - "void", - "web-time", -] - [[package]] name = "graffiti_file" version = "0.1.0" @@ -3635,8 +3507,8 @@ dependencies = [ "bls", "hex", "serde", - "slog", "tempfile", + "tracing", "types", ] @@ -3657,7 +3529,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.0", + "ff 0.13.1", "rand 0.8.5", "rand_core 0.6.4", "rand_xorshift", @@ -3676,7 +3548,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.1", + "indexmap 2.8.0", "slab", "tokio", "tokio-util", @@ -3685,17 +3557,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.2.0", - "indexmap 2.7.1", + "http 1.3.0", + "indexmap 2.8.0", "slab", "tokio", "tokio-util", @@ -3849,6 +3721,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hermit-abi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" + [[package]] name = "hex" version = "0.4.3" @@ -3866,9 +3744,9 @@ checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" [[package]] name = "hickory-proto" -version = "0.25.0-alpha.4" +version = "0.25.0-alpha.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d063c0692ee669aa6d261988aa19ca5510f1cc40e4f211024f50c888499a35d7" +checksum = "1d00147af6310f4392a31680db52a3ed45a2e0f68eb18e8c3fe5537ecc96d9e2" dependencies = [ "async-recursion", "async-trait", @@ -3881,9 +3759,9 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.8.5", + "rand 0.9.0", "socket2", - "thiserror 2.0.11", + "thiserror 2.0.12", "tinyvec", "tokio", "tracing", @@ -3892,9 +3770,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.25.0-alpha.4" +version = "0.25.0-alpha.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bc352e4412fb657e795f79b4efcf2bd60b59ee5ca0187f3554194cd1107a27" +checksum = "5762f69ebdbd4ddb2e975cd24690bf21fe6b2604039189c26acddbc427f12887" dependencies = [ "cfg-if", "futures-util", @@ -3903,10 +3781,10 @@ dependencies = [ "moka", "once_cell", "parking_lot 0.12.3", - "rand 0.8.5", + "rand 0.9.0", "resolv-conf", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -3993,9 +3871,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "0a761d192fbf18bdef69f5ceedd0d1333afcbda0ee23840373b8317570d23c65" dependencies = [ "bytes", "fnv", @@ -4020,18 +3898,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.0", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.2.0", + "futures-core", + "http 1.3.0", "http-body 1.0.1", "pin-project-lite", ] @@ -4069,7 +3947,6 @@ dependencies = [ "sensitive_url", "serde", "serde_json", - "slog", "slot_clock", "state_processing", "store", @@ -4078,6 +3955,7 @@ dependencies = [ "task_executor", "tokio", "tokio-stream", + "tracing", "tree_hash", "types", "warp", @@ -4097,10 +3975,10 @@ dependencies = [ "metrics", "reqwest", "serde", - "slog", "slot_clock", "store", "tokio", + "tracing", "types", "warp", "warp_utils", @@ -4108,9 +3986,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -4157,8 +4035,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.7", - "http 1.2.0", + "h2 0.4.8", + "http 1.3.0", "http-body 1.0.1", "httparse", "httpdate", @@ -4205,7 +4083,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.2.0", + "http 1.3.0", "http-body 1.0.1", "hyper 1.6.0", "pin-project-lite", @@ -4353,7 +4231,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4426,7 +4304,7 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http 1.2.0", + "http 1.3.0", "http-body-util", "hyper 1.6.0", "hyper-util", @@ -4447,7 +4325,7 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http 1.2.0", + "http 1.3.0", "http-body-util", "hyper 1.6.0", "hyper-util", @@ -4473,7 +4351,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "parity-scale-codec 3.6.12", + "parity-scale-codec 3.7.4", ] [[package]] @@ -4511,7 +4389,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4532,9 +4410,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "arbitrary", "equivalent", @@ -4559,8 +4437,8 @@ dependencies = [ "serde", "serde_json", "signing_method", - "slog", "tokio", + "tracing", "types", "url", "validator_dir", @@ -4570,9 +4448,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] @@ -4638,11 +4516,11 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi 0.5.0", "libc", "windows-sys 0.59.0", ] @@ -4682,9 +4560,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" @@ -4707,14 +4585,14 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.3.0" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "js-sys", "pem", - "ring 0.17.8", + "ring 0.17.14", "serde", "serde_json", "simple_asn1", @@ -4812,7 +4690,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lcli" -version = "6.0.1" +version = "7.0.0-beta.0" dependencies = [ "account_utils", "beacon_chain", @@ -4837,10 +4715,11 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "sloggers", "snap", "state_processing", "store", + "tracing", + "tracing-subscriber", "tree_hash", "types", "validator_dir", @@ -4871,33 +4750,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "libflate" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" -dependencies = [ - "adler32", - "core2", - "crc32fast", - "dary_heap", - "libflate_lz77", -] - -[[package]] -name = "libflate_lz77" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" -dependencies = [ - "core2", - "hashbrown 0.14.5", - "rle-decode-fast", -] +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libloading" @@ -4922,7 +4777,7 @@ source = "git+https://github.com/sigp/libmdbx-rs?rev=e6ff4b9377c1619bcf0bfdf52be dependencies = [ "bitflags 1.3.2", "byteorder", - "derive_more 0.99.18", + "derive_more 0.99.19", "indexmap 1.9.3", "libc", "mdbx-sys", @@ -4959,7 +4814,7 @@ dependencies = [ "multiaddr", "pin-project", "rw-stream-sink", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -5004,7 +4859,7 @@ dependencies = [ "quick-protobuf", "rand 0.8.5", "rw-stream-sink", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "unsigned-varint 0.8.0", "web-time", @@ -5026,6 +4881,36 @@ dependencies = [ "tracing", ] +[[package]] +name = "libp2p-gossipsub" +version = "0.48.1" +source = "git+https://github.com/sigp/rust-libp2p.git?branch=sigp-gossipsub#3e24b1bbec5fae182595aee0958f823be87afaad" +dependencies = [ + "async-channel 2.3.1", + "asynchronous-codec", + "base64 0.22.1", + "byteorder", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "getrandom 0.2.15", + "hashlink 0.9.1", + "hex_fmt", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "prometheus-client", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "regex", + "sha2 0.10.8", + "tracing", + "web-time", +] + [[package]] name = "libp2p-identify" version = "0.46.0" @@ -5043,7 +4928,7 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", ] @@ -5141,7 +5026,7 @@ dependencies = [ "rand 0.8.5", "snow", "static_assertions", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "x25519-dalek", "zeroize", @@ -5177,10 +5062,10 @@ dependencies = [ "libp2p-tls", "quinn", "rand 0.8.5", - "ring 0.17.8", - "rustls 0.23.22", + "ring 0.17.14", + "rustls 0.23.23", "socket2", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -5217,7 +5102,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5238,19 +5123,19 @@ dependencies = [ [[package]] name = "libp2p-tls" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaebc1069dea12c5b86a597eaaddae0317c2c2cb9ec99dc94f82fd340f5c78b" +checksum = "42bbf5084fb44133267ad4caaa72a253d68d709edd2ed1cf9b42431a8ead8fd5" dependencies = [ "futures", "futures-rustls", "libp2p-core", "libp2p-identity", "rcgen", - "ring 0.17.8", - "rustls 0.23.22", + "ring 0.17.14", + "rustls 0.23.23", "rustls-webpki 0.101.7", - "thiserror 2.0.11", + "thiserror 2.0.12", "x509-parser", "yasna", ] @@ -5279,7 +5164,7 @@ dependencies = [ "either", "futures", "libp2p-core", - "thiserror 2.0.11", + "thiserror 2.0.12", "tracing", "yamux 0.12.1", "yamux 0.13.4", @@ -5291,7 +5176,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "libc", ] @@ -5367,7 +5252,7 @@ dependencies = [ [[package]] name = "lighthouse" -version = "6.0.1" +version = "7.0.0-beta.0" dependencies = [ "account_manager", "account_utils", @@ -5398,10 +5283,11 @@ dependencies = [ "serde_yaml", "slasher", "slashing_protection", - "slog", "store", "task_executor", "tempfile", + "tracing", + "tracing-subscriber", "types", "unused_port", "validator_client", @@ -5416,22 +5302,21 @@ version = "0.2.0" dependencies = [ "alloy-primitives", "alloy-rlp", - "async-channel", + "async-channel 1.9.0", "bytes", "delay_map", "directory", "dirs", "discv5", "either", - "eth2", "ethereum_ssz", "ethereum_ssz_derive", "fnv", "futures", - "gossipsub", "hex", "itertools 0.10.5", "libp2p", + "libp2p-gossipsub", "libp2p-mplex", "lighthouse_version", "local-ip-address", @@ -5447,9 +5332,6 @@ dependencies = [ "regex", "serde", "sha2 0.9.9", - "slog", - "slog-async", - "slog-term", "smallvec", "snap", "ssz_types", @@ -5461,36 +5343,11 @@ dependencies = [ "tokio", "tokio-io-timeout", "tokio-util", + "tracing", + "tracing-subscriber", "types", "unsigned-varint 0.8.0", "unused_port", - "void", -] - -[[package]] -name = "lighthouse_validator_store" -version = "0.1.0" -dependencies = [ - "account_utils", - "beacon_node_fallback", - "doppelganger_service", - "either", - "environment", - "eth2", - "futures", - "initialized_validators", - "logging", - "parking_lot 0.12.3", - "serde", - "signing_method", - "slashing_protection", - "slog", - "slot_clock", - "task_executor", - "tokio", - "types", - "validator_metrics", - "validator_store", ] [[package]] @@ -5515,10 +5372,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] -name = "litemap" -version = "0.7.4" +name = "linux-raw-sys" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lmdb-rkv" @@ -5573,23 +5436,21 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "logging" version = "0.2.0" dependencies = [ "chrono", + "logroller", "metrics", + "once_cell", "parking_lot 0.12.3", "serde", "serde_json", - "slog", - "slog-term", - "sloggers", - "take_mut", "tokio", "tracing", "tracing-appender", @@ -5598,6 +5459,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "logroller" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8dd932139da44917b3cd5812ed9536d985aa67203778e0507347579499f49c" +dependencies = [ + "chrono", + "flate2", + "regex", + "thiserror 1.0.69", +] + [[package]] name = "loom" version = "0.7.2" @@ -5675,22 +5548,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest 0.10.7", -] - [[package]] name = "mdbx-sys" version = "0.11.6-4" @@ -5704,9 +5561,9 @@ dependencies = [ [[package]] name = "mediatype" -version = "0.19.18" +version = "0.19.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8878cd8d1b3c8c8ae4b2ba0a36652b7cf192f618a599a7fbdfa25cffd4ea72dd" +checksum = "33746aadcb41349ec291e7f2f0a3aa6834d1d7c58066fb4b01f68efc4c4b7631" [[package]] name = "memchr" @@ -5765,36 +5622,15 @@ dependencies = [ "prometheus", ] -[[package]] -name = "migrations_internals" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" -dependencies = [ - "serde", - "toml 0.8.19", -] - -[[package]] -name = "migrations_macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" -dependencies = [ - "migrations_internals", - "proc-macro2", - "quote", -] - [[package]] name = "milhouse" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f68e33f98199224d1073f7c1468ea6abfea30736306fb79c7181a881e97ea32f" +checksum = "eb1ada1f56cc1c79f40517fdcbf57e19f60424a3a1ce372c3fe9b22e4fdd83eb" dependencies = [ "alloy-primitives", "arbitrary", - "derivative", + "educe", "ethereum_hashing", "ethereum_ssz", "ethereum_ssz_derive", @@ -5833,9 +5669,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -5859,21 +5695,21 @@ checksum = "9366861eb2a2c436c20b12c8dbec5f798cea6b47ad99216be0282942e2c81ea0" [[package]] name = "mockito" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "652cd6d169a36eaf9d1e6bce1a221130439a966d7f27858af66a33a66e9c4ee2" +checksum = "7760e0e418d9b7e5777c0374009ca4c93861b9066f18cb334a20ce50ab63aa48" dependencies = [ "assert-json-diff", "bytes", "colored", "futures-util", - "http 1.2.0", + "http 1.3.0", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", "hyper-util", "log", - "rand 0.8.5", + "rand 0.9.0", "regex", "serde_json", "serde_urlencoded", @@ -5897,7 +5733,7 @@ dependencies = [ "smallvec", "tagptr", "thiserror 1.0.69", - "uuid 1.12.1", + "uuid 1.15.1", ] [[package]] @@ -5913,10 +5749,10 @@ dependencies = [ "sensitive_url", "serde", "serde_json", - "slog", "store", "task_executor", "tokio", + "tracing", ] [[package]] @@ -5981,9 +5817,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", @@ -6069,7 +5905,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -6092,7 +5928,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "anyhow", - "async-channel", + "async-channel 1.9.0", "beacon_chain", "beacon_processor", "bls", @@ -6105,11 +5941,12 @@ dependencies = [ "fnv", "futures", "genesis", - "gossipsub", "hex", "igd-next 0.16.0", "itertools 0.10.5", + "k256 0.13.4", "kzg", + "libp2p-gossipsub", "lighthouse_network", "logging", "lru_cache", @@ -6118,11 +5955,8 @@ dependencies = [ "operation_pool", "parking_lot 0.12.3", "rand 0.8.5", + "rand_chacha 0.3.1", "serde_json", - "slog", - "slog-async", - "slog-term", - "sloggers", "slot_clock", "smallvec", "ssz_types", @@ -6131,6 +5965,8 @@ dependencies = [ "task_executor", "tokio", "tokio-stream", + "tracing", + "tracing-subscriber", "types", ] @@ -6162,7 +5998,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cfg-if", "cfg_aliases", "libc", @@ -6314,9 +6150,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" [[package]] name = "oneshot_broadcast" @@ -6327,9 +6163,9 @@ dependencies = [ [[package]] name = "oorandom" -version = "11.1.4" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opaque-debug" @@ -6364,11 +6200,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.70" +version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "cfg-if", "foreign-types", "libc", @@ -6385,7 +6221,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6396,18 +6232,18 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.4.1+3.4.0" +version = "300.4.2+3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" +checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.105" +version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", @@ -6481,15 +6317,17 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.12" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" dependencies = [ "arrayvec", "bitvec 1.0.1", "byte-slice-cast", + "const_format", "impl-trait-for-tuples", - "parity-scale-codec-derive 3.6.12", + "parity-scale-codec-derive 3.7.4", + "rustversion", "serde", ] @@ -6507,14 +6345,14 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.12" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.100", ] [[package]] @@ -6566,7 +6404,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.8", + "redox_syscall 0.5.10", "smallvec", "windows-targets 0.52.6", ] @@ -6611,9 +6449,9 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64 0.22.1", "serde", @@ -6641,7 +6479,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.11", + "thiserror 2.0.12", "ucd-trie", ] @@ -6655,42 +6493,24 @@ dependencies = [ "rustc_version 0.4.1", ] -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6727,9 +6547,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platforms" @@ -6805,38 +6625,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" - -[[package]] -name = "postgres-protocol" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" -dependencies = [ - "base64 0.22.1", - "byteorder", - "bytes", - "fallible-iterator", - "hmac 0.12.1", - "md-5", - "memchr", - "rand 0.9.0", - "sha2 0.10.8", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" -dependencies = [ - "bytes", - "fallible-iterator", - "postgres-protocol", -] +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "powerfmt" @@ -6846,21 +6637,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", -] - -[[package]] -name = "pq-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b51d65ebe1cb1f40641b15abae017fed35ccdda46e3dab1ff8768f625a3222" -dependencies = [ - "libc", - "vcpkg", + "zerocopy 0.8.23", ] [[package]] @@ -6873,12 +6654,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" dependencies = [ "proc-macro2", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6929,18 +6710,18 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.23", + "toml_edit 0.22.24", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -6995,18 +6776,18 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.8.0", + "bitflags 2.9.0", "lazy_static", "num-traits", "rand 0.8.5", @@ -7026,7 +6807,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7056,7 +6837,7 @@ checksum = "5e617cc9058daa5e1fe5a0d23ed745773a5ee354111dad1ec0235b0cc16b6730" dependencies = [ "cfg-if", "darwin-libproc", - "derive_more 0.99.18", + "derive_more 0.99.19", "glob", "mach2", "nix 0.24.3", @@ -7127,10 +6908,10 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.0", - "rustls 0.23.22", + "rustc-hash 2.1.1", + "rustls 0.23.23", "socket2", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -7144,12 +6925,12 @@ dependencies = [ "bytes", "getrandom 0.2.15", "rand 0.8.5", - "ring 0.17.8", - "rustc-hash 2.1.0", - "rustls 0.23.22", + "ring 0.17.14", + "rustc-hash 2.1.1", + "rustls 0.23.23", "rustls-pki-types", "slab", - "thiserror 2.0.11", + "thiserror 2.0.12", "tinyvec", "tracing", "web-time", @@ -7157,9 +6938,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" dependencies = [ "cfg_aliases", "libc", @@ -7171,9 +6952,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] @@ -7230,8 +7011,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.14", + "rand_core 0.9.3", + "zerocopy 0.8.23", ] [[package]] @@ -7251,7 +7032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -7265,12 +7046,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.1", - "zerocopy 0.8.14", ] [[package]] @@ -7304,12 +7084,13 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.11.3" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" dependencies = [ "pem", - "ring 0.16.20", + "ring 0.17.14", + "rustls-pki-types", "time", "yasna", ] @@ -7334,11 +7115,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -7426,7 +7207,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", + "sync_wrapper", "system-configuration 0.5.1", "tokio", "tokio-native-tls", @@ -7506,25 +7287,18 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", - "spin 0.9.8", "untrusted 0.9.0", "windows-sys 0.52.0", ] -[[package]] -name = "rle-decode-fast" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" - [[package]] name = "rlp" version = "0.5.2" @@ -7585,9 +7359,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.12.4" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" +checksum = "825df406ec217a8116bd7b06897c6cc8f65ffefc15d030ae2c9540acc9ed50b6" dependencies = [ "alloy-rlp", "arbitrary", @@ -7599,7 +7373,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", - "parity-scale-codec 3.6.12", + "parity-scale-codec 3.7.4", "primitive-types 0.12.2", "proptest", "rand 0.8.5", @@ -7632,9 +7406,9 @@ dependencies = [ [[package]] name = "rust_eth_kzg" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a237a478ee68e491a0f40bbcbb958b79ba9b37aacce459f7ab3ba78f3cbfa9d0" +checksum = "3f83b5559e1dcd3f7721838909288faf4500fb466eff98eac99b67ac04335b93" dependencies = [ "crate_crypto_internal_eth_kzg_bls12_381", "crate_crypto_internal_eth_kzg_erasure_codes", @@ -7658,9 +7432,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-hex" @@ -7683,7 +7457,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.25", + "semver 1.0.26", ] [[package]] @@ -7715,13 +7489,26 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.2", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.21.12" @@ -7729,7 +7516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring 0.17.14", "rustls-webpki 0.101.7", "sct", ] @@ -7741,7 +7528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.8", + "ring 0.17.14", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -7750,12 +7537,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.22" +version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "once_cell", - "ring 0.17.8", + "ring 0.17.14", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -7795,7 +7582,7 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", + "ring 0.17.14", "untrusted 0.9.0", ] @@ -7805,16 +7592,16 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.8", + "ring 0.17.14", "rustls-pki-types", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "rusty-fork" @@ -7841,9 +7628,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "safe_arith" @@ -7875,7 +7662,7 @@ checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "cfg-if", "derive_more 1.0.0", - "parity-scale-codec 3.6.12", + "parity-scale-codec 3.7.4", "scale-info-derive", ] @@ -7885,10 +7672,10 @@ version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -7939,7 +7726,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", + "ring 0.17.14", "untrusted 0.9.0", ] @@ -7977,7 +7764,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "core-foundation", "core-foundation-sys", "libc", @@ -8005,9 +7792,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] @@ -8021,12 +7808,6 @@ dependencies = [ "pest", ] -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - [[package]] name = "send_wrapper" version = "0.6.0" @@ -8043,9 +7824,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -8062,20 +7843,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -8083,34 +7864,15 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" -dependencies = [ - "itoa", - "serde", -] - [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", + "syn 2.0.100", ] [[package]] @@ -8125,35 +7887,13 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling 0.13.4", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "itoa", "ryu", "serde", @@ -8289,9 +8029,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "simple_asn1" @@ -8301,7 +8041,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", ] @@ -8311,25 +8051,22 @@ version = "0.2.0" dependencies = [ "clap", "env_logger 0.9.3", + "environment", "eth2_network_config", "execution_layer", "futures", "kzg", + "logging", "node_test_rig", "parking_lot 0.12.3", "rayon", "sensitive_url", "serde_json", "tokio", + "tracing-subscriber", "types", ] -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "slab" version = "0.4.9" @@ -8353,7 +8090,6 @@ dependencies = [ "libmdbx", "lmdb-rkv", "lmdb-rkv-sys", - "logging", "lru", "maplit", "metrics", @@ -8363,10 +8099,10 @@ dependencies = [ "redb", "safe_arith", "serde", - "slog", "ssz_types", "strum", "tempfile", + "tracing", "tree_hash", "tree_hash_derive", "types", @@ -8381,11 +8117,11 @@ dependencies = [ "lighthouse_network", "network", "slasher", - "slog", "slot_clock", "state_processing", "task_executor", "tokio", + "tracing", "types", ] @@ -8403,111 +8139,10 @@ dependencies = [ "serde", "serde_json", "tempfile", + "tracing", "types", ] -[[package]] -name = "slog" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" -dependencies = [ - "erased-serde", -] - -[[package]] -name = "slog-async" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84" -dependencies = [ - "crossbeam-channel", - "slog", - "take_mut", - "thread_local", -] - -[[package]] -name = "slog-json" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1e53f61af1e3c8b852eef0a9dee29008f55d6dd63794f3f12cef786cf0f219" -dependencies = [ - "serde", - "serde_json", - "slog", - "time", -] - -[[package]] -name = "slog-kvfilter" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae939ed7d169eed9699f4f5cd440f046f5dc5dfc27c19e3cd311619594c175e0" -dependencies = [ - "regex", - "slog", -] - -[[package]] -name = "slog-scope" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" -dependencies = [ - "arc-swap", - "lazy_static", - "slog", -] - -[[package]] -name = "slog-stdlog" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" -dependencies = [ - "log", - "slog", - "slog-scope", -] - -[[package]] -name = "slog-term" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" -dependencies = [ - "is-terminal", - "slog", - "term", - "thread_local", - "time", -] - -[[package]] -name = "sloggers" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75062c2738b82cd45ae633623caae3393f43eb00aada1dc2d3ebe88db6b0db9b" -dependencies = [ - "chrono", - "libc", - "libflate", - "once_cell", - "regex", - "serde", - "slog", - "slog-async", - "slog-json", - "slog-kvfilter", - "slog-scope", - "slog-stdlog", - "slog-term", - "trackable", - "winapi", - "windows-acl", -] - [[package]] name = "slot_clock" version = "0.2.0" @@ -8519,9 +8154,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" dependencies = [ "arbitrary", ] @@ -8543,7 +8178,7 @@ dependencies = [ "chacha20poly1305", "curve25519-dalek", "rand_core 0.6.4", - "ring 0.17.8", + "ring 0.17.14", "rustc_version 0.4.1", "sha2 0.10.8", "subtle", @@ -8593,12 +8228,11 @@ dependencies = [ [[package]] name = "ssz_types" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e0719d2b86ac738a55ae71a8429f52aa2741da988f1fd0975b4cc610fd1e08" +checksum = "dad0fa7e9a85c06d0a6ba5100d733fff72e231eb6db2d86078225cf716fd2d95" dependencies = [ "arbitrary", - "derivative", "ethereum_serde_utils", "ethereum_ssz", "itertools 0.13.0", @@ -8681,27 +8315,16 @@ dependencies = [ "redb", "safe_arith", "serde", - "slog", - "sloggers", "smallvec", "state_processing", "strum", "superstruct", "tempfile", + "tracing", + "tracing-subscriber", "types", "xdelta3", - "zstd 0.13.2", -] - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", + "zstd 0.13.3", ] [[package]] @@ -8781,9 +8404,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -8796,12 +8419,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - [[package]] name = "synstructure" version = "0.13.1" @@ -8810,7 +8427,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -8845,7 +8462,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "core-foundation", "system-configuration-sys 0.6.0", ] @@ -8887,12 +8504,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - [[package]] name = "tap" version = "1.0.1" @@ -8916,41 +8527,27 @@ checksum = "c63f48baada5c52e65a29eef93ab4f8982681b67f9e8d29c7b05abcfec2b9ffe" name = "task_executor" version = "0.1.0" dependencies = [ - "async-channel", + "async-channel 1.9.0", "futures", - "logging", "metrics", - "slog", - "sloggers", "tokio", "tracing", ] [[package]] name = "tempfile" -version = "3.16.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", "fastrand", "getrandom 0.3.1", "once_cell", - "rustix 0.38.44", + "rustix 1.0.2", "windows-sys 0.59.0", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - [[package]] name = "termcolor" version = "1.4.1" @@ -8962,22 +8559,14 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "rustix 0.38.44", + "rustix 1.0.2", "windows-sys 0.59.0", ] -[[package]] -name = "test-test_logger" -version = "0.1.0" -dependencies = [ - "logging", - "slog", -] - [[package]] name = "test_random_derive" version = "0.2.0" @@ -8986,23 +8575,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "testcontainers" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d2931d7f521af5bae989f716c3fa43a6af9af7ec7a5e21b59ae40878cec00" -dependencies = [ - "bollard-stubs", - "futures", - "hex", - "hmac 0.12.1", - "log", - "rand 0.8.5", - "serde", - "serde_json", - "sha2 0.10.8", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -9014,11 +8586,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -9029,18 +8601,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9095,9 +8667,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ "deranged", "itoa", @@ -9110,15 +8682,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" dependencies = [ "num-conv", "time-core", @@ -9129,10 +8701,10 @@ name = "timer" version = "0.2.0" dependencies = [ "beacon_chain", - "slog", "slot_clock", "task_executor", "tokio", + "tracing", ] [[package]] @@ -9185,9 +8757,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -9200,9 +8772,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" dependencies = [ "backtrace", "bytes", @@ -9234,7 +8806,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9247,32 +8819,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-postgres" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator", - "futures-channel", - "futures-util", - "log", - "parking_lot 0.12.3", - "percent-encoding", - "phf", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "rand 0.9.0", - "socket2", - "tokio", - "tokio-util", - "whoami", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -9330,26 +8876,11 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.23", -] - [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -9357,46 +8888,22 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.23" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", + "indexmap 2.8.0", "toml_datetime", - "winnow 0.7.0", + "winnow 0.7.3", ] -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper 1.0.2", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - [[package]] name = "tower-service" version = "0.3.3" @@ -9435,7 +8942,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9469,6 +8976,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.19" @@ -9479,54 +8996,40 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", -] - -[[package]] -name = "trackable" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" -dependencies = [ - "trackable_derive", -] - -[[package]] -name = "trackable_derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" -dependencies = [ - "quote", - "syn 1.0.109", + "tracing-serde", ] [[package]] name = "tree_hash" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373495c23db675a5192de8b610395e1bec324d596f9e6111192ce903dc11403a" +checksum = "6c58eb0f518840670270d90d97ffee702d8662d9c5494870c9e1e9e0fa00f668" dependencies = [ "alloy-primitives", "ethereum_hashing", + "ethereum_ssz", "smallvec", + "typenum", ] [[package]] name = "tree_hash_derive" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0857056ca4eb5de8c417309be42bcff6017b47e86fbaddde609b4633f66061e" +checksum = "699e7fb6b3fdfe0c809916f251cf5132d64966858601695c3736630a87e7166a" dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -9557,9 +9060,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "types" @@ -9584,7 +9087,6 @@ dependencies = [ "int_to_bytes", "itertools 0.10.5", "kzg", - "log", "maplit", "merkle_proof", "metastruct", @@ -9601,7 +9103,6 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "slog", "smallvec", "ssz_types", "state_processing", @@ -9610,6 +9111,7 @@ dependencies = [ "tempfile", "test_random_derive", "tokio", + "tracing", "tree_hash", "tree_hash_derive", ] @@ -9662,17 +9164,11 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" @@ -9683,12 +9179,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -9789,11 +9279,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.12.1" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.1", ] [[package]] @@ -9813,7 +9303,6 @@ dependencies = [ "graffiti_file", "hyper 1.6.0", "initialized_validators", - "lighthouse_validator_store", "metrics", "monitoring_api", "parking_lot 0.12.3", @@ -9821,9 +9310,9 @@ dependencies = [ "sensitive_url", "serde", "slashing_protection", - "slog", "slot_clock", "tokio", + "tracing", "types", "validator_http_api", "validator_http_metrics", @@ -9869,16 +9358,15 @@ dependencies = [ "health_metrics", "initialized_validators", "itertools 0.10.5", - "lighthouse_validator_store", "lighthouse_version", "logging", "parking_lot 0.12.3", "rand 0.8.5", "sensitive_url", "serde", + "serde_json", "signing_method", "slashing_protection", - "slog", "slot_clock", "sysinfo", "system_health", @@ -9886,6 +9374,7 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "tracing", "types", "url", "validator_dir", @@ -9901,17 +9390,18 @@ name = "validator_http_metrics" version = "0.1.0" dependencies = [ "health_metrics", - "lighthouse_validator_store", "lighthouse_version", + "logging", "malloc_utils", "metrics", "parking_lot 0.12.3", "serde", - "slog", "slot_clock", + "tracing", "types", "validator_metrics", "validator_services", + "validator_store", "warp", "warp_utils", ] @@ -9954,7 +9444,9 @@ version = "0.1.0" dependencies = [ "beacon_node_fallback", "bls", + "doppelganger_service", "either", + "environment", "eth2", "futures", "graffiti_file", @@ -9962,7 +9454,6 @@ dependencies = [ "parking_lot 0.12.3", "safe_arith", "slot_clock", - "task_executor", "tokio", "tracing", "tree_hash", @@ -9975,8 +9466,19 @@ dependencies = [ name = "validator_store" version = "0.1.0" dependencies = [ + "account_utils", + "doppelganger_service", + "initialized_validators", + "logging", + "parking_lot 0.12.3", + "serde", + "signing_method", "slashing_protection", + "slot_clock", + "task_executor", + "tracing", "types", + "validator_metrics", ] [[package]] @@ -9989,7 +9491,7 @@ dependencies = [ "regex", "sensitive_url", "serde_json", - "slog", + "tracing", "types", ] @@ -10017,17 +9519,11 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -10112,12 +9608,6 @@ dependencies = [ "wit-bindgen-rt", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -10140,7 +9630,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -10175,7 +9665,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10217,40 +9707,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "watch" -version = "0.1.0" -dependencies = [ - "axum", - "beacon_chain", - "beacon_node", - "bls", - "clap", - "clap_utils", - "diesel", - "diesel_migrations", - "env_logger 0.9.3", - "eth2", - "http_api", - "hyper 1.6.0", - "log", - "logging", - "network", - "r2d2", - "rand 0.8.5", - "reqwest", - "serde", - "serde_json", - "serde_yaml", - "task_executor", - "testcontainers", - "tokio", - "tokio-postgres", - "types", - "unused_port", - "url", -] - [[package]] name = "web-sys" version = "0.3.77" @@ -10276,13 +9732,12 @@ name = "web3signer_tests" version = "0.1.0" dependencies = [ "account_utils", - "async-channel", + "async-channel 1.9.0", "environment", "eth2_keystore", "eth2_network_config", "futures", "initialized_validators", - "lighthouse_validator_store", "logging", "parking_lot 0.12.3", "reqwest", @@ -10318,17 +9773,6 @@ dependencies = [ "rustix 0.38.44", ] -[[package]] -name = "whoami" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" -dependencies = [ - "redox_syscall 0.5.8", - "wasite", - "web-sys", -] - [[package]] name = "widestring" version = "0.4.3" @@ -10444,7 +9888,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -10455,9 +9899,15 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + [[package]] name = "windows-result" version = "0.1.2" @@ -10711,9 +10161,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] @@ -10734,7 +10184,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -10761,7 +10211,7 @@ dependencies = [ "log", "pharos", "rustc_version 0.4.1", - "send_wrapper 0.6.0", + "send_wrapper", "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", @@ -10912,7 +10362,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -10922,17 +10372,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" -version = "0.8.14" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ - "zerocopy-derive 0.8.14", + "zerocopy-derive 0.8.23", ] [[package]] @@ -10943,38 +10392,38 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "zerocopy-derive" -version = "0.8.14" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -10996,7 +10445,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -11018,7 +10467,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -11052,11 +10501,11 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "zstd-safe 7.2.1", + "zstd-safe 7.2.3", ] [[package]] @@ -11071,18 +10520,18 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.14+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 36f5efe2d8..bad374201d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ members = [ "beacon_node/http_api", "beacon_node/http_metrics", "beacon_node/lighthouse_network", - "beacon_node/lighthouse_network/gossipsub", "beacon_node/network", "beacon_node/operation_pool", "beacon_node/store", @@ -85,7 +84,6 @@ members = [ "testing/node_test_rig", "testing/simulator", "testing/state_transition_vectors", - "testing/test-test_logger", "testing/validator_test_rig", "testing/web3signer_tests", @@ -97,15 +95,13 @@ members = [ "validator_client/http_api", "validator_client/http_metrics", "validator_client/initialized_validators", - "validator_client/lighthouse_validator_store", "validator_client/signing_method", "validator_client/slashing_protection", "validator_client/validator_metrics", "validator_client/validator_services", + "validator_client/validator_store", "validator_manager", - - "watch", ] resolver = "2" @@ -134,13 +130,13 @@ delay_map = "0.4" derivative = "2" dirs = "3" either = "1.9" -rust_eth_kzg = "0.5.3" +rust_eth_kzg = "0.5.4" discv5 = { version = "0.9", features = ["libp2p"] } env_logger = "0.9" ethereum_hashing = "0.7.0" ethereum_serde_utils = "0.7" -ethereum_ssz = "0.7" -ethereum_ssz_derive = "0.7" +ethereum_ssz = "0.8.2" +ethereum_ssz_derive = "0.8.2" ethers-core = "1" ethers-providers = { version = "1", default-features = false } exit-future = "0.2" @@ -154,9 +150,10 @@ hyper = "1" itertools = "0.10" libsecp256k1 = "0.7" log = "0.4" +logroller = "0.1.4" lru = "0.12" maplit = "1" -milhouse = "0.3" +milhouse = "0.5" mockito = "1.5.0" num_cpus = "1" parking_lot = "0.12" @@ -184,17 +181,9 @@ serde_json = "1" serde_repr = "0.1" serde_yaml = "0.9" sha2 = "0.9" -slog = { version = "2", features = [ - "max_level_debug", - "release_max_level_debug", - "nested-values", -] } -slog-async = "2" -slog-term = "2" -sloggers = { version = "2", features = ["json"] } smallvec = { version = "1.11.2", features = ["arbitrary"] } snap = "1" -ssz_types = "0.8" +ssz_types = "0.10" strum = { version = "0.24", features = ["derive"] } superstruct = "0.8" syn = "1" @@ -212,9 +201,9 @@ tracing = "0.1.40" tracing-appender = "0.2" tracing-core = "0.1" tracing-log = "0.2" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tree_hash = "0.8" -tree_hash_derive = "0.8" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +tree_hash = "0.9" +tree_hash_derive = "0.9" url = "2" uuid = { version = "0.8", features = ["serde", "v4"] } warp = { version = "0.3.7", default-features = false, features = ["tls"] } @@ -233,6 +222,7 @@ compare_fields = { path = "common/compare_fields" } deposit_contract = { path = "common/deposit_contract" } directory = { path = "common/directory" } doppelganger_service = { path = "validator_client/doppelganger_service" } +validator_services = { path = "validator_client/validator_services" } environment = { path = "lighthouse/environment" } eth1 = { path = "beacon_node/eth1" } eth1_test_rig = { path = "testing/eth1_test_rig" } @@ -247,7 +237,6 @@ fixed_bytes = { path = "consensus/fixed_bytes" } filesystem = { path = "common/filesystem" } fork_choice = { path = "consensus/fork_choice" } genesis = { path = "beacon_node/genesis" } -gossipsub = { path = "beacon_node/lighthouse_network/gossipsub/" } health_metrics = { path = "common/health_metrics" } http_api = { path = "beacon_node/http_api" } initialized_validators = { path = "validator_client/initialized_validators" } @@ -255,7 +244,6 @@ int_to_bytes = { path = "consensus/int_to_bytes" } kzg = { path = "crypto/kzg" } metrics = { path = "common/metrics" } lighthouse_network = { path = "beacon_node/lighthouse_network" } -lighthouse_validator_store = { path = "validator_client/lighthouse_validator_store" } lighthouse_version = { path = "common/lighthouse_version" } lockfile = { path = "common/lockfile" } logging = { path = "common/logging" } @@ -286,7 +274,6 @@ validator_dir = { path = "common/validator_dir" } validator_http_api = { path = "validator_client/http_api" } validator_http_metrics = { path = "validator_client/http_metrics" } validator_metrics = { path = "validator_client/validator_metrics" } -validator_services = { path = "validator_client/validator_services" } validator_store = { path = "validator_client/validator_store" } validator_test_rig = { path = "testing/validator_test_rig" } warp_utils = { path = "common/warp_utils" } diff --git a/Makefile b/Makefile index f621f38a63..3282e4fa0e 100644 --- a/Makefile +++ b/Makefile @@ -250,7 +250,7 @@ install-audit: cargo install --force cargo-audit audit-CI: - cargo audit + cargo audit --ignore RUSTSEC-2025-0009 --ignore RUSTSEC-2024-0437 # Runs `cargo vendor` to make sure dependencies can be vendored for packaging, reproducibility and archival purpose. vendor: diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 7da65ad742..e30705719e 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "beacon_node" -version = "6.0.1" +version = "7.0.0-beta.0" authors = [ "Paul Hauner ", "Age Manning BeaconChain { epoch: Epoch, validators: Vec, ) -> Result { - debug!(self.log, "computing attestation rewards"; "epoch" => epoch, "validator_count" => validators.len()); + debug!( + %epoch, + validator_count = validators.len(), + "computing attestation rewards" + ); // Get state let state_slot = (epoch + 1).end_slot(T::EthSpec::slots_per_epoch()); @@ -214,10 +218,9 @@ impl BeaconChain { // Return 0s for unknown/inactive validator indices. let Ok(validator) = state.get_validator(validator_index) else { debug!( - self.log, - "No rewards for inactive/unknown validator"; - "index" => validator_index, - "epoch" => previous_epoch + index = validator_index, + epoch = %previous_epoch, + "No rewards for inactive/unknown validator" ); total_rewards.push(TotalAttestationRewards { validator_index: validator_index as u64, diff --git a/beacon_node/beacon_chain/src/attestation_simulator.rs b/beacon_node/beacon_chain/src/attestation_simulator.rs index c97c4490af..59d316578b 100644 --- a/beacon_node/beacon_chain/src/attestation_simulator.rs +++ b/beacon_node/beacon_chain/src/attestation_simulator.rs @@ -1,9 +1,9 @@ use crate::{BeaconChain, BeaconChainTypes}; -use slog::{debug, error}; use slot_clock::SlotClock; use std::sync::Arc; use task_executor::TaskExecutor; use tokio::time::sleep; +use tracing::{debug, error}; use types::{EthSpec, Slot}; /// Don't run the attestation simulator if the head slot is this many epochs @@ -36,10 +36,7 @@ async fn attestation_simulator_service( Some(duration) => { sleep(duration + additional_delay).await; - debug!( - chain.log, - "Simulating unagg. attestation production"; - ); + debug!("Simulating unagg. attestation production"); // Run the task in the executor let inner_chain = chain.clone(); @@ -53,7 +50,7 @@ async fn attestation_simulator_service( ); } None => { - error!(chain.log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; } @@ -85,10 +82,9 @@ pub fn produce_unaggregated_attestation( let data = unaggregated_attestation.data(); debug!( - chain.log, - "Produce unagg. attestation"; - "attestation_source" => data.source.root.to_string(), - "attestation_target" => data.target.root.to_string(), + attestation_source = data.source.root.to_string(), + attestation_target = data.target.root.to_string(), + "Produce unagg. attestation" ); chain @@ -98,9 +94,8 @@ pub fn produce_unaggregated_attestation( } Err(e) => { debug!( - chain.log, - "Failed to simulate attestation"; - "error" => ?e + error = ?e, + "Failed to simulate attestation" ); } } diff --git a/beacon_node/beacon_chain/src/attestation_verification.rs b/beacon_node/beacon_chain/src/attestation_verification.rs index a70a2caa4f..baacd93c45 100644 --- a/beacon_node/beacon_chain/src/attestation_verification.rs +++ b/beacon_node/beacon_chain/src/attestation_verification.rs @@ -43,7 +43,6 @@ use crate::{ use bls::verify_signature_sets; use itertools::Itertools; use proto_array::Block as ProtoBlock; -use slog::debug; use slot_clock::SlotClock; use state_processing::{ common::{ @@ -58,6 +57,7 @@ use state_processing::{ }; use std::borrow::Cow; use strum::AsRefStr; +use tracing::debug; use tree_hash::TreeHash; use types::{ Attestation, AttestationData, AttestationRef, BeaconCommittee, @@ -430,10 +430,9 @@ fn process_slash_info( Ok((indexed, _)) => (indexed, true, err), Err(e) => { debug!( - chain.log, - "Unable to obtain indexed form of attestation for slasher"; - "attestation_root" => format!("{:?}", attestation.tree_hash_root()), - "error" => format!("{:?}", e) + attestation_root = ?attestation.tree_hash_root(), + error = ?e, + "Unable to obtain indexed form of attestation for slasher" ); return err; } @@ -447,9 +446,8 @@ fn process_slash_info( if check_signature { if let Err(e) = verify_attestation_signature(chain, &indexed_attestation) { debug!( - chain.log, - "Signature verification for slasher failed"; - "error" => format!("{:?}", e), + error = ?e, + "Signature verification for slasher failed" ); return err; } @@ -1450,19 +1448,17 @@ where return Err(Error::UnknownTargetRoot(target.root)); } - chain - .with_committee_cache(target.root, attestation_epoch, |committee_cache, _| { - let committees_per_slot = committee_cache.committees_per_slot(); + chain.with_committee_cache(target.root, attestation_epoch, |committee_cache, _| { + let committees_per_slot = committee_cache.committees_per_slot(); - Ok(committee_cache - .get_beacon_committees_at_slot(attestation.data().slot) - .map(|committees| map_fn((committees, committees_per_slot))) - .unwrap_or_else(|_| { - Err(Error::NoCommitteeForSlotAndIndex { - slot: attestation.data().slot, - index: attestation.committee_index().unwrap_or(0), - }) - })) - }) - .map_err(BeaconChainError::from)? + Ok(committee_cache + .get_beacon_committees_at_slot(attestation.data().slot) + .map(|committees| map_fn((committees, committees_per_slot))) + .unwrap_or_else(|_| { + Err(Error::NoCommitteeForSlotAndIndex { + slot: attestation.data().slot, + index: attestation.committee_index().unwrap_or(0), + }) + })) + })? } diff --git a/beacon_node/beacon_chain/src/beacon_block_reward.rs b/beacon_node/beacon_chain/src/beacon_block_reward.rs index e0bb79bf38..591102126f 100644 --- a/beacon_node/beacon_chain/src/beacon_block_reward.rs +++ b/beacon_node/beacon_chain/src/beacon_block_reward.rs @@ -2,7 +2,6 @@ use crate::{BeaconChain, BeaconChainError, BeaconChainTypes, StateSkipConfig}; use attesting_indices_base::get_attesting_indices; use eth2::lighthouse::StandardBlockReward; use safe_arith::SafeArith; -use slog::error; use state_processing::common::attesting_indices_base; use state_processing::{ common::{ @@ -19,6 +18,7 @@ use store::{ consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR}, RelativeEpoch, }; +use tracing::error; use types::{AbstractExecPayload, BeaconBlockRef, BeaconState, BeaconStateError, EthSpec}; type BeaconBlockSubRewardValue = u64; @@ -56,9 +56,8 @@ impl BeaconChain { .compute_beacon_block_proposer_slashing_reward(block, state) .map_err(|e| { error!( - self.log, - "Error calculating proposer slashing reward"; - "error" => ?e + error = ?e, + "Error calculating proposer slashing reward" ); BeaconChainError::BlockRewardError })?; @@ -67,9 +66,8 @@ impl BeaconChain { .compute_beacon_block_attester_slashing_reward(block, state) .map_err(|e| { error!( - self.log, - "Error calculating attester slashing reward"; - "error" => ?e + error = ?e, + "Error calculating attester slashing reward" ); BeaconChainError::BlockRewardError })?; @@ -78,9 +76,8 @@ impl BeaconChain { self.compute_beacon_block_attestation_reward_base(block, state) .map_err(|e| { error!( - self.log, - "Error calculating base block attestation reward"; - "error" => ?e + error = ?e, + "Error calculating base block attestation reward" ); BeaconChainError::BlockRewardAttestationError })? @@ -88,9 +85,8 @@ impl BeaconChain { self.compute_beacon_block_attestation_reward_altair_deneb(block, state) .map_err(|e| { error!( - self.log, - "Error calculating altair block attestation reward"; - "error" => ?e + error = ?e, + "Error calculating altair block attestation reward" ); BeaconChainError::BlockRewardAttestationError })? diff --git a/beacon_node/beacon_chain/src/beacon_block_streamer.rs b/beacon_node/beacon_chain/src/beacon_block_streamer.rs index 73ef71da1d..e37a69040d 100644 --- a/beacon_node/beacon_chain/src/beacon_block_streamer.rs +++ b/beacon_node/beacon_chain/src/beacon_block_streamer.rs @@ -1,6 +1,6 @@ use crate::{metrics, BeaconChain, BeaconChainError, BeaconChainTypes, BlockProcessStatus}; use execution_layer::{ExecutionLayer, ExecutionPayloadBodyV1}; -use slog::{crit, debug, error, Logger}; +use logging::crit; use std::collections::HashMap; use std::sync::Arc; use store::{DatabaseBlock, ExecutionPayloadDeneb}; @@ -9,6 +9,7 @@ use tokio::sync::{ RwLock, }; use tokio_stream::{wrappers::UnboundedReceiverStream, Stream}; +use tracing::{debug, error}; use types::{ ChainSpec, EthSpec, ExecPayload, ExecutionBlockHash, ForkName, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, @@ -129,7 +130,6 @@ fn reconstruct_default_header_block( fn reconstruct_blocks( block_map: &mut HashMap>>, block_parts_with_bodies: HashMap>, - log: &Logger, ) { for (root, block_parts) in block_parts_with_bodies { if let Some(payload_body) = block_parts.body { @@ -156,7 +156,7 @@ fn reconstruct_blocks( reconstructed_transactions_root: header_from_payload .transactions_root(), }; - debug!(log, "Failed to reconstruct block"; "root" => ?root, "error" => ?error); + debug!(?root, ?error, "Failed to reconstruct block"); block_map.insert(root, Arc::new(Err(error))); } } @@ -232,7 +232,7 @@ impl BodiesByRange { } } - async fn execute(&mut self, execution_layer: &ExecutionLayer, log: &Logger) { + async fn execute(&mut self, execution_layer: &ExecutionLayer) { if let RequestState::UnSent(blocks_parts_ref) = &mut self.state { let block_parts_vec = std::mem::take(blocks_parts_ref); @@ -261,12 +261,12 @@ impl BodiesByRange { }); } - reconstruct_blocks(&mut block_map, with_bodies, log); + reconstruct_blocks(&mut block_map, with_bodies); } Err(e) => { let block_result = Arc::new(Err(Error::BlocksByRangeFailure(Box::new(e)).into())); - debug!(log, "Payload bodies by range failure"; "error" => ?block_result); + debug!(error = ?block_result, "Payload bodies by range failure"); for block_parts in block_parts_vec { block_map.insert(block_parts.root(), block_result.clone()); } @@ -280,9 +280,8 @@ impl BodiesByRange { &mut self, root: &Hash256, execution_layer: &ExecutionLayer, - log: &Logger, ) -> Option>> { - self.execute(execution_layer, log).await; + self.execute(execution_layer).await; if let RequestState::Sent(map) = &self.state { return map.get(root).cloned(); } @@ -313,7 +312,7 @@ impl EngineRequest { } } - pub async fn push_block_parts(&mut self, block_parts: BlockParts, log: &Logger) { + pub async fn push_block_parts(&mut self, block_parts: BlockParts) { match self { Self::ByRange(bodies_by_range) => { let mut request = bodies_by_range.write().await; @@ -327,28 +326,21 @@ impl EngineRequest { Self::NoRequest(_) => { // this should _never_ happen crit!( - log, - "Please notify the devs"; - "beacon_block_streamer" => "push_block_parts called on NoRequest Variant", + beacon_block_streamer = "push_block_parts called on NoRequest Variant", + "Please notify the devs" ); } } } - pub async fn push_block_result( - &mut self, - root: Hash256, - block_result: BlockResult, - log: &Logger, - ) { + pub async fn push_block_result(&mut self, root: Hash256, block_result: BlockResult) { // this function will only fail if something is seriously wrong match self { Self::ByRange(_) => { // this should _never_ happen crit!( - log, - "Please notify the devs"; - "beacon_block_streamer" => "push_block_result called on ByRange", + beacon_block_streamer = "push_block_result called on ByRange", + "Please notify the devs" ); } Self::NoRequest(results) => { @@ -361,24 +353,22 @@ impl EngineRequest { &self, root: &Hash256, execution_layer: &ExecutionLayer, - log: &Logger, ) -> Arc> { match self { Self::ByRange(by_range) => { by_range .write() .await - .get_block_result(root, execution_layer, log) + .get_block_result(root, execution_layer) .await } Self::NoRequest(map) => map.read().await.get(root).cloned(), } .unwrap_or_else(|| { crit!( - log, - "Please notify the devs"; - "beacon_block_streamer" => "block_result not found in request", - "root" => ?root, + beacon_block_streamer = "block_result not found in request", + ?root, + "Please notify the devs" ); Arc::new(Err(Error::BlockResultNotFound.into())) }) @@ -518,9 +508,7 @@ impl BeaconBlockStreamer { } }; - no_request - .push_block_result(root, block_result, &self.beacon_chain.log) - .await; + no_request.push_block_result(root, block_result).await; requests.insert(root, no_request.clone()); } @@ -529,9 +517,7 @@ impl BeaconBlockStreamer { by_range_blocks.sort_by_key(|block_parts| block_parts.slot()); for block_parts in by_range_blocks { let root = block_parts.root(); - by_range - .push_block_parts(block_parts, &self.beacon_chain.log) - .await; + by_range.push_block_parts(block_parts).await; requests.insert(root, by_range.clone()); } @@ -541,17 +527,12 @@ impl BeaconBlockStreamer { result.push((root, request.clone())) } else { crit!( - self.beacon_chain.log, - "Please notify the devs"; - "beacon_block_streamer" => "request not found", - "root" => ?root, + beacon_block_streamer = "request not found", + ?root, + "Please notify the devs" ); no_request - .push_block_result( - root, - Err(Error::RequestNotFound.into()), - &self.beacon_chain.log, - ) + .push_block_result(root, Err(Error::RequestNotFound.into())) .await; result.push((root, no_request.clone())); } @@ -566,10 +547,7 @@ impl BeaconBlockStreamer { block_roots: Vec, sender: UnboundedSender<(Hash256, Arc>)>, ) { - debug!( - self.beacon_chain.log, - "Using slower fallback method of eth_getBlockByHash()" - ); + debug!("Using slower fallback method of eth_getBlockByHash()"); for root in block_roots { let cached_block = self.check_caches(root); let block_result = if cached_block.is_some() { @@ -601,9 +579,8 @@ impl BeaconBlockStreamer { Ok(payloads) => payloads, Err(e) => { error!( - self.beacon_chain.log, - "BeaconBlockStreamer: Failed to load payloads"; - "error" => ?e + error = ?e, + "BeaconBlockStreamer: Failed to load payloads" ); return; } @@ -615,9 +592,7 @@ impl BeaconBlockStreamer { engine_requests += 1; } - let result = request - .get_block_result(&root, &self.execution_layer, &self.beacon_chain.log) - .await; + let result = request.get_block_result(&root, &self.execution_layer).await; let successful = result .as_ref() @@ -636,13 +611,12 @@ impl BeaconBlockStreamer { } debug!( - self.beacon_chain.log, - "BeaconBlockStreamer finished"; - "requested blocks" => n_roots, - "sent" => n_sent, - "succeeded" => n_success, - "failed" => (n_sent - n_success), - "engine requests" => engine_requests, + requested_blocks = n_roots, + sent = n_sent, + succeeded = n_success, + failed = (n_sent - n_success), + engine_requests, + "BeaconBlockStreamer finished" ); } @@ -678,9 +652,8 @@ impl BeaconBlockStreamer { ) -> impl Stream>)> { let (block_tx, block_rx) = mpsc::unbounded_channel(); debug!( - self.beacon_chain.log, - "Launching a BeaconBlockStreamer"; - "blocks" => block_roots.len(), + blocks = block_roots.len(), + "Launching a BeaconBlockStreamer" ); let executor = self.beacon_chain.task_executor.clone(); executor.spawn(self.stream(block_roots, block_tx), "get_blocks_sender"); @@ -691,9 +664,9 @@ impl BeaconBlockStreamer { async fn send_errors( block_roots: Vec, sender: UnboundedSender<(Hash256, Arc>)>, - unhandled_error: BeaconChainError, + beacon_chain_error: BeaconChainError, ) { - let result = Arc::new(Err(unhandled_error)); + let result = Arc::new(Err(beacon_chain_error)); for root in block_roots { if sender.send((root, result.clone())).is_err() { break; @@ -732,7 +705,6 @@ mod tests { let harness = BeaconChainHarness::builder(MinimalEthSpec) .spec(spec) .keypairs(KEYPAIRS[0..validator_count].to_vec()) - .logger(logging::test_logger()) .fresh_ephemeral_store() .mock_execution_layer() .build(); diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index ca21b519f1..0defbecf35 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -21,8 +21,8 @@ use crate::block_verification_types::{ pub use crate::canonical_head::CanonicalHead; use crate::chain_config::ChainConfig; use crate::data_availability_checker::{ - Availability, AvailabilityCheckError, AvailableBlock, DataAvailabilityChecker, - DataColumnReconstructionResult, + Availability, AvailabilityCheckError, AvailableBlock, AvailableBlockData, + DataAvailabilityChecker, DataColumnReconstructionResult, }; use crate::data_column_verification::{GossipDataColumnError, GossipVerifiedDataColumn}; use crate::early_attester_cache::EarlyAttesterCache; @@ -86,6 +86,7 @@ use futures::channel::mpsc::Sender; use itertools::process_results; use itertools::Itertools; use kzg::Kzg; +use logging::crit; use operation_pool::{ CompactAttestationRef, OperationPool, PersistedOperationPool, ReceivedPreCapella, }; @@ -93,7 +94,6 @@ use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; use proto_array::{DoNotReOrg, ProposerHeadError}; use safe_arith::SafeArith; use slasher::Slasher; -use slog::{crit, debug, error, info, trace, warn, Logger}; use slot_clock::SlotClock; use ssz::Encode; use state_processing::{ @@ -124,6 +124,7 @@ use store::{ use task_executor::{ShutdownReason, TaskExecutor}; use tokio::sync::oneshot; use tokio_stream::Stream; +use tracing::{debug, error, info, trace, warn}; use tree_hash::TreeHash; use types::blob_sidecar::FixedBlobSidecarList; use types::data_column_sidecar::{ColumnIndex, DataColumnIdentifier}; @@ -144,11 +145,6 @@ pub const FORK_CHOICE_DB_KEY: Hash256 = Hash256::ZERO; /// Defines how old a block can be before it's no longer a candidate for the early attester cache. const EARLY_ATTESTER_CACHE_HISTORIC_SLOTS: u64 = 4; -/// Defines a distance between the head block slot and the current slot. -/// -/// If the head block is older than this value, don't bother preparing beacon proposers. -const PREPARE_PROPOSER_HISTORIC_EPOCHS: u64 = 4; - /// If the head is more than `MAX_PER_SLOT_FORK_CHOICE_DISTANCE` slots behind the wall-clock slot, DO NOT /// run the per-slot tasks (primarily fork choice). /// @@ -485,8 +481,6 @@ pub struct BeaconChain { /// Sender given to tasks, so that if they encounter a state in which execution cannot /// continue they can request that everything shuts down. pub shutdown_sender: Sender, - /// Logging to CLI, etc. - pub(crate) log: Logger, /// Arbitrary bytes included in the blocks. pub(crate) graffiti_calculator: GraffitiCalculator, /// Optional slasher. @@ -671,7 +665,6 @@ impl BeaconChain { store: BeaconStore, reset_payload_statuses: ResetPayloadStatuses, spec: &ChainSpec, - log: &Logger, ) -> Result>, Error> { let Some(persisted_fork_choice) = store.get_item::(&FORK_CHOICE_DB_KEY)? @@ -687,7 +680,6 @@ impl BeaconChain { reset_payload_statuses, fc_store, spec, - log, )?)) } @@ -1218,9 +1210,8 @@ impl BeaconChain { if header_from_payload != execution_payload_header { for txn in execution_payload.transactions() { debug!( - self.log, - "Reconstructed txn"; - "bytes" => format!("0x{}", hex::encode(&**txn)), + bytes = format!("0x{}", hex::encode(&**txn)), + "Reconstructed txn" ); } @@ -1435,7 +1426,6 @@ impl BeaconChain { slot, &parent_root, &sync_aggregate, - &self.log, &self.spec, ) } @@ -1481,10 +1471,9 @@ impl BeaconChain { Ordering::Greater => { if slot > head_state.slot() + T::EthSpec::slots_per_epoch() { warn!( - self.log, - "Skipping more than an epoch"; - "head_slot" => head_state.slot(), - "request_slot" => slot + head_slot = %head_state.slot(), + request_slot = %slot, + "Skipping more than an epoch" ) } @@ -1503,11 +1492,10 @@ impl BeaconChain { Ok(_) => (), Err(e) => { warn!( - self.log, - "Unable to load state at slot"; - "error" => ?e, - "head_slot" => head_state_slot, - "requested_slot" => slot + error = ?e, + head_slot= %head_state_slot, + requested_slot = %slot, + "Unable to load state at slot" ); return Err(Error::NoStateForSlot(slot)); } @@ -1871,9 +1859,8 @@ impl BeaconChain { // The cache returned an error. Log the error and proceed with the rest of this // function. Err(e) => warn!( - self.log, - "Early attester cache failed"; - "error" => ?e + error = ?e, + "Early attester cache failed" ), } @@ -2018,11 +2005,10 @@ impl BeaconChain { cached_values } else { debug!( - self.log, - "Attester cache miss"; - "beacon_block_root" => ?beacon_block_root, - "head_state_slot" => %head_state_slot, - "request_slot" => %request_slot, + ?beacon_block_root, + %head_state_slot, + %request_slot, + "Attester cache miss" ); // Neither the head state, nor the attester cache was able to produce the required @@ -2282,30 +2268,27 @@ impl BeaconChain { match self.naive_aggregation_pool.write().insert(attestation) { Ok(outcome) => trace!( - self.log, - "Stored unaggregated attestation"; - "outcome" => ?outcome, - "index" => attestation.committee_index(), - "slot" => attestation.data().slot.as_u64(), + ?outcome, + index = attestation.committee_index(), + slot = attestation.data().slot.as_u64(), + "Stored unaggregated attestation" ), Err(NaiveAggregationError::SlotTooLow { slot, lowest_permissible_slot, }) => { trace!( - self.log, - "Refused to store unaggregated attestation"; - "lowest_permissible_slot" => lowest_permissible_slot.as_u64(), - "slot" => slot.as_u64(), + lowest_permissible_slot = lowest_permissible_slot.as_u64(), + slot = slot.as_u64(), + "Refused to store unaggregated attestation" ); } Err(e) => { error!( - self.log, - "Failed to store unaggregated attestation"; - "error" => ?e, - "index" => attestation.committee_index(), - "slot" => attestation.data().slot.as_u64(), + error = ?e, + index = attestation.committee_index(), + slot = attestation.data().slot.as_u64(), + "Failed to store unaggregated attestation" ); return Err(Error::from(e).into()); } @@ -2345,30 +2328,27 @@ impl BeaconChain { .insert(&contribution) { Ok(outcome) => trace!( - self.log, - "Stored unaggregated sync committee message"; - "outcome" => ?outcome, - "index" => sync_message.validator_index, - "slot" => sync_message.slot.as_u64(), + ?outcome, + index = sync_message.validator_index, + slot = sync_message.slot.as_u64(), + "Stored unaggregated sync committee message" ), Err(NaiveAggregationError::SlotTooLow { slot, lowest_permissible_slot, }) => { trace!( - self.log, - "Refused to store unaggregated sync committee message"; - "lowest_permissible_slot" => lowest_permissible_slot.as_u64(), - "slot" => slot.as_u64(), + lowest_permissible_slot = lowest_permissible_slot.as_u64(), + slot = slot.as_u64(), + "Refused to store unaggregated sync committee message" ); } Err(e) => { error!( - self.log, - "Failed to store unaggregated sync committee message"; - "error" => ?e, - "index" => sync_message.validator_index, - "slot" => sync_message.slot.as_u64(), + error = ?e, + index = sync_message.validator_index, + slot = sync_message.slot.as_u64(), + "Failed to store unaggregated sync committee message" ); return Err(Error::from(e).into()); } @@ -2461,11 +2441,10 @@ impl BeaconChain { self.shuffling_is_compatible_result(block_root, target_epoch, state) .unwrap_or_else(|e| { debug!( - self.log, - "Skipping attestation with incompatible shuffling"; - "block_root" => ?block_root, - "target_epoch" => target_epoch, - "reason" => ?e, + ?block_root, + %target_epoch, + reason = ?e, + "Skipping attestation with incompatible shuffling" ); false }) @@ -2506,11 +2485,10 @@ impl BeaconChain { } } else { debug!( - self.log, - "Skipping attestation with incompatible shuffling"; - "block_root" => ?block_root, - "target_epoch" => target_epoch, - "reason" => "target epoch less than block epoch" + ?block_root, + %target_epoch, + reason = "target epoch less than block epoch", + "Skipping attestation with incompatible shuffling" ); return Ok(false); }; @@ -2519,12 +2497,11 @@ impl BeaconChain { Ok(true) } else { debug!( - self.log, - "Skipping attestation with incompatible shuffling"; - "block_root" => ?block_root, - "target_epoch" => target_epoch, - "head_shuffling_id" => ?head_shuffling_id, - "block_shuffling_id" => ?block_shuffling_id, + ?block_root, + %target_epoch, + ?head_shuffling_id, + ?block_shuffling_id, + "Skipping attestation with incompatible shuffling" ); Ok(false) } @@ -2940,8 +2917,11 @@ impl BeaconChain { imported_blocks.push((block_root, block_slot)); } AvailabilityProcessingStatus::MissingComponents(slot, block_root) => { - warn!(self.log, "Blobs missing in response to range request"; - "block_root" => ?block_root, "slot" => slot); + warn!( + ?block_root, + %slot, + "Blobs missing in response to range request" + ); return ChainSegmentResult::Failed { imported_blocks, error: BlockError::AvailabilityCheck( @@ -2952,9 +2932,10 @@ impl BeaconChain { } } Err(BlockError::DuplicateFullyImported(block_root)) => { - debug!(self.log, - "Ignoring already known blocks while processing chain segment"; - "block_root" => ?block_root); + debug!( + ?block_root, + "Ignoring already known blocks while processing chain segment" + ); continue; } Err(error) => { @@ -2974,11 +2955,10 @@ impl BeaconChain { /// Only completed sampling results are received. Blocks are unavailable by default and should /// be pruned on finalization, on a timeout or by a max count. pub async fn process_sampling_completed(self: &Arc, block_root: Hash256) { - // TODO(das): update fork-choice + // TODO(das): update fork-choice, act on sampling result, adjust log level // NOTE: It is possible that sampling complets before block is imported into fork choice, // in that case we may need to update availability cache. - // TODO(das): These log levels are too high, reduce once DAS matures - info!(self.log, "Sampling completed"; "block_root" => %block_root); + info!(%block_root, "Sampling completed"); } /// Returns `Ok(GossipVerifiedBlock)` if the supplied `block` should be forwarded onto the @@ -3007,23 +2987,21 @@ impl BeaconChain { Ok(verified) => { let commitments_formatted = verified.block.commitments_formatted(); debug!( - chain.log, - "Successfully verified gossip block"; - "graffiti" => graffiti_string, - "slot" => slot, - "root" => ?verified.block_root(), - "commitments" => commitments_formatted, + graffiti = graffiti_string, + %slot, + root = ?verified.block_root(), + commitments = commitments_formatted, + "Successfully verified gossip block" ); Ok(verified) } Err(e) => { debug!( - chain.log, - "Rejected gossip block"; - "error" => e.to_string(), - "graffiti" => graffiti_string, - "slot" => slot, + error = e.to_string(), + graffiti = graffiti_string, + %slot, + "Rejected gossip block" ); Err(e) @@ -3170,7 +3148,14 @@ impl BeaconChain { return Err(BlockError::DuplicateFullyImported(block_root)); } - self.emit_sse_blob_sidecar_events(&block_root, blobs.iter().flatten().map(Arc::as_ref)); + // process_engine_blobs is called for both pre and post PeerDAS. However, post PeerDAS + // consumers don't expect the blobs event to fire erratically. + if !self + .spec + .is_peer_das_enabled_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch())) + { + self.emit_sse_blob_sidecar_events(&block_root, blobs.iter().flatten().map(Arc::as_ref)); + } let r = self .check_engine_blob_availability_and_import(slot, block_root, blobs, data_column_recv) @@ -3427,11 +3412,10 @@ impl BeaconChain { // The block was successfully verified and imported. Yay. Ok(status @ AvailabilityProcessingStatus::Imported(block_root)) => { debug!( - self.log, - "Beacon block imported"; - "block_root" => ?block_root, - "block_slot" => block_slot, - "source" => %block_source, + ?block_root, + %block_slot, + source = %block_source, + "Beacon block imported" ); // Increment the Prometheus counter for block processing successes. @@ -3440,20 +3424,14 @@ impl BeaconChain { Ok(status) } Ok(status @ AvailabilityProcessingStatus::MissingComponents(slot, block_root)) => { - debug!( - self.log, - "Beacon block awaiting blobs"; - "block_root" => ?block_root, - "block_slot" => slot, - ); + debug!(?block_root, %slot, "Beacon block awaiting blobs"); Ok(status) } Err(e @ BlockError::BeaconChainError(BeaconChainError::TokioJoin(_))) => { debug!( - self.log, - "Beacon block processing cancelled"; - "error" => ?e, + error = ?e, + "Beacon block processing cancelled" ); Err(e) } @@ -3461,19 +3439,14 @@ impl BeaconChain { // be partially verified or partially imported. Err(BlockError::BeaconChainError(e)) => { crit!( - self.log, - "Beacon block processing error"; - "error" => ?e, + error = ?e, + "Beacon block processing error" ); Err(BlockError::BeaconChainError(e)) } // The block failed verification. Err(other) => { - debug!( - self.log, - "Beacon block rejected"; - "reason" => other.to_string(), - ); + debug!(reason = other.to_string(), "Beacon block rejected"); Err(other) } } @@ -3500,31 +3473,24 @@ impl BeaconChain { // Log the PoS pandas if a merge transition just occurred. if payload_verification_outcome.is_valid_merge_transition_block { - info!(self.log, "{}", POS_PANDA_BANNER); + info!("{}", POS_PANDA_BANNER); + info!(slot = %block.slot(), "Proof of Stake Activated"); info!( - self.log, - "Proof of Stake Activated"; - "slot" => block.slot() + terminal_pow_block_hash = ?block + .message() + .execution_payload()? + .parent_hash() + .into_root(), ); info!( - self.log, ""; - "Terminal POW Block Hash" => ?block - .message() - .execution_payload()? - .parent_hash() - .into_root() + merge_transition_block_root = ?block.message().tree_hash_root(), ); info!( - self.log, ""; - "Merge Transition Block Root" => ?block.message().tree_hash_root() - ); - info!( - self.log, ""; - "Merge Transition Execution Hash" => ?block - .message() - .execution_payload()? - .block_hash() - .into_root() + merge_transition_execution_hash = ?block + .message() + .execution_payload()? + .block_hash() + .into_root(), ); } Ok(ExecutedBlock::new( @@ -3641,9 +3607,12 @@ impl BeaconChain { data_column_recv: Option>>, ) -> Result { self.check_blobs_for_slashability(block_root, &blobs)?; - let availability = - self.data_availability_checker - .put_engine_blobs(block_root, blobs, data_column_recv)?; + let availability = self.data_availability_checker.put_engine_blobs( + block_root, + slot.epoch(T::EthSpec::slots_per_epoch()), + blobs, + data_column_recv, + )?; self.process_availability(slot, availability, || Ok(())) .await @@ -3728,7 +3697,6 @@ impl BeaconChain { parent_eth1_finalization_data, confirmed_state_roots, consensus_context, - data_column_recv, } = import_data; // Record the time at which this block's blobs became available. @@ -3756,7 +3724,6 @@ impl BeaconChain { parent_block, parent_eth1_finalization_data, consensus_context, - data_column_recv, ) }, "payload_verification_handle", @@ -3795,7 +3762,6 @@ impl BeaconChain { parent_block: SignedBlindedBeaconBlock, parent_eth1_finalization_data: Eth1FinalizationData, mut consensus_context: ConsensusContext, - data_column_recv: Option>>, ) -> Result { // ----------------------------- BLOCK NOT YET ATTESTABLE ---------------------------------- // Everything in this initial section is on the hot path between processing the block and @@ -3893,15 +3859,14 @@ impl BeaconChain { if let Some(proto_block) = fork_choice.get_block(&block_root) { if let Err(e) = self.early_attester_cache.add_head_block( block_root, - signed_block.clone(), + &signed_block, proto_block, &state, &self.spec, ) { warn!( - self.log, - "Early attester cache insert failed"; - "error" => ?e + error = ?e, + "Early attester cache insert failed" ); } else { let attestable_timestamp = @@ -3913,19 +3878,14 @@ impl BeaconChain { ) } } else { - warn!( - self.log, - "Early attester block missing"; - "block_root" => ?block_root - ); + warn!(?block_root, "Early attester block missing"); } } // This block did not become the head, nothing to do. Ok(_) => (), Err(e) => error!( - self.log, - "Failed to compute head during block import"; - "error" => ?e + error = ?e, + "Failed to compute head during block import" ), } drop(fork_choice_timer); @@ -3962,26 +3922,19 @@ impl BeaconChain { // If the write fails, revert fork choice to the version from disk, else we can // end up with blocks in fork choice that are missing from disk. // See https://github.com/sigp/lighthouse/issues/2028 - let (_, signed_block, blobs, data_columns) = signed_block.deconstruct(); + let (_, signed_block, block_data) = signed_block.deconstruct(); - match self.get_blobs_or_columns_store_op( - block_root, - signed_block.epoch(), - blobs, - data_columns, - data_column_recv, - ) { + match self.get_blobs_or_columns_store_op(block_root, block_data) { Ok(Some(blobs_or_columns_store_op)) => { ops.push(blobs_or_columns_store_op); } Ok(None) => {} Err(e) => { error!( - self.log, - "Failed to store data columns into the database"; - "msg" => "Restoring fork choice from disk", - "error" => &e, - "block_root" => ?block_root + msg = "Restoring fork choice from disk", + error = &e, + ?block_root, + "Failed to store data columns into the database" ); return Err(self .handle_import_block_db_write_error(fork_choice) @@ -4004,10 +3957,9 @@ impl BeaconChain { if let Err(e) = self.store.do_atomically_with_block_and_blobs_cache(ops) { error!( - self.log, - "Database write failed!"; - "msg" => "Restoring fork choice from disk", - "error" => ?e, + msg = "Restoring fork choice from disk", + error = ?e, + "Database write failed!" ); return Err(self .handle_import_block_db_write_error(fork_choice) @@ -4043,7 +3995,7 @@ impl BeaconChain { &mut state, ) .unwrap_or_else(|e| { - error!(self.log, "error caching light_client data {:?}", e); + error!("error caching light_client data {:?}", e); }); } @@ -4101,13 +4053,11 @@ impl BeaconChain { ), &self.store, &self.spec, - &self.log, ) { crit!( - self.log, - "No stored fork choice found to restore from"; - "error" => ?e, - "warning" => "The database is likely corrupt now, consider --purge-db" + error = ?e, + warning = "The database is likely corrupt now, consider --purge-db", + "No stored fork choice found to restore from" ); Err(BlockError::BeaconChainError(e)) } else { @@ -4146,17 +4096,15 @@ impl BeaconChain { { let mut shutdown_sender = self.shutdown_sender(); crit!( - self.log, - "Weak subjectivity checkpoint verification failed while importing block!"; - "block_root" => ?block_root, - "parent_root" => ?block.parent_root(), - "old_finalized_epoch" => ?current_head_finalized_checkpoint.epoch, - "new_finalized_epoch" => ?new_finalized_checkpoint.epoch, - "weak_subjectivity_epoch" => ?wss_checkpoint.epoch, - "error" => ?e + ?block_root, + parent_root = ?block.parent_root(), + old_finalized_epoch = ?current_head_finalized_checkpoint.epoch, + new_finalized_epoch = ?new_finalized_checkpoint.epoch, + weak_subjectivity_epoch = ?wss_checkpoint.epoch, + error = ?e, + "Weak subjectivity checkpoint verification failed while importing block!" ); crit!( - self.log, "You must use the `--purge-db` flag to clear the database and restart sync. \ You may be on a hostile network." ); @@ -4225,11 +4173,10 @@ impl BeaconChain { } Err(e) => { warn!( - self.log, - "Unable to fetch sync committee"; - "epoch" => duty_epoch, - "purpose" => "validator monitor", - "error" => ?e, + epoch = %duty_epoch, + purpose = "validator monitor", + error = ?e, + "Unable to fetch sync committee" ); } } @@ -4241,11 +4188,10 @@ impl BeaconChain { Ok(indexed) => indexed, Err(e) => { debug!( - self.log, - "Failed to get indexed attestation"; - "purpose" => "validator monitor", - "attestation_slot" => attestation.data().slot, - "error" => ?e, + purpose = "validator monitor", + attestation_slot = %attestation.data().slot, + error = ?e, + "Failed to get indexed attestation" ); continue; } @@ -4297,10 +4243,9 @@ impl BeaconChain { Ok(_) | Err(AttestationObservationError::SlotTooLow { .. }) => {} Err(e) => { debug!( - self.log, - "Failed to register observed attestation"; - "error" => ?e, - "epoch" => a.data().target.epoch + error = ?e, + epoch = %a.data().target.epoch, + "Failed to register observed attestation" ); } } @@ -4309,11 +4254,10 @@ impl BeaconChain { Ok(indexed) => indexed, Err(e) => { debug!( - self.log, - "Failed to get indexed attestation"; - "purpose" => "observation", - "attestation_slot" => a.data().slot, - "error" => ?e, + purpose = "observation", + attestation_slot = %a.data().slot, + error = ?e, + "Failed to get indexed attestation" ); continue; } @@ -4326,11 +4270,10 @@ impl BeaconChain { .observe_validator(a.data().target.epoch, validator_index as usize) { debug!( - self.log, - "Failed to register observed block attester"; - "error" => ?e, - "epoch" => a.data().target.epoch, - "validator_index" => validator_index, + error = ?e, + epoch = %a.data().target.epoch, + validator_index, + "Failed to register observed block attester" ) } } @@ -4350,11 +4293,10 @@ impl BeaconChain { Ok(indexed) => indexed, Err(e) => { debug!( - self.log, - "Failed to get indexed attestation"; - "purpose" => "slasher", - "attestation_slot" => attestation.data().slot, - "error" => ?e, + purpose = "slasher", + attestation_slot = %attestation.data().slot, + error = ?e, + "Failed to get indexed attestation" ); continue; } @@ -4426,9 +4368,8 @@ impl BeaconChain { sync_aggregate.clone(), )) { warn!( - self.log, - "Failed to send light_client server event"; - "error" => ?e + error = ?e, + "Failed to send light_client server event" ); } } @@ -4445,9 +4386,8 @@ impl BeaconChain { ) { if let Err(e) = self.import_block_update_shuffling_cache_fallible(block_root, state) { warn!( - self.log, - "Failed to prime shuffling cache"; - "error" => ?e + error = ?e, + "Failed to prime shuffling cache" ); } } @@ -4523,10 +4463,9 @@ impl BeaconChain { let finalized_deposit_count = finalized_eth1_data.deposit_count; eth1_chain.finalize_eth1_data(finalized_eth1_data); debug!( - self.log, - "called eth1_chain.finalize_eth1_data()"; - "epoch" => current_finalized_checkpoint.epoch, - "deposit count" => finalized_deposit_count, + epoch = %current_finalized_checkpoint.epoch, + deposit_count = %finalized_deposit_count, + "called eth1_chain.finalize_eth1_data()" ); } } @@ -4549,36 +4488,32 @@ impl BeaconChain { match rx.wait_for_fork_choice(slot, timeout) { ForkChoiceWaitResult::Success(fc_slot) => { debug!( - self.log, - "Fork choice successfully updated before block production"; - "slot" => slot, - "fork_choice_slot" => fc_slot, + %slot, + fork_choice_slot = %fc_slot, + "Fork choice successfully updated before block production" ); } ForkChoiceWaitResult::Behind(fc_slot) => { warn!( - self.log, - "Fork choice notifier out of sync with block production"; - "fork_choice_slot" => fc_slot, - "slot" => slot, - "message" => "this block may be orphaned", + fork_choice_slot = %fc_slot, + %slot, + message = "this block may be orphaned", + "Fork choice notifier out of sync with block production" ); } ForkChoiceWaitResult::TimeOut => { warn!( - self.log, - "Timed out waiting for fork choice before proposal"; - "message" => "this block may be orphaned", + message = "this block may be orphaned", + "Timed out waiting for fork choice before proposal" ); } } } else { error!( - self.log, - "Producing block at incorrect slot"; - "block_slot" => slot, - "current_slot" => current_slot, - "message" => "check clock sync, this block may be orphaned", + %slot, + %current_slot, + message = "check clock sync, this block may be orphaned", + "Producing block at incorrect slot" ); } } @@ -4654,10 +4589,9 @@ impl BeaconChain { self.get_state_for_re_org(slot, head_slot, head_block_root) { info!( - self.log, - "Proposing block to re-org current head"; - "slot" => slot, - "head_to_reorg" => %head_block_root, + %slot, + head_to_reorg = %head_block_root, + "Proposing block to re-org current head" ); (re_org_state, Some(re_org_state_root)) } else { @@ -4672,10 +4606,9 @@ impl BeaconChain { } } else { warn!( - self.log, - "Producing block that conflicts with head"; - "message" => "this block is more likely to be orphaned", - "slot" => slot, + message = "this block is more likely to be orphaned", + %slot, + "Producing block that conflicts with head" ); let state = self .state_at_slot(slot - 1, StateSkipConfig::WithStateRoots) @@ -4703,9 +4636,8 @@ impl BeaconChain { if self.spec.proposer_score_boost.is_none() { warn!( - self.log, - "Ignoring proposer re-org configuration"; - "reason" => "this network does not have proposer boost enabled" + reason = "this network does not have proposer boost enabled", + "Ignoring proposer re-org configuration" ); return None; } @@ -4714,11 +4646,7 @@ impl BeaconChain { .slot_clock .seconds_from_current_slot_start() .or_else(|| { - warn!( - self.log, - "Not attempting re-org"; - "error" => "unable to read slot clock" - ); + warn!(error = "unable to read slot clock", "Not attempting re-org"); None })?; @@ -4729,21 +4657,13 @@ impl BeaconChain { // 3. The `get_proposer_head` conditions from fork choice pass. let proposing_on_time = slot_delay < self.config.re_org_cutoff(self.spec.seconds_per_slot); if !proposing_on_time { - debug!( - self.log, - "Not attempting re-org"; - "reason" => "not proposing on time", - ); + debug!(reason = "not proposing on time", "Not attempting re-org"); return None; } let head_late = self.block_observed_after_attestation_deadline(canonical_head, head_slot); if !head_late { - debug!( - self.log, - "Not attempting re-org"; - "reason" => "head not late" - ); + debug!(reason = "head not late", "Not attempting re-org"); return None; } @@ -4764,16 +4684,14 @@ impl BeaconChain { .map_err(|e| match e { ProposerHeadError::DoNotReOrg(reason) => { debug!( - self.log, - "Not attempting re-org"; - "reason" => %reason, + %reason, + "Not attempting re-org" ); } ProposerHeadError::Error(e) => { warn!( - self.log, - "Not attempting re-org"; - "error" => ?e, + error = ?e, + "Not attempting re-org" ); } }) @@ -4785,21 +4703,16 @@ impl BeaconChain { .store .get_advanced_hot_state_from_cache(re_org_parent_block, slot) .or_else(|| { - warn!( - self.log, - "Not attempting re-org"; - "reason" => "no state in cache" - ); + warn!(reason = "no state in cache", "Not attempting re-org"); None })?; info!( - self.log, - "Attempting re-org due to weak head"; - "weak_head" => ?canonical_head, - "parent" => ?re_org_parent_block, - "head_weight" => proposer_head.head_node.weight, - "threshold_weight" => proposer_head.re_org_head_weight_threshold + weak_head = ?canonical_head, + parent = ?re_org_parent_block, + head_weight = proposer_head.head_node.weight, + threshold_weight = proposer_head.re_org_head_weight_threshold, + "Attempting re-org due to weak head" ); Some((state, state_root)) @@ -4823,10 +4736,9 @@ impl BeaconChain { // The proposer head must be equal to the canonical head or its parent. if proposer_head != head_block_root && proposer_head != head_parent_block_root { warn!( - self.log, - "Unable to compute payload attributes"; - "block_root" => ?proposer_head, - "head_block_root" => ?head_block_root, + block_root = ?proposer_head, + head_block_root = ?head_block_root, + "Unable to compute payload attributes" ); return Ok(None); } @@ -4848,14 +4760,13 @@ impl BeaconChain { let proposer_index = if let Some(proposer) = cached_proposer { proposer.index as u64 } else { - if head_epoch + 2 < proposal_epoch { + if head_epoch + self.config.sync_tolerance_epochs < proposal_epoch { warn!( - self.log, - "Skipping proposer preparation"; - "msg" => "this is a non-critical issue that can happen on unhealthy nodes or \ + msg = "this is a non-critical issue that can happen on unhealthy nodes or \ networks.", - "proposal_epoch" => proposal_epoch, - "head_epoch" => head_epoch, + %proposal_epoch, + %head_epoch, + "Skipping proposer preparation" ); // Don't skip the head forward more than two epochs. This avoids burdening an @@ -4888,10 +4799,7 @@ impl BeaconChain { // // Exit now, after updating the cache. if decision_root != shuffling_decision_root { - warn!( - self.log, - "Head changed during proposer preparation"; - ); + warn!("Head changed during proposer preparation"); return Ok(None); } @@ -4953,10 +4861,9 @@ impl BeaconChain { // Advance the state using the partial method. debug!( - self.log, - "Advancing state for withdrawals calculation"; - "proposal_slot" => proposal_slot, - "parent_block_root" => ?parent_block_root, + %proposal_slot, + ?parent_block_root, + "Advancing state for withdrawals calculation" ); let mut advanced_state = unadvanced_state.into_owned(); partial_state_advance( @@ -4986,9 +4893,8 @@ impl BeaconChain { .or_else(|e| match e { ProposerHeadError::DoNotReOrg(reason) => { trace!( - self.log, - "Not suppressing fork choice update"; - "reason" => %reason, + %reason, + "Not suppressing fork choice update" ); Ok(canonical_forkchoice_params) } @@ -5071,10 +4977,9 @@ impl BeaconChain { .get_slot::(shuffling_decision_root, re_org_block_slot) .ok_or_else(|| { debug!( - self.log, - "Fork choice override proposer shuffling miss"; - "slot" => re_org_block_slot, - "decision_root" => ?shuffling_decision_root, + slot = %re_org_block_slot, + decision_root = ?shuffling_decision_root, + "Fork choice override proposer shuffling miss" ); DoNotReOrg::NotProposing })? @@ -5135,11 +5040,10 @@ impl BeaconChain { }; debug!( - self.log, - "Fork choice update overridden"; - "canonical_head" => ?head_block_root, - "override" => ?info.parent_node.root, - "slot" => fork_choice_slot, + canonical_head = ?head_block_root, + ?info.parent_node.root, + slot = %fork_choice_slot, + "Fork choice update overridden" ); Ok(forkchoice_update_params) @@ -5392,9 +5296,8 @@ impl BeaconChain { if let Err(e) = import(attestation) { // Don't stop block production if there's an error, just create a log. error!( - self.log, - "Attestation did not transfer to op pool"; - "reason" => ?e + reason = ?e, + "Attestation did not transfer to op pool" ); } } @@ -5442,11 +5345,10 @@ impl BeaconChain { ) .map_err(|e| { warn!( - self.log, - "Attempted to include an invalid attestation"; - "err" => ?e, - "block_slot" => state.slot(), - "attestation" => ?att + err = ?e, + block_slot = %state.slot(), + attestation = ?att, + "Attempted to include an invalid attestation" ); }) .is_ok() @@ -5458,11 +5360,10 @@ impl BeaconChain { .validate(&state, &self.spec) .map_err(|e| { warn!( - self.log, - "Attempted to include an invalid proposer slashing"; - "err" => ?e, - "block_slot" => state.slot(), - "slashing" => ?slashing + err = ?e, + block_slot = %state.slot(), + ?slashing, + "Attempted to include an invalid proposer slashing" ); }) .is_ok() @@ -5474,11 +5375,10 @@ impl BeaconChain { .validate(&state, &self.spec) .map_err(|e| { warn!( - self.log, - "Attempted to include an invalid attester slashing"; - "err" => ?e, - "block_slot" => state.slot(), - "slashing" => ?slashing + err = ?e, + block_slot = %state.slot(), + ?slashing, + "Attempted to include an invalid attester slashing" ); }) .is_ok() @@ -5489,11 +5389,10 @@ impl BeaconChain { .validate(&state, &self.spec) .map_err(|e| { warn!( - self.log, - "Attempted to include an invalid proposer slashing"; - "err" => ?e, - "block_slot" => state.slot(), - "exit" => ?exit + err = ?e, + block_slot = %state.slot(), + ?exit, + "Attempted to include an invalid proposer slashing" ); }) .is_ok() @@ -5511,9 +5410,8 @@ impl BeaconChain { .map_err(BlockProductionError::OpPoolError)? .unwrap_or_else(|| { warn!( - self.log, - "Producing block with no sync contributions"; - "slot" => state.slot(), + slot = %state.slot(), + "Producing block with no sync contributions" ); SyncAggregate::new() }); @@ -5833,11 +5731,7 @@ impl BeaconChain { ); let block_size = block.ssz_bytes_len(); - debug!( - self.log, - "Produced block on state"; - "block_size" => block_size, - ); + debug!(%block_size, "Produced block on state"); metrics::observe(&metrics::BLOCK_SIZE, block_size as f64); @@ -5919,11 +5813,10 @@ impl BeaconChain { metrics::inc_counter(&metrics::BLOCK_PRODUCTION_SUCCESSES); trace!( - self.log, - "Produced beacon block"; - "parent" => ?block.parent_root(), - "attestations" => block.body().attestations_len(), - "slot" => block.slot() + parent = ?block.parent_root(), + attestations = block.body().attestations_len(), + slot = %block.slot(), + "Produced beacon block" ); Ok(BeaconBlockResponse { @@ -5946,11 +5839,7 @@ impl BeaconChain { self: &Arc, op: &InvalidationOperation, ) -> Result<(), Error> { - debug!( - self.log, - "Processing payload invalidation"; - "op" => ?op, - ); + debug!(?op, "Processing payload invalidation"); // Update the execution status in fork choice. // @@ -5973,11 +5862,10 @@ impl BeaconChain { // Update fork choice. if let Err(e) = fork_choice_result { crit!( - self.log, - "Failed to process invalid payload"; - "error" => ?e, - "latest_valid_ancestor" => ?op.latest_valid_ancestor(), - "block_root" => ?op.block_root(), + error = ?e, + latest_valid_ancestor = ?op.latest_valid_ancestor(), + block_root = ?op.block_root(), + "Failed to process invalid payload" ); } @@ -6004,10 +5892,9 @@ impl BeaconChain { if justified_block.execution_status.is_invalid() { crit!( - self.log, - "The justified checkpoint is invalid"; - "msg" => "ensure you are not connected to a malicious network. This error is not \ - recoverable, please reach out to the lighthouse developers for assistance." + msg = "ensure you are not connected to a malicious network. This error is not \ + recoverable, please reach out to the lighthouse developers for assistance.", + "The justified checkpoint is invalid" ); let mut shutdown_sender = self.shutdown_sender(); @@ -6015,10 +5902,9 @@ impl BeaconChain { INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, )) { crit!( - self.log, - "Unable to trigger client shut down"; - "msg" => "shut down may already be under way", - "error" => ?e + msg = "shut down may already be under way", + error = ?e, + "Unable to trigger client shut down" ); } @@ -6079,25 +5965,19 @@ impl BeaconChain { // Use a blocking task since blocking the core executor on the canonical head read lock can // block the core tokio executor. let chain = self.clone(); + let tolerance_slots = self.config.sync_tolerance_epochs * T::EthSpec::slots_per_epoch(); let maybe_prep_data = self .spawn_blocking_handle( move || { let cached_head = chain.canonical_head.cached_head(); // Don't bother with proposer prep if the head is more than - // `PREPARE_PROPOSER_HISTORIC_EPOCHS` prior to the current slot. + // `sync_tolerance_epochs` prior to the current slot. // // This prevents the routine from running during sync. let head_slot = cached_head.head_slot(); - if head_slot + T::EthSpec::slots_per_epoch() * PREPARE_PROPOSER_HISTORIC_EPOCHS - < current_slot - { - debug!( - chain.log, - "Head too old for proposer prep"; - "head_slot" => head_slot, - "current_slot" => current_slot, - ); + if head_slot + tolerance_slots < current_slot { + debug!(%head_slot, %current_slot, "Head too old for proposer prep"); return Ok(None); } @@ -6185,11 +6065,10 @@ impl BeaconChain { // Only push a log to the user if this is the first time we've seen this proposer for // this slot. info!( - self.log, - "Prepared beacon proposer"; - "prepare_slot" => prepare_slot, - "validator" => proposer, - "parent_root" => ?head_root, + %prepare_slot, + validator = proposer, + parent_root = ?head_root, + "Prepared beacon proposer" ); payload_attributes }; @@ -6219,10 +6098,9 @@ impl BeaconChain { // // This scenario might occur on an overloaded/under-resourced node. warn!( - self.log, - "Delayed proposer preparation"; - "prepare_slot" => prepare_slot, - "validator" => proposer, + %prepare_slot, + validator = proposer, + "Delayed proposer preparation" ); return Ok(None); }; @@ -6233,10 +6111,9 @@ impl BeaconChain { || till_prepare_slot <= self.config.prepare_payload_lookahead { debug!( - self.log, - "Sending forkchoiceUpdate for proposer prep"; - "till_prepare_slot" => ?till_prepare_slot, - "prepare_slot" => prepare_slot + ?till_prepare_slot, + %prepare_slot, + "Sending forkchoiceUpdate for proposer prep" ); self.update_execution_engine_forkchoice( @@ -6338,8 +6215,8 @@ impl BeaconChain { .map_err(Error::ForkchoiceUpdate)? { info!( - self.log, - "Prepared POS transition block proposer"; "slot" => next_slot + slot = %next_slot, + "Prepared POS transition block proposer" ); ( params.head_root, @@ -6397,9 +6274,8 @@ impl BeaconChain { .await?; if let Err(e) = fork_choice_update_result { error!( - self.log, - "Failed to validate payload"; - "error" => ?e + error= ?e, + "Failed to validate payload" ) }; Ok(()) @@ -6413,11 +6289,10 @@ impl BeaconChain { // error. However, we create a log to bring attention to the issue. PayloadStatus::Accepted => { warn!( - self.log, - "Fork choice update received ACCEPTED"; - "msg" => "execution engine provided an unexpected response to a fork \ + msg = "execution engine provided an unexpected response to a fork \ choice update. although this is not a serious issue, please raise \ - an issue." + an issue.", + "Fork choice update received ACCEPTED" ); Ok(()) } @@ -6426,13 +6301,12 @@ impl BeaconChain { ref validation_error, } => { warn!( - self.log, - "Invalid execution payload"; - "validation_error" => ?validation_error, - "latest_valid_hash" => ?latest_valid_hash, - "head_hash" => ?head_hash, - "head_block_root" => ?head_block_root, - "method" => "fcU", + ?validation_error, + ?latest_valid_hash, + ?head_hash, + head_block_root = ?head_block_root, + method = "fcU", + "Invalid execution payload" ); match latest_valid_hash { @@ -6480,12 +6354,11 @@ impl BeaconChain { ref validation_error, } => { warn!( - self.log, - "Invalid execution payload block hash"; - "validation_error" => ?validation_error, - "head_hash" => ?head_hash, - "head_block_root" => ?head_block_root, - "method" => "fcU", + ?validation_error, + ?head_hash, + ?head_block_root, + method = "fcU", + "Invalid execution payload block hash" ); // The execution engine has stated that the head block is invalid, however it // hasn't returned a latest valid ancestor. @@ -6506,9 +6379,9 @@ impl BeaconChain { /// Returns `true` if the given slot is prior to the `bellatrix_fork_epoch`. pub fn slot_is_prior_to_bellatrix(&self, slot: Slot) -> bool { - self.spec.bellatrix_fork_epoch.map_or(true, |bellatrix| { - slot.epoch(T::EthSpec::slots_per_epoch()) < bellatrix - }) + self.spec + .bellatrix_fork_epoch + .is_none_or(|bellatrix| slot.epoch(T::EthSpec::slots_per_epoch()) < bellatrix) } /// Returns the value of `execution_optimistic` for `block`. @@ -6600,16 +6473,19 @@ impl BeaconChain { state: &BeaconState, ) -> Result<(), BeaconChainError> { let finalized_checkpoint = state.finalized_checkpoint(); - info!(self.log, "Verifying the configured weak subjectivity checkpoint"; "weak_subjectivity_epoch" => wss_checkpoint.epoch, "weak_subjectivity_root" => ?wss_checkpoint.root); + info!( + weak_subjectivity_epoch = %wss_checkpoint.epoch, + weak_subjectivity_root = ?wss_checkpoint.root, + "Verifying the configured weak subjectivity checkpoint" + ); // If epochs match, simply compare roots. if wss_checkpoint.epoch == finalized_checkpoint.epoch && wss_checkpoint.root != finalized_checkpoint.root { crit!( - self.log, - "Root found at the specified checkpoint differs"; - "weak_subjectivity_root" => ?wss_checkpoint.root, - "finalized_checkpoint_root" => ?finalized_checkpoint.root + weak_subjectivity_root = ?wss_checkpoint.root, + finalized_checkpoint_root = ?finalized_checkpoint.root, + "Root found at the specified checkpoint differs" ); return Err(BeaconChainError::WeakSubjectivtyVerificationFailure); } else if wss_checkpoint.epoch < finalized_checkpoint.epoch { @@ -6623,17 +6499,18 @@ impl BeaconChain { Some(root) => { if root != wss_checkpoint.root { crit!( - self.log, - "Root found at the specified checkpoint differs"; - "weak_subjectivity_root" => ?wss_checkpoint.root, - "finalized_checkpoint_root" => ?finalized_checkpoint.root + weak_subjectivity_root = ?wss_checkpoint.root, + finalized_checkpoint_root = ?finalized_checkpoint.root, + "Root found at the specified checkpoint differs" ); return Err(BeaconChainError::WeakSubjectivtyVerificationFailure); } } None => { - crit!(self.log, "The root at the start slot of the given epoch could not be found"; - "wss_checkpoint_slot" => ?slot); + crit!( + wss_checkpoint_slot = ?slot, + "The root at the start slot of the given epoch could not be found" + ); return Err(BeaconChainError::WeakSubjectivtyVerificationFailure); } } @@ -6648,11 +6525,7 @@ impl BeaconChain { /// `tokio::runtime::block_on` in certain cases. pub async fn per_slot_task(self: &Arc) { if let Some(slot) = self.slot_clock.now() { - debug!( - self.log, - "Running beacon chain per slot tasks"; - "slot" => ?slot - ); + debug!(?slot, "Running beacon chain per slot tasks"); // Always run the light-weight pruning tasks (these structures should be empty during // sync anyway). @@ -6678,10 +6551,9 @@ impl BeaconChain { if let Some(tx) = &chain.fork_choice_signal_tx { if let Err(e) = tx.notify_fork_choice_complete(slot) { warn!( - chain.log, - "Error signalling fork choice waiter"; - "error" => ?e, - "slot" => slot, + error = ?e, + %slot, + "Error signalling fork choice waiter" ); } } @@ -6774,10 +6646,9 @@ impl BeaconChain { drop(shuffling_cache); debug!( - self.log, - "Committee cache miss"; - "shuffling_id" => ?shuffling_epoch, - "head_block_root" => head_block_root.to_string(), + shuffling_id = ?shuffling_epoch, + head_block_root = head_block_root.to_string(), + "Committee cache miss" ); // If the block's state will be so far ahead of `shuffling_epoch` that even its @@ -7188,10 +7059,6 @@ impl BeaconChain { .is_peer_das_enabled_for_epoch(slot.epoch(T::EthSpec::slots_per_epoch())) } - pub fn logger(&self) -> &Logger { - &self.log - } - /// Gets the `LightClientBootstrap` object for a requested block root. /// /// Returns `None` when the state or block is not found in the database. @@ -7219,29 +7086,34 @@ impl BeaconChain { } } - fn get_blobs_or_columns_store_op( + pub(crate) fn get_blobs_or_columns_store_op( &self, block_root: Hash256, - block_epoch: Epoch, - blobs: Option>, - data_columns: Option>, - data_column_recv: Option>>, + block_data: AvailableBlockData, ) -> Result>, String> { - if self.spec.is_peer_das_enabled_for_epoch(block_epoch) { - // TODO(das) we currently store all subnet sampled columns. Tracking issue to exclude non - // custody columns: https://github.com/sigp/lighthouse/issues/6465 - let custody_columns_count = self.data_availability_checker.get_sampling_column_count(); + // TODO(das) we currently store all subnet sampled columns. Tracking issue to exclude non + // custody columns: https://github.com/sigp/lighthouse/issues/6465 + let _custody_columns_count = self.data_availability_checker.get_sampling_column_count(); - let custody_columns_available = data_columns - .as_ref() - .as_ref() - .is_some_and(|columns| columns.len() == custody_columns_count); - - let data_columns_to_persist = if custody_columns_available { - // If the block was made available via custody columns received from gossip / rpc, use them - // since we already have them. - data_columns - } else if let Some(data_column_recv) = data_column_recv { + match block_data { + AvailableBlockData::NoData => Ok(None), + AvailableBlockData::Blobs(blobs) => { + debug!( + %block_root, + count = blobs.len(), + "Writing blobs to store" + ); + Ok(Some(StoreOp::PutBlobs(block_root, blobs))) + } + AvailableBlockData::DataColumns(data_columns) => { + debug!( + %block_root, + count = data_columns.len(), + "Writing data columns to store" + ); + Ok(Some(StoreOp::PutDataColumns(block_root, data_columns))) + } + AvailableBlockData::DataColumnsRecv(data_column_recv) => { // Blobs were available from the EL, in this case we wait for the data columns to be computed (blocking). let _column_recv_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DATA_COLUMNS_WAIT); @@ -7251,34 +7123,18 @@ impl BeaconChain { let computed_data_columns = data_column_recv .blocking_recv() .map_err(|e| format!("Did not receive data columns from sender: {e:?}"))?; - Some(computed_data_columns) - } else { - // No blobs in the block. - None - }; - - if let Some(data_columns) = data_columns_to_persist { - if !data_columns.is_empty() { - debug!( - self.log, "Writing data_columns to store"; - "block_root" => %block_root, - "count" => data_columns.len(), - ); - return Ok(Some(StoreOp::PutDataColumns(block_root, data_columns))); - } - } - } else if let Some(blobs) = blobs { - if !blobs.is_empty() { debug!( - self.log, "Writing blobs to store"; - "block_root" => %block_root, - "count" => blobs.len(), + %block_root, + count = computed_data_columns.len(), + "Writing data columns to store" ); - return Ok(Some(StoreOp::PutBlobs(block_root, blobs))); + // TODO(das): Store only this node's custody columns + Ok(Some(StoreOp::PutDataColumns( + block_root, + computed_data_columns, + ))) } } - - Ok(None) } } @@ -7292,15 +7148,11 @@ impl Drop for BeaconChain { if let Err(e) = drop() { error!( - self.log, - "Failed to persist on BeaconChain drop"; - "error" => ?e + error = ?e, + "Failed to persist on BeaconChain drop" ) } else { - info!( - self.log, - "Saved beacon chain to disk"; - ) + info!("Saved beacon chain to disk") } } } diff --git a/beacon_node/beacon_chain/src/bellatrix_readiness.rs b/beacon_node/beacon_chain/src/bellatrix_readiness.rs index 500588953f..412870354b 100644 --- a/beacon_node/beacon_chain/src/bellatrix_readiness.rs +++ b/beacon_node/beacon_chain/src/bellatrix_readiness.rs @@ -171,7 +171,7 @@ impl BeaconChain { return BellatrixReadiness::NotSynced; } let params = MergeConfig::from_chainspec(&self.spec); - let current_difficulty = el.get_current_difficulty().await.ok(); + let current_difficulty = el.get_current_difficulty().await.ok().flatten(); BellatrixReadiness::Ready { config: params, current_difficulty, diff --git a/beacon_node/beacon_chain/src/blob_verification.rs b/beacon_node/beacon_chain/src/blob_verification.rs index 786b627bb7..fe9d8c6bfc 100644 --- a/beacon_node/beacon_chain/src/blob_verification.rs +++ b/beacon_node/beacon_chain/src/blob_verification.rs @@ -12,9 +12,9 @@ use crate::kzg_utils::{validate_blob, validate_blobs}; use crate::observed_data_sidecars::{DoNotObserve, ObservationStrategy, Observe}; use crate::{metrics, BeaconChainError}; use kzg::{Error as KzgError, Kzg, KzgCommitment}; -use slog::debug; use ssz_derive::{Decode, Encode}; use std::time::Duration; +use tracing::debug; use tree_hash::TreeHash; use types::blob_sidecar::BlobIdentifier; use types::{ @@ -504,10 +504,9 @@ pub fn validate_blob_sidecar_for_gossip %block_root, - "index" => %blob_index, + %block_root, + %blob_index, + "Proposer shuffling cache miss for blob verification" ); let (parent_state_root, mut parent_state) = chain .store diff --git a/beacon_node/beacon_chain/src/block_times_cache.rs b/beacon_node/beacon_chain/src/block_times_cache.rs index af122ccdc0..bd1adb7e40 100644 --- a/beacon_node/beacon_chain/src/block_times_cache.rs +++ b/beacon_node/beacon_chain/src/block_times_cache.rs @@ -173,7 +173,7 @@ impl BlockTimesCache { if block_times .timestamps .all_blobs_observed - .map_or(true, |prev| timestamp > prev) + .is_none_or(|prev| timestamp > prev) { block_times.timestamps.all_blobs_observed = Some(timestamp); } @@ -195,7 +195,7 @@ impl BlockTimesCache { .entry(block_root) .or_insert_with(|| BlockTimesCacheValue::new(slot)); let existing_timestamp = field(&mut block_times.timestamps); - if existing_timestamp.map_or(true, |prev| timestamp < prev) { + if existing_timestamp.is_none_or(|prev| timestamp < prev) { *existing_timestamp = Some(timestamp); } } diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 1265276376..88df48d0e9 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -74,7 +74,6 @@ use metrics::TryExt; use parking_lot::RwLockReadGuard; use proto_array::Block as ProtoBlock; use safe_arith::ArithError; -use slog::{debug, error, Logger}; use slot_clock::SlotClock; use ssz::Encode; use ssz_derive::{Decode, Encode}; @@ -94,6 +93,7 @@ use std::sync::Arc; use store::{Error as DBError, HotStateSummary, KeyValueStore, StoreOp}; use strum::AsRefStr; use task_executor::JoinHandle; +use tracing::{debug, error}; use types::{ data_column_sidecar::DataColumnSidecarError, BeaconBlockRef, BeaconState, BeaconStateError, BlobsList, ChainSpec, DataColumnSidecarList, Epoch, EthSpec, ExecutionBlockHash, FullPayload, @@ -924,12 +924,11 @@ impl GossipVerifiedBlock { let (mut parent, block) = load_parent(block, chain)?; debug!( - chain.log, - "Proposer shuffling cache miss"; - "parent_root" => ?parent.beacon_block_root, - "parent_slot" => parent.beacon_block.slot(), - "block_root" => ?block_root, - "block_slot" => block.slot(), + parent_root = ?parent.beacon_block_root, + parent_slot = %parent.beacon_block.slot(), + ?block_root, + block_slot = %block.slot(), + "Proposer shuffling cache miss" ); // The state produced is only valid for determining proposer/attester shuffling indices. @@ -1536,10 +1535,9 @@ impl ExecutionPendingBlock { // Expose Prometheus metrics. if let Err(e) = summary.observe_metrics() { error!( - chain.log, - "Failed to observe epoch summary metrics"; - "src" => "block_verification", - "error" => ?e + src = "block_verification", + error = ?e, + "Failed to observe epoch summary metrics" ); } summaries.push(summary); @@ -1567,9 +1565,8 @@ impl ExecutionPendingBlock { validator_monitor.process_validator_statuses(epoch, summary, &chain.spec) { error!( - chain.log, - "Failed to process validator statuses"; - "error" => ?e + error = ?e, + "Failed to process validator statuses" ); } } @@ -1609,12 +1606,8 @@ impl ExecutionPendingBlock { * invalid. */ - write_state( - &format!("state_pre_block_{}", block_root), - &state, - &chain.log, - ); - write_block(block.as_block(), block_root, &chain.log); + write_state(&format!("state_pre_block_{}", block_root), &state); + write_block(block.as_block(), block_root); let core_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_CORE); @@ -1647,11 +1640,7 @@ impl ExecutionPendingBlock { metrics::stop_timer(state_root_timer); - write_state( - &format!("state_post_block_{}", block_root), - &state, - &chain.log, - ); + write_state(&format!("state_post_block_{}", block_root), &state); /* * Check to ensure the state root on the block matches the one we have calculated. @@ -1707,7 +1696,6 @@ impl ExecutionPendingBlock { parent_eth1_finalization_data, confirmed_state_roots, consensus_context, - data_column_recv: None, }, payload_verification_handle, }) @@ -1943,19 +1931,17 @@ fn load_parent>( if !state.all_caches_built() { debug!( - chain.log, - "Parent state lacks built caches"; - "block_slot" => block.slot(), - "state_slot" => state.slot(), + block_slot = %block.slot(), + state_slot = %state.slot(), + "Parent state lacks built caches" ); } if block.slot() != state.slot() { debug!( - chain.log, - "Parent state is not advanced"; - "block_slot" => block.slot(), - "state_slot" => state.slot(), + block_slot = %block.slot(), + state_slot = %state.slot(), + "Parent state is not advanced" ); } @@ -2161,14 +2147,11 @@ pub fn verify_header_signature( } } -fn write_state(prefix: &str, state: &BeaconState, log: &Logger) { +fn write_state(prefix: &str, state: &BeaconState) { if WRITE_BLOCK_PROCESSING_SSZ { let mut state = state.clone(); let Ok(root) = state.canonical_root() else { - error!( - log, - "Unable to hash state for writing"; - ); + error!("Unable to hash state for writing"); return; }; let filename = format!("{}_slot_{}_root_{}.ssz", prefix, state.slot(), root); @@ -2181,16 +2164,15 @@ fn write_state(prefix: &str, state: &BeaconState, log: &Logger) { let _ = file.write_all(&state.as_ssz_bytes()); } Err(e) => error!( - log, - "Failed to log state"; - "path" => format!("{:?}", path), - "error" => format!("{:?}", e) + ?path, + error = ?e, + "Failed to log state" ), } } } -fn write_block(block: &SignedBeaconBlock, root: Hash256, log: &Logger) { +fn write_block(block: &SignedBeaconBlock, root: Hash256) { if WRITE_BLOCK_PROCESSING_SSZ { let filename = format!("block_slot_{}_root{}.ssz", block.slot(), root); let mut path = std::env::temp_dir().join("lighthouse"); @@ -2202,10 +2184,9 @@ fn write_block(block: &SignedBeaconBlock, root: Hash256, log: &Lo let _ = file.write_all(&block.as_ssz_bytes()); } Err(e) => error!( - log, - "Failed to log block"; - "path" => format!("{:?}", path), - "error" => format!("{:?}", e) + ?path, + error = ?e, + "Failed to log block" ), } } diff --git a/beacon_node/beacon_chain/src/block_verification_types.rs b/beacon_node/beacon_chain/src/block_verification_types.rs index 38d0fc708c..07ffae7712 100644 --- a/beacon_node/beacon_chain/src/block_verification_types.rs +++ b/beacon_node/beacon_chain/src/block_verification_types.rs @@ -7,11 +7,10 @@ use derivative::Derivative; use state_processing::ConsensusContext; use std::fmt::{Debug, Formatter}; use std::sync::Arc; -use tokio::sync::oneshot; use types::blob_sidecar::BlobIdentifier; use types::{ - BeaconBlockRef, BeaconState, BlindedPayload, BlobSidecarList, ChainSpec, DataColumnSidecarList, - Epoch, EthSpec, Hash256, RuntimeVariableList, SignedBeaconBlock, SignedBeaconBlockHeader, Slot, + BeaconBlockRef, BeaconState, BlindedPayload, BlobSidecarList, ChainSpec, Epoch, EthSpec, + Hash256, RuntimeVariableList, SignedBeaconBlock, SignedBeaconBlockHeader, Slot, }; /// A block that has been received over RPC. It has 2 internal variants: @@ -265,7 +264,6 @@ impl ExecutedBlock { /// A block that has completed all pre-deneb block processing checks including verification /// by an EL client **and** has all requisite blob data to be imported into fork choice. -#[derive(PartialEq)] pub struct AvailableExecutedBlock { pub block: AvailableBlock, pub import_data: BlockImportData, @@ -338,8 +336,7 @@ impl AvailabilityPendingExecutedBlock { } } -#[derive(Debug, Derivative)] -#[derivative(PartialEq)] +#[derive(Debug, PartialEq)] pub struct BlockImportData { pub block_root: Hash256, pub state: BeaconState, @@ -347,12 +344,6 @@ pub struct BlockImportData { pub parent_eth1_finalization_data: Eth1FinalizationData, pub confirmed_state_roots: Vec, pub consensus_context: ConsensusContext, - #[derivative(PartialEq = "ignore")] - /// An optional receiver for `DataColumnSidecarList`. - /// - /// This field is `Some` when data columns are being computed asynchronously. - /// The resulting `DataColumnSidecarList` will be sent through this receiver. - pub data_column_recv: Option>>, } impl BlockImportData { @@ -371,7 +362,6 @@ impl BlockImportData { }, confirmed_state_roots: vec![], consensus_context: ConsensusContext::new(Slot::new(0)), - data_column_recv: None, } } } diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 8d62478bea..78216770e5 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -27,11 +27,11 @@ use execution_layer::ExecutionLayer; use fork_choice::{ForkChoice, ResetPayloadStatuses}; use futures::channel::mpsc::Sender; use kzg::Kzg; +use logging::crit; use operation_pool::{OperationPool, PersistedOperationPool}; use parking_lot::{Mutex, RwLock}; use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold}; use slasher::Slasher; -use slog::{crit, debug, error, info, o, Logger}; use slot_clock::{SlotClock, TestingSlotClock}; use state_processing::{per_slot_processing, AllCaches}; use std::marker::PhantomData; @@ -39,6 +39,7 @@ use std::sync::Arc; use std::time::Duration; use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp}; use task_executor::{ShutdownReason, TaskExecutor}; +use tracing::{debug, error, info}; use types::{ BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, Checkpoint, Epoch, EthSpec, FixedBytesExtended, Hash256, Signature, SignedBeaconBlock, Slot, @@ -96,7 +97,6 @@ pub struct BeaconChainBuilder { validator_pubkey_cache: Option>, spec: Arc, chain_config: ChainConfig, - log: Option, beacon_graffiti: GraffitiOrigin, slasher: Option>>, // Pending I/O batch that is constructed during building and should be executed atomically @@ -140,7 +140,6 @@ where validator_pubkey_cache: None, spec: Arc::new(E::default_spec()), chain_config: ChainConfig::default(), - log: None, beacon_graffiti: GraffitiOrigin::default(), slasher: None, pending_io_batch: vec![], @@ -218,14 +217,6 @@ where self } - /// Sets the logger. - /// - /// Should generally be called early in the build chain. - pub fn logger(mut self, log: Logger) -> Self { - self.log = Some(log); - self - } - /// Sets the task executor. pub fn task_executor(mut self, task_executor: TaskExecutor) -> Self { self.task_executor = Some(task_executor); @@ -261,13 +252,7 @@ where /// /// May initialize several components; including the op_pool and finalized checkpoints. pub fn resume_from_db(mut self) -> Result { - let log = self.log.as_ref().ok_or("resume_from_db requires a log")?; - - info!( - log, - "Starting beacon chain"; - "method" => "resume" - ); + info!(method = "resume", "Starting beacon chain"); let store = self .store @@ -289,7 +274,6 @@ where self.chain_config.always_reset_payload_statuses, ), &self.spec, - log, ) .map_err(|e| format!("Unable to load fork choice from disk: {:?}", e))? .ok_or("Fork choice not found in store")?; @@ -451,19 +435,14 @@ where .store .clone() .ok_or("weak_subjectivity_state requires a store")?; - let log = self - .log - .as_ref() - .ok_or("weak_subjectivity_state requires a log")?; // Ensure the state is advanced to an epoch boundary. let slots_per_epoch = E::slots_per_epoch(); if weak_subj_state.slot() % slots_per_epoch != 0 { debug!( - log, - "Advancing checkpoint state to boundary"; - "state_slot" => weak_subj_state.slot(), - "block_slot" => weak_subj_block.slot(), + state_slot = %weak_subj_state.slot(), + block_slot = %weak_subj_block.slot(), + "Advancing checkpoint state to boundary" ); while weak_subj_state.slot() % slots_per_epoch != 0 { per_slot_processing(&mut weak_subj_state, None, &self.spec) @@ -731,7 +710,6 @@ where mut self, ) -> Result>, String> { - let log = self.log.ok_or("Cannot build without a logger")?; let slot_clock = self .slot_clock .ok_or("Cannot build without a slot_clock.")?; @@ -749,11 +727,8 @@ where let head_tracker = Arc::new(self.head_tracker.unwrap_or_default()); let beacon_proposer_cache: Arc> = <_>::default(); - let mut validator_monitor = ValidatorMonitor::new( - validator_monitor_config, - beacon_proposer_cache.clone(), - log.new(o!("service" => "val_mon")), - ); + let mut validator_monitor = + ValidatorMonitor::new(validator_monitor_config, beacon_proposer_cache.clone()); let current_slot = if slot_clock .is_prior_to_genesis() @@ -776,19 +751,17 @@ where Ok(None) => return Err("Head block not found in store".into()), Err(StoreError::SszDecodeError(_)) => { error!( - log, - "Error decoding head block"; - "message" => "This node has likely missed a hard fork. \ - It will try to revert the invalid blocks and keep running, \ - but any stray blocks and states will not be deleted. \ - Long-term you should consider re-syncing this node." + message = "This node has likely missed a hard fork. \ + It will try to revert the invalid blocks and keep running, \ + but any stray blocks and states will not be deleted. \ + Long-term you should consider re-syncing this node.", + "Error decoding head block" ); let (block_root, block) = revert_to_fork_boundary( current_slot, initial_head_block_root, store.clone(), &self.spec, - &log, )?; // Update head tracker. @@ -848,12 +821,8 @@ where })?; let migrator_config = self.store_migrator_config.unwrap_or_default(); - let store_migrator = BackgroundMigrator::new( - store.clone(), - migrator_config, - genesis_block_root, - log.clone(), - ); + let store_migrator = + BackgroundMigrator::new(store.clone(), migrator_config, genesis_block_root); if let Some(slot) = slot_clock.now() { validator_monitor.process_valid_state( @@ -978,9 +947,8 @@ where shuffling_cache: RwLock::new(ShufflingCache::new( shuffling_cache_size, head_shuffling_ids, - log.clone(), )), - eth1_finalization_cache: RwLock::new(Eth1FinalizationCache::new(log.clone())), + eth1_finalization_cache: RwLock::new(Eth1FinalizationCache::default()), beacon_proposer_cache, block_times_cache: <_>::default(), pre_finalization_block_cache: <_>::default(), @@ -993,12 +961,10 @@ where shutdown_sender: self .shutdown_sender .ok_or("Cannot build without a shutdown sender.")?, - log: log.clone(), graffiti_calculator: GraffitiCalculator::new( self.beacon_graffiti, self.execution_layer, slot_clock.slot_duration() * E::slots_per_epoch() as u32, - log.clone(), ), slasher: self.slasher.clone(), validator_monitor: RwLock::new(validator_monitor), @@ -1010,7 +976,6 @@ where store, self.import_all_data_columns, self.spec, - log.new(o!("service" => "data_availability_checker")), ) .map_err(|e| format!("Error initializing DataAvailabilityChecker: {:?}", e))?, ), @@ -1037,25 +1002,23 @@ where &head.beacon_state, ) { crit!( - log, - "Weak subjectivity checkpoint verification failed on startup!"; - "head_block_root" => format!("{}", head.beacon_block_root), - "head_slot" => format!("{}", head.beacon_block.slot()), - "finalized_epoch" => format!("{}", head.beacon_state.finalized_checkpoint().epoch), - "wss_checkpoint_epoch" => format!("{}", wss_checkpoint.epoch), - "error" => format!("{:?}", e), + head_block_root = %head.beacon_block_root, + head_slot = %head.beacon_block.slot(), + finalized_epoch = %head.beacon_state.finalized_checkpoint().epoch, + wss_checkpoint_epoch = %wss_checkpoint.epoch, + error = ?e, + "Weak subjectivity checkpoint verification failed on startup!" ); - crit!(log, "You must use the `--purge-db` flag to clear the database and restart sync. You may be on a hostile network."); + crit!("You must use the `--purge-db` flag to clear the database and restart sync. You may be on a hostile network."); return Err(format!("Weak subjectivity verification failed: {:?}", e)); } } info!( - log, - "Beacon chain initialized"; - "head_state" => format!("{}", head.beacon_state_root()), - "head_block" => format!("{}", head.beacon_block_root), - "head_slot" => format!("{}", head.beacon_block.slot()), + head_state = %head.beacon_state_root(), + head_block = %head.beacon_block_root, + head_slot = %head.beacon_block.slot(), + "Beacon chain initialized" ); // Check for states to reconstruct (in the background). @@ -1068,11 +1031,10 @@ where // Prune finalized execution payloads in the background. if beacon_chain.store.get_config().prune_payloads { let store = beacon_chain.store.clone(); - let log = log.clone(); beacon_chain.task_executor.spawn_blocking( move || { if let Err(e) = store.try_prune_execution_payloads(false) { - error!(log, "Error pruning payloads in background"; "error" => ?e); + error!( error = ?e,"Error pruning payloads in background"); } }, "prune_payloads_background", @@ -1105,13 +1067,7 @@ where /// Sets the `BeaconChain` eth1 back-end to produce predictably junk data when producing blocks. pub fn dummy_eth1_backend(mut self) -> Result { - let log = self - .log - .as_ref() - .ok_or("dummy_eth1_backend requires a log")?; - - let backend = - CachingEth1Backend::new(Eth1Config::default(), log.clone(), self.spec.clone())?; + let backend = CachingEth1Backend::new(Eth1Config::default(), self.spec.clone())?; self.eth1_chain = Some(Eth1Chain::new_dummy(backend)); @@ -1186,7 +1142,6 @@ mod test { use genesis::{ generate_deterministic_keypairs, interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH, }; - use sloggers::{null::NullLoggerBuilder, Build}; use ssz::Encode; use std::time::Duration; use store::config::StoreConfig; @@ -1197,27 +1152,16 @@ mod test { type TestEthSpec = MinimalEthSpec; type Builder = BeaconChainBuilder>; - fn get_logger() -> Logger { - let builder = NullLoggerBuilder; - builder.build().expect("should build logger") - } - #[test] fn recent_genesis() { let validator_count = 1; let genesis_time = 13_371_337; - let log = get_logger(); let store: HotColdDB< MinimalEthSpec, MemoryStore, MemoryStore, - > = HotColdDB::open_ephemeral( - StoreConfig::default(), - ChainSpec::minimal().into(), - log.clone(), - ) - .unwrap(); + > = HotColdDB::open_ephemeral(StoreConfig::default(), ChainSpec::minimal().into()).unwrap(); let spec = MinimalEthSpec::default_spec(); let genesis_state = interop_genesis_state( @@ -1235,7 +1179,6 @@ mod test { let kzg = get_kzg(&spec); let chain = Builder::new(MinimalEthSpec, kzg) - .logger(log.clone()) .store(Arc::new(store)) .task_executor(runtime.task_executor.clone()) .genesis_state(genesis_state) diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 4e21372efb..bac47f5da7 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -47,14 +47,15 @@ use fork_choice::{ ResetPayloadStatuses, }; use itertools::process_results; +use logging::crit; use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use state_processing::AllCaches; use std::sync::Arc; use std::time::Duration; use store::{iter::StateRootsIterator, KeyValueStoreOp, StoreItem}; use task_executor::{JoinHandle, ShutdownReason}; +use tracing::{debug, error, info, warn}; use types::*; /// Simple wrapper around `RwLock` that uses private visibility to prevent any other modules from @@ -286,10 +287,9 @@ impl CanonicalHead { reset_payload_statuses: ResetPayloadStatuses, store: &BeaconStore, spec: &ChainSpec, - log: &Logger, ) -> Result<(), Error> { let fork_choice = - >::load_fork_choice(store.clone(), reset_payload_statuses, spec, log)? + >::load_fork_choice(store.clone(), reset_payload_statuses, spec)? .ok_or(Error::MissingPersistedForkChoice)?; let fork_choice_view = fork_choice.cached_fork_choice_view(); let beacon_block_root = fork_choice_view.head_block_root; @@ -475,9 +475,8 @@ impl BeaconChain { match self.slot() { Ok(current_slot) => self.recompute_head_at_slot(current_slot).await, Err(e) => error!( - self.log, - "No slot when recomputing head"; - "error" => ?e + error = ?e, + "No slot when recomputing head" ), } } @@ -515,18 +514,13 @@ impl BeaconChain { Ok(Some(())) => (), // The async task did not complete successfully since the runtime is shutting down. Ok(None) => { - debug!( - self.log, - "Did not update EL fork choice"; - "info" => "shutting down" - ); + debug!(info = "shutting down", "Did not update EL fork choice"); } // The async task did not complete successfully, tokio returned an error. Err(e) => { error!( - self.log, - "Did not update EL fork choice"; - "error" => ?e + error = ?e, + "Did not update EL fork choice" ); } }, @@ -534,17 +528,15 @@ impl BeaconChain { Ok(Err(e)) => { metrics::inc_counter(&metrics::FORK_CHOICE_ERRORS); error!( - self.log, - "Error whist recomputing head"; - "error" => ?e + error = ?e, + "Error whist recomputing head" ); } // There was an error spawning the task. Err(e) => { error!( - self.log, - "Failed to spawn recompute head task"; - "error" => ?e + error = ?e, + "Failed to spawn recompute head task" ); } } @@ -627,9 +619,8 @@ impl BeaconChain { // nothing to do. if new_view == old_view { debug!( - self.log, - "No change in canonical head"; - "head" => ?new_view.head_block_root + head = ?new_view.head_block_root, + "No change in canonical head" ); return Ok(None); } @@ -639,7 +630,7 @@ impl BeaconChain { let new_forkchoice_update_parameters = fork_choice_read_lock.get_forkchoice_update_parameters(); - perform_debug_logging::(&old_view, &new_view, &fork_choice_read_lock, &self.log); + perform_debug_logging::(&old_view, &new_view, &fork_choice_read_lock); // Drop the read lock, it's no longer required and holding it any longer than necessary // will just cause lock contention. @@ -732,9 +723,8 @@ impl BeaconChain { self.after_new_head(&old_cached_head, &new_cached_head, new_head_proto_block) { crit!( - self.log, - "Error updating canonical head"; - "error" => ?e + error = ?e, + "Error updating canonical head" ); } } @@ -751,9 +741,8 @@ impl BeaconChain { self.after_finalization(&new_cached_head, new_view, finalized_proto_block) { crit!( - self.log, - "Error updating finalization"; - "error" => ?e + error = ?e, + "Error updating finalization" ); } } @@ -791,7 +780,6 @@ impl BeaconChain { &new_snapshot.beacon_state, new_snapshot.beacon_block_root, &self.spec, - &self.log, ); // Determine if the new head is in a later epoch to the previous head. @@ -824,10 +812,9 @@ impl BeaconChain { .update_head_shuffling_ids(head_shuffling_ids), Err(e) => { error!( - self.log, - "Failed to get head shuffling ids"; - "error" => ?e, - "head_block_root" => ?new_snapshot.beacon_block_root + error = ?e, + head_block_root = ?new_snapshot.beacon_block_root, + "Failed to get head shuffling ids" ); } } @@ -844,7 +831,6 @@ impl BeaconChain { .as_utf8_lossy(), &self.slot_clock, self.event_handler.as_ref(), - &self.log, ); if is_epoch_transition || reorg_distance.is_some() { @@ -872,9 +858,8 @@ impl BeaconChain { } (Err(e), _) | (_, Err(e)) => { warn!( - self.log, - "Unable to find dependent roots, cannot register head event"; - "error" => ?e + error = ?e, + "Unable to find dependent roots, cannot register head event" ); } } @@ -1037,11 +1022,10 @@ fn check_finalized_payload_validity( ) -> Result<(), Error> { if let ExecutionStatus::Invalid(block_hash) = finalized_proto_block.execution_status { crit!( - chain.log, - "Finalized block has an invalid payload"; - "msg" => "You must use the `--purge-db` flag to clear the database and restart sync. \ + ?block_hash, + msg = "You must use the `--purge-db` flag to clear the database and restart sync. \ You may be on a hostile network.", - "block_hash" => ?block_hash + "Finalized block has an invalid payload" ); let mut shutdown_sender = chain.shutdown_sender(); shutdown_sender @@ -1083,38 +1067,34 @@ fn perform_debug_logging( old_view: &ForkChoiceView, new_view: &ForkChoiceView, fork_choice: &BeaconForkChoice, - log: &Logger, ) { if new_view.head_block_root != old_view.head_block_root { debug!( - log, - "Fork choice updated head"; - "new_head_weight" => ?fork_choice - .get_block_weight(&new_view.head_block_root), - "new_head" => ?new_view.head_block_root, - "old_head_weight" => ?fork_choice - .get_block_weight(&old_view.head_block_root), - "old_head" => ?old_view.head_block_root, + new_head_weight = ?fork_choice + .get_block_weight(&new_view.head_block_root), + new_head = ?new_view.head_block_root, + old_head_weight = ?fork_choice + .get_block_weight(&old_view.head_block_root), + old_head = ?old_view.head_block_root, + "Fork choice updated head" ) } if new_view.justified_checkpoint != old_view.justified_checkpoint { debug!( - log, - "Fork choice justified"; - "new_root" => ?new_view.justified_checkpoint.root, - "new_epoch" => new_view.justified_checkpoint.epoch, - "old_root" => ?old_view.justified_checkpoint.root, - "old_epoch" => old_view.justified_checkpoint.epoch, + new_root = ?new_view.justified_checkpoint.root, + new_epoch = %new_view.justified_checkpoint.epoch, + old_root = ?old_view.justified_checkpoint.root, + old_epoch = %old_view.justified_checkpoint.epoch, + "Fork choice justified" ) } if new_view.finalized_checkpoint != old_view.finalized_checkpoint { debug!( - log, - "Fork choice finalized"; - "new_root" => ?new_view.finalized_checkpoint.root, - "new_epoch" => new_view.finalized_checkpoint.epoch, - "old_root" => ?old_view.finalized_checkpoint.root, - "old_epoch" => old_view.finalized_checkpoint.epoch, + new_root = ?new_view.finalized_checkpoint.root, + new_epoch = %new_view.finalized_checkpoint.epoch, + old_root = ?old_view.finalized_checkpoint.root, + old_epoch = %old_view.finalized_checkpoint.epoch, + "Fork choice finalized" ) } } @@ -1149,9 +1129,8 @@ fn spawn_execution_layer_updates( .await { crit!( - chain.log, - "Failed to update execution head"; - "error" => ?e + error = ?e, + "Failed to update execution head" ); } @@ -1165,9 +1144,8 @@ fn spawn_execution_layer_updates( // know. if let Err(e) = chain.prepare_beacon_proposer(current_slot).await { crit!( - chain.log, - "Failed to prepare proposers after fork choice"; - "error" => ?e + error = ?e, + "Failed to prepare proposers after fork choice" ); } }, @@ -1188,7 +1166,6 @@ fn detect_reorg( new_state: &BeaconState, new_block_root: Hash256, spec: &ChainSpec, - log: &Logger, ) -> Option { let is_reorg = new_state .get_block_root(old_state.slot()) @@ -1199,11 +1176,7 @@ fn detect_reorg( match find_reorg_slot(old_state, old_block_root, new_state, new_block_root, spec) { Ok(slot) => old_state.slot().saturating_sub(slot), Err(e) => { - warn!( - log, - "Could not find re-org depth"; - "error" => format!("{:?}", e), - ); + warn!(error = ?e, "Could not find re-org depth"); return None; } }; @@ -1215,13 +1188,12 @@ fn detect_reorg( reorg_distance.as_u64() as i64, ); info!( - log, - "Beacon chain re-org"; - "previous_head" => ?old_block_root, - "previous_slot" => old_state.slot(), - "new_head" => ?new_block_root, - "new_slot" => new_state.slot(), - "reorg_distance" => reorg_distance, + previous_head = ?old_block_root, + previous_slot = %old_state.slot(), + new_head = ?new_block_root, + new_slot = %new_state.slot(), + %reorg_distance, + "Beacon chain re-org" ); Some(reorg_distance) @@ -1301,7 +1273,6 @@ fn observe_head_block_delays( head_block_graffiti: String, slot_clock: &S, event_handler: Option<&ServerSentEventHandler>, - log: &Logger, ) { let block_time_set_as_head = timestamp_now(); let head_block_root = head_block.root; @@ -1434,37 +1405,35 @@ fn observe_head_block_delays( if late_head { metrics::inc_counter(&metrics::BEACON_BLOCK_DELAY_HEAD_SLOT_START_EXCEEDED_TOTAL); debug!( - log, - "Delayed head block"; - "block_root" => ?head_block_root, - "proposer_index" => head_block_proposer_index, - "slot" => head_block_slot, - "total_delay_ms" => block_delay_total.as_millis(), - "observed_delay_ms" => format_delay(&block_delays.observed), - "blob_delay_ms" => format_delay(&block_delays.all_blobs_observed), - "consensus_time_ms" => format_delay(&block_delays.consensus_verification_time), - "execution_time_ms" => format_delay(&block_delays.execution_time), - "available_delay_ms" => format_delay(&block_delays.available), - "attestable_delay_ms" => format_delay(&block_delays.attestable), - "imported_time_ms" => format_delay(&block_delays.imported), - "set_as_head_time_ms" => format_delay(&block_delays.set_as_head), + block_root = ?head_block_root, + proposer_index = head_block_proposer_index, + slot = %head_block_slot, + total_delay_ms = block_delay_total.as_millis(), + observed_delay_ms = format_delay(&block_delays.observed), + blob_delay_ms = format_delay(&block_delays.all_blobs_observed), + consensus_time_ms = format_delay(&block_delays.consensus_verification_time), + execution_time_ms = format_delay(&block_delays.execution_time), + available_delay_ms = format_delay(&block_delays.available), + attestable_delay_ms = format_delay(&block_delays.attestable), + imported_time_ms = format_delay(&block_delays.imported), + set_as_head_time_ms = format_delay(&block_delays.set_as_head), + "Delayed head block" ); } else { debug!( - log, - "On-time head block"; - "block_root" => ?head_block_root, - "proposer_index" => head_block_proposer_index, - "slot" => head_block_slot, - "total_delay_ms" => block_delay_total.as_millis(), - "observed_delay_ms" => format_delay(&block_delays.observed), - "blob_delay_ms" => format_delay(&block_delays.all_blobs_observed), - "consensus_time_ms" => format_delay(&block_delays.consensus_verification_time), - "execution_time_ms" => format_delay(&block_delays.execution_time), - "available_delay_ms" => format_delay(&block_delays.available), - "attestable_delay_ms" => format_delay(&block_delays.attestable), - "imported_time_ms" => format_delay(&block_delays.imported), - "set_as_head_time_ms" => format_delay(&block_delays.set_as_head), + block_root = ?head_block_root, + proposer_index = head_block_proposer_index, + slot = %head_block_slot, + total_delay_ms = block_delay_total.as_millis(), + observed_delay_ms = format_delay(&block_delays.observed), + blob_delay_ms = format_delay(&block_delays.all_blobs_observed), + consensus_time_ms = format_delay(&block_delays.consensus_verification_time), + execution_time_ms = format_delay(&block_delays.execution_time), + available_delay_ms = format_delay(&block_delays.available), + attestable_delay_ms = format_delay(&block_delays.attestable), + imported_time_ms = format_delay(&block_delays.imported), + set_as_head_time_ms = format_delay(&block_delays.set_as_head), + "On-time head block" ); } } diff --git a/beacon_node/beacon_chain/src/chain_config.rs b/beacon_node/beacon_chain/src/chain_config.rs index fcdd57abbc..b881438c1c 100644 --- a/beacon_node/beacon_chain/src/chain_config.rs +++ b/beacon_node/beacon_chain/src/chain_config.rs @@ -16,6 +16,9 @@ pub const DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR: u32 = 3; /// Fraction of a slot lookahead for fork choice in the state advance timer (500ms on mainnet). pub const FORK_CHOICE_LOOKAHEAD_FACTOR: u32 = 24; +/// Default sync tolerance epochs. +pub const DEFAULT_SYNC_TOLERANCE_EPOCHS: u64 = 2; + #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub struct ChainConfig { /// Maximum number of slots to skip when importing an attestation. @@ -94,6 +97,13 @@ pub struct ChainConfig { /// The delay in milliseconds applied by the node between sending each blob or data column batch. /// This doesn't apply if the node is the block proposer. pub blob_publication_batch_interval: Duration, + /// The max distance between the head block and the current slot at which Lighthouse will + /// consider itself synced and still serve validator-related requests. + pub sync_tolerance_epochs: u64, + /// Artificial delay for block publishing. For PeerDAS testing only. + pub block_publishing_delay: Option, + /// Artificial delay for data column publishing. For PeerDAS testing only. + pub data_column_publishing_delay: Option, } impl Default for ChainConfig { @@ -129,6 +139,9 @@ impl Default for ChainConfig { enable_sampling: false, blob_publication_batches: 4, blob_publication_batch_interval: Duration::from_millis(300), + sync_tolerance_epochs: DEFAULT_SYNC_TOLERANCE_EPOCHS, + block_publishing_delay: None, + data_column_publishing_delay: None, } } } diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs index f10d59ca1a..07d663369a 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker.rs @@ -7,7 +7,6 @@ use crate::data_availability_checker::overflow_lru_cache::{ }; use crate::{metrics, BeaconChain, BeaconChainTypes, BeaconStore}; use kzg::Kzg; -use slog::{debug, error, Logger}; use slot_clock::SlotClock; use std::fmt; use std::fmt::Debug; @@ -16,6 +15,7 @@ use std::sync::Arc; use std::time::Duration; use task_executor::TaskExecutor; use tokio::sync::oneshot; +use tracing::{debug, error, info_span, Instrument}; use types::blob_sidecar::{BlobIdentifier, BlobSidecar, FixedBlobSidecarList}; use types::{ BlobSidecarList, ChainSpec, DataColumnIdentifier, DataColumnSidecar, DataColumnSidecarList, @@ -75,7 +75,6 @@ pub struct DataAvailabilityChecker { slot_clock: T::SlotClock, kzg: Arc, spec: Arc, - log: Logger, } pub type AvailabilityAndReconstructedColumns = (Availability, DataColumnSidecarList); @@ -91,7 +90,6 @@ pub enum DataColumnReconstructionResult { /// /// Indicates if the block is fully `Available` or if we need blobs or blocks /// to "complete" the requirements for an `AvailableBlock`. -#[derive(PartialEq)] pub enum Availability { MissingComponents(Hash256), Available(Box>), @@ -115,7 +113,6 @@ impl DataAvailabilityChecker { store: BeaconStore, import_all_data_columns: bool, spec: Arc, - log: Logger, ) -> Result { let custody_group_count = spec.custody_group_count(import_all_data_columns); // This should only panic if the chain spec contains invalid values. @@ -134,7 +131,6 @@ impl DataAvailabilityChecker { slot_clock, kzg, spec, - log, }) } @@ -219,7 +215,7 @@ impl DataAvailabilityChecker { .map_err(AvailabilityCheckError::InvalidBlobs)?; self.availability_cache - .put_kzg_verified_blobs(block_root, verified_blobs, None, &self.log) + .put_kzg_verified_blobs(block_root, verified_blobs) } /// Put a list of custody columns received via RPC into the availability cache. This performs KZG @@ -239,11 +235,8 @@ impl DataAvailabilityChecker { .map(KzgVerifiedCustodyDataColumn::from_asserted_custody) .collect::>(); - self.availability_cache.put_kzg_verified_data_columns( - block_root, - verified_custody_columns, - &self.log, - ) + self.availability_cache + .put_kzg_verified_data_columns(block_root, verified_custody_columns) } /// Put a list of blobs received from the EL pool into the availability cache. @@ -253,23 +246,27 @@ impl DataAvailabilityChecker { pub fn put_engine_blobs( &self, block_root: Hash256, + block_epoch: Epoch, blobs: FixedBlobSidecarList, - data_column_recv: Option>>, + data_columns_recv: Option>>, ) -> Result, AvailabilityCheckError> { - let seen_timestamp = self - .slot_clock - .now_duration() - .ok_or(AvailabilityCheckError::SlotClockError)?; - - let verified_blobs = - KzgVerifiedBlobList::from_verified(blobs.iter().flatten().cloned(), seen_timestamp); - - self.availability_cache.put_kzg_verified_blobs( - block_root, - verified_blobs, - data_column_recv, - &self.log, - ) + // `data_columns_recv` is always Some if block_root is post-PeerDAS + if let Some(data_columns_recv) = data_columns_recv { + self.availability_cache.put_computed_data_columns_recv( + block_root, + block_epoch, + data_columns_recv, + ) + } else { + let seen_timestamp = self + .slot_clock + .now_duration() + .ok_or(AvailabilityCheckError::SlotClockError)?; + self.availability_cache.put_kzg_verified_blobs( + block_root, + KzgVerifiedBlobList::from_verified(blobs.iter().flatten().cloned(), seen_timestamp), + ) + } } /// Check if we've cached other blobs for this block. If it completes a set and we also @@ -281,12 +278,8 @@ impl DataAvailabilityChecker { &self, gossip_blob: GossipVerifiedBlob, ) -> Result, AvailabilityCheckError> { - self.availability_cache.put_kzg_verified_blobs( - gossip_blob.block_root(), - vec![gossip_blob.into_inner()], - None, - &self.log, - ) + self.availability_cache + .put_kzg_verified_blobs(gossip_blob.block_root(), vec![gossip_blob.into_inner()]) } /// Check if we've cached other data columns for this block. If it satisfies the custody requirement and we also @@ -305,11 +298,8 @@ impl DataAvailabilityChecker { .map(|c| KzgVerifiedCustodyDataColumn::from_asserted_custody(c.into_inner())) .collect::>(); - self.availability_cache.put_kzg_verified_data_columns( - block_root, - custody_columns, - &self.log, - ) + self.availability_cache + .put_kzg_verified_data_columns(block_root, custody_columns) } /// Check if we have all the blobs for a block. Returns `Availability` which has information @@ -319,7 +309,7 @@ impl DataAvailabilityChecker { executed_block: AvailabilityPendingExecutedBlock, ) -> Result, AvailabilityCheckError> { self.availability_cache - .put_pending_executed_block(executed_block, &self.log) + .put_pending_executed_block(executed_block) } pub fn remove_pending_components(&self, block_root: Hash256) { @@ -338,15 +328,14 @@ impl DataAvailabilityChecker { ) -> Result, AvailabilityCheckError> { let (block_root, block, blobs, data_columns) = block.deconstruct(); if self.blobs_required_for_block(&block) { - return if let Some(blob_list) = blobs.as_ref() { + return if let Some(blob_list) = blobs { verify_kzg_for_blob_list(blob_list.iter(), &self.kzg) .map_err(AvailabilityCheckError::InvalidBlobs)?; Ok(MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs, + blob_data: AvailableBlockData::Blobs(blob_list), blobs_available_timestamp: None, - data_columns: None, spec: self.spec.clone(), })) } else { @@ -365,14 +354,13 @@ impl DataAvailabilityChecker { Ok(MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs: None, - blobs_available_timestamp: None, - data_columns: Some( + blob_data: AvailableBlockData::DataColumns( data_column_list .into_iter() .map(|d| d.clone_arc()) .collect(), ), + blobs_available_timestamp: None, spec: self.spec.clone(), })) } else { @@ -383,9 +371,8 @@ impl DataAvailabilityChecker { Ok(MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs: None, + blob_data: AvailableBlockData::NoData, blobs_available_timestamp: None, - data_columns: None, spec: self.spec.clone(), })) } @@ -437,27 +424,25 @@ impl DataAvailabilityChecker { let (block_root, block, blobs, data_columns) = block.deconstruct(); let maybe_available_block = if self.blobs_required_for_block(&block) { - if blobs.is_some() { + if let Some(blobs) = blobs { MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs, + blob_data: AvailableBlockData::Blobs(blobs), blobs_available_timestamp: None, - data_columns: None, spec: self.spec.clone(), }) } else { MaybeAvailableBlock::AvailabilityPending { block_root, block } } } else if self.data_columns_required_for_block(&block) { - if data_columns.is_some() { + if let Some(data_columns) = data_columns { MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs: None, - data_columns: data_columns.map(|data_columns| { - data_columns.into_iter().map(|d| d.into_inner()).collect() - }), + blob_data: AvailableBlockData::DataColumns( + data_columns.into_iter().map(|d| d.into_inner()).collect(), + ), blobs_available_timestamp: None, spec: self.spec.clone(), }) @@ -468,8 +453,7 @@ impl DataAvailabilityChecker { MaybeAvailableBlock::Available(AvailableBlock { block_root, block, - blobs: None, - data_columns: None, + blob_data: AvailableBlockData::NoData, blobs_available_timestamp: None, spec: self.spec.clone(), }) @@ -545,11 +529,11 @@ impl DataAvailabilityChecker { &self, block_root: &Hash256, ) -> Result, AvailabilityCheckError> { - let pending_components = match self + let verified_data_columns = match self .availability_cache .check_and_set_reconstruction_started(block_root) { - ReconstructColumnsDecision::Yes(pending_components) => pending_components, + ReconstructColumnsDecision::Yes(verified_data_columns) => verified_data_columns, ReconstructColumnsDecision::No(reason) => { return Ok(DataColumnReconstructionResult::NotStarted(reason)); } @@ -560,15 +544,14 @@ impl DataAvailabilityChecker { let all_data_columns = KzgVerifiedCustodyDataColumn::reconstruct_columns( &self.kzg, - &pending_components.verified_data_columns, + &verified_data_columns, &self.spec, ) .map_err(|e| { error!( - self.log, - "Error reconstructing data columns"; - "block_root" => ?block_root, - "error" => ?e + ?block_root, + error = ?e, + "Error reconstructing data columns" ); self.availability_cache .handle_reconstruction_failure(block_root); @@ -603,14 +586,15 @@ impl DataAvailabilityChecker { data_columns_to_publish.len() as u64, ); - debug!(self.log, "Reconstructed columns"; - "count" => data_columns_to_publish.len(), - "block_root" => ?block_root, - "slot" => slot, + debug!( + count = data_columns_to_publish.len(), + ?block_root, + %slot, + "Reconstructed columns" ); self.availability_cache - .put_kzg_verified_data_columns(*block_root, data_columns_to_publish.clone(), &self.log) + .put_kzg_verified_data_columns(*block_root, data_columns_to_publish.clone()) .map(|availability| { DataColumnReconstructionResult::Success(( availability, @@ -637,14 +621,18 @@ pub fn start_availability_cache_maintenance_service( if chain.spec.deneb_fork_epoch.is_some() { let overflow_cache = chain.data_availability_checker.availability_cache.clone(); executor.spawn( - async move { availability_cache_maintenance_service(chain, overflow_cache).await }, + async move { + availability_cache_maintenance_service(chain, overflow_cache) + .instrument(info_span!( + "DataAvailabilityChecker", + service = "data_availability_checker" + )) + .await + }, "availability_cache_service", ); } else { - debug!( - chain.log, - "Deneb fork not configured, not starting availability cache maintenance service" - ); + debug!("Deneb fork not configured, not starting availability cache maintenance service"); } } @@ -668,10 +656,7 @@ async fn availability_cache_maintenance_service( break; }; - debug!( - chain.log, - "Availability cache maintenance service firing"; - ); + debug!("Availability cache maintenance service firing"); let Some(current_epoch) = chain .slot_clock .now() @@ -701,11 +686,11 @@ async fn availability_cache_maintenance_service( ); if let Err(e) = overflow_cache.do_maintenance(cutoff_epoch) { - error!(chain.log, "Failed to maintain availability cache"; "error" => ?e); + error!(error = ?e,"Failed to maintain availability cache"); } } None => { - error!(chain.log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. tokio::time::sleep(chain.slot_clock.slot_duration()).await; } @@ -713,13 +698,25 @@ async fn availability_cache_maintenance_service( } } +#[derive(Debug)] +pub enum AvailableBlockData { + /// Block is pre-Deneb or has zero blobs + NoData, + /// Block is post-Deneb, pre-PeerDAS and has more than zero blobs + Blobs(BlobSidecarList), + /// Block is post-PeerDAS and has more than zero blobs + DataColumns(DataColumnSidecarList), + /// Block is post-PeerDAS, has more than zero blobs and we recomputed the columns from the EL's + /// mempool blobs + DataColumnsRecv(oneshot::Receiver>), +} + /// A fully available block that is ready to be imported into fork choice. -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug)] pub struct AvailableBlock { block_root: Hash256, block: Arc>, - blobs: Option>, - data_columns: Option>, + blob_data: AvailableBlockData, /// Timestamp at which this block first became available (UNIX timestamp, time since 1970). blobs_available_timestamp: Option, pub spec: Arc, @@ -729,15 +726,13 @@ impl AvailableBlock { pub fn __new_for_testing( block_root: Hash256, block: Arc>, - blobs: Option>, - data_columns: Option>, + data: AvailableBlockData, spec: Arc, ) -> Self { Self { block_root, block, - blobs, - data_columns, + blob_data: data, blobs_available_timestamp: None, spec, } @@ -750,39 +745,56 @@ impl AvailableBlock { self.block.clone() } - pub fn blobs(&self) -> Option<&BlobSidecarList> { - self.blobs.as_ref() - } - pub fn blobs_available_timestamp(&self) -> Option { self.blobs_available_timestamp } - pub fn data_columns(&self) -> Option<&DataColumnSidecarList> { - self.data_columns.as_ref() + pub fn data(&self) -> &AvailableBlockData { + &self.blob_data + } + + pub fn has_blobs(&self) -> bool { + match self.blob_data { + AvailableBlockData::NoData => false, + AvailableBlockData::Blobs(..) => true, + AvailableBlockData::DataColumns(_) => false, + AvailableBlockData::DataColumnsRecv(_) => false, + } } #[allow(clippy::type_complexity)] - pub fn deconstruct( - self, - ) -> ( - Hash256, - Arc>, - Option>, - Option>, - ) { + pub fn deconstruct(self) -> (Hash256, Arc>, AvailableBlockData) { let AvailableBlock { block_root, block, - blobs, - data_columns, + blob_data, .. } = self; - (block_root, block, blobs, data_columns) + (block_root, block, blob_data) + } + + /// Only used for testing + pub fn __clone_without_recv(&self) -> Result { + Ok(Self { + block_root: self.block_root, + block: self.block.clone(), + blob_data: match &self.blob_data { + AvailableBlockData::NoData => AvailableBlockData::NoData, + AvailableBlockData::Blobs(blobs) => AvailableBlockData::Blobs(blobs.clone()), + AvailableBlockData::DataColumns(data_columns) => { + AvailableBlockData::DataColumns(data_columns.clone()) + } + AvailableBlockData::DataColumnsRecv(_) => { + return Err("Can't clone DataColumnsRecv".to_owned()) + } + }, + blobs_available_timestamp: self.blobs_available_timestamp, + spec: self.spec.clone(), + }) } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum MaybeAvailableBlock { /// This variant is fully available. /// i.e. for pre-deneb blocks, it contains a (`SignedBeaconBlock`, `Blobs::None`) and for diff --git a/beacon_node/beacon_chain/src/data_availability_checker/error.rs b/beacon_node/beacon_chain/src/data_availability_checker/error.rs index 1ab85ab105..4e75ed4945 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/error.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/error.rs @@ -10,7 +10,7 @@ pub enum Error { blob_commitment: KzgCommitment, block_commitment: KzgCommitment, }, - Unexpected, + Unexpected(&'static str), SszTypes(ssz_types::Error), MissingBlobs, MissingCustodyColumns, @@ -40,7 +40,7 @@ impl Error { | Error::MissingCustodyColumns | Error::StoreError(_) | Error::DecodeError(_) - | Error::Unexpected + | Error::Unexpected(_) | Error::ParentStateMissing(_) | Error::BlockReplayError(_) | Error::RebuildingStateCaches(_) diff --git a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs index cd793c8394..d4cbf5ab76 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs @@ -1,4 +1,5 @@ use super::state_lru_cache::{DietAvailabilityPendingExecutedBlock, StateLRUCache}; +use super::AvailableBlockData; use crate::beacon_chain::BeaconStore; use crate::blob_verification::KzgVerifiedBlob; use crate::block_verification_types::{ @@ -9,10 +10,11 @@ use crate::data_column_verification::KzgVerifiedCustodyDataColumn; use crate::BeaconChainTypes; use lru::LruCache; use parking_lot::RwLock; -use slog::{debug, Logger}; +use std::cmp::Ordering; use std::num::NonZeroUsize; use std::sync::Arc; use tokio::sync::oneshot; +use tracing::debug; use types::blob_sidecar::BlobIdentifier; use types::{ BlobSidecar, ChainSpec, ColumnIndex, DataColumnIdentifier, DataColumnSidecar, @@ -39,19 +41,6 @@ pub struct PendingComponents { } impl PendingComponents { - /// Clones the `PendingComponent` without cloning `data_column_recv`, as `Receiver` is not cloneable. - /// This should only be used when the receiver is no longer needed. - pub fn clone_without_column_recv(&self) -> Self { - PendingComponents { - block_root: self.block_root, - verified_blobs: self.verified_blobs.clone(), - verified_data_columns: self.verified_data_columns.clone(), - executed_block: self.executed_block.clone(), - reconstruction_started: self.reconstruction_started, - data_column_recv: None, - } - } - /// Returns an immutable reference to the cached block. pub fn get_cached_block(&self) -> &Option> { &self.executed_block @@ -95,35 +84,6 @@ impl PendingComponents { .unwrap_or(false) } - /// Returns the number of blobs that are expected to be present. Returns `None` if we don't have a - /// block. - /// - /// This corresponds to the number of commitments that are present in a block. - pub fn block_kzg_commitments_count(&self) -> Option { - self.get_cached_block() - .as_ref() - .map(|b| b.get_commitments().len()) - } - - /// Returns the number of blobs that have been received and are stored in the cache. - pub fn num_received_blobs(&self) -> usize { - self.get_cached_blobs().iter().flatten().count() - } - - /// Checks if a data column of a given index exists in the cache. - /// - /// Returns: - /// - `true` if a data column for the given index exists. - /// - `false` otherwise. - fn data_column_exists(&self, data_column_index: u64) -> bool { - self.get_cached_data_column(data_column_index).is_some() - } - - /// Returns the number of data columns that have been received and are stored in the cache. - pub fn num_received_data_columns(&self) -> usize { - self.verified_data_columns.len() - } - /// Returns the indices of cached custody columns pub fn get_cached_data_columns_indices(&self) -> Vec { self.verified_data_columns @@ -182,8 +142,7 @@ impl PendingComponents { kzg_verified_data_columns: I, ) -> Result<(), AvailabilityCheckError> { for data_column in kzg_verified_data_columns { - // TODO(das): Add equivalent checks for data columns if necessary - if !self.data_column_exists(data_column.index()) { + if self.get_cached_data_column(data_column.index()).is_none() { self.verified_data_columns.push(data_column); } } @@ -199,51 +158,121 @@ impl PendingComponents { self.merge_blobs(reinsert); } - /// Checks if the block and all of its expected blobs or custody columns (post-PeerDAS) are - /// available in the cache. + /// Returns Some if the block has received all its required data for import. The return value + /// must be persisted in the DB along with the block. /// - /// Returns `true` if both the block exists and the number of received blobs / custody columns - /// matches the number of expected blobs / custody columns. - pub fn is_available(&self, custody_column_count: usize, log: &Logger) -> bool { - let block_kzg_commitments_count_opt = self.block_kzg_commitments_count(); - let expected_blobs_msg = block_kzg_commitments_count_opt - .as_ref() - .map(|num| num.to_string()) - .unwrap_or("unknown".to_string()); + /// WARNING: This function can potentially take a lot of time if the state needs to be + /// reconstructed from disk. Ensure you are not holding any write locks while calling this. + pub fn make_available( + &mut self, + custody_column_count: usize, + spec: &Arc, + recover: R, + ) -> Result>, AvailabilityCheckError> + where + R: FnOnce( + DietAvailabilityPendingExecutedBlock, + ) -> Result, AvailabilityCheckError>, + { + let Some(block) = &self.executed_block else { + // Block not available yet + return Ok(None); + }; - // No data columns when there are 0 blobs - let expected_columns_opt = block_kzg_commitments_count_opt.map(|blob_count| { - if blob_count > 0 { - custody_column_count - } else { - 0 + let num_expected_blobs = block.num_blobs_expected(); + + let blob_data = if num_expected_blobs == 0 { + Some(AvailableBlockData::NoData) + } else if spec.is_peer_das_enabled_for_epoch(block.epoch()) { + match self.verified_data_columns.len().cmp(&custody_column_count) { + Ordering::Greater => { + // Should never happen + return Err(AvailabilityCheckError::Unexpected("too many columns")); + } + Ordering::Equal => { + // Block is post-peerdas, and we got enough columns + let data_columns = self + .verified_data_columns + .iter() + .map(|d| d.clone().into_inner()) + .collect::>(); + Some(AvailableBlockData::DataColumns(data_columns)) + } + Ordering::Less => { + // The data_columns_recv is an infallible promise that we will receive all expected + // columns, so we consider the block available. + // We take the receiver as it can't be cloned, and make_available should never + // be called again once it returns `Some`. + self.data_column_recv + .take() + .map(AvailableBlockData::DataColumnsRecv) + } } - }); - let expected_columns_msg = expected_columns_opt - .as_ref() - .map(|num| num.to_string()) - .unwrap_or("unknown".to_string()); + } else { + // Before PeerDAS, blobs + let num_received_blobs = self.verified_blobs.iter().flatten().count(); + match num_received_blobs.cmp(&num_expected_blobs) { + Ordering::Greater => { + // Should never happen + return Err(AvailabilityCheckError::Unexpected("too many blobs")); + } + Ordering::Equal => { + let max_blobs = spec.max_blobs_per_block(block.epoch()) as usize; + let blobs_vec = self + .verified_blobs + .iter() + .flatten() + .map(|blob| blob.clone().to_blob()) + .collect::>(); + let blobs = RuntimeVariableList::new(blobs_vec, max_blobs) + .map_err(|_| AvailabilityCheckError::Unexpected("over max_blobs"))?; + Some(AvailableBlockData::Blobs(blobs)) + } + Ordering::Less => { + // Not enough blobs received yet + None + } + } + }; - let num_received_blobs = self.num_received_blobs(); - let num_received_columns = self.num_received_data_columns(); + // Block's data not available yet + let Some(blob_data) = blob_data else { + return Ok(None); + }; - debug!( - log, - "Component(s) added to data availability checker"; - "block_root" => ?self.block_root, - "received_blobs" => num_received_blobs, - "expected_blobs" => expected_blobs_msg, - "received_columns" => num_received_columns, - "expected_columns" => expected_columns_msg, - ); + // Block is available, construct `AvailableExecutedBlock` - let all_blobs_received = block_kzg_commitments_count_opt - .is_some_and(|num_expected_blobs| num_expected_blobs == num_received_blobs); + let blobs_available_timestamp = match blob_data { + AvailableBlockData::NoData => None, + AvailableBlockData::Blobs(_) => self + .verified_blobs + .iter() + .flatten() + .map(|blob| blob.seen_timestamp()) + .max(), + // TODO(das): To be fixed with https://github.com/sigp/lighthouse/pull/6850 + AvailableBlockData::DataColumns(_) => None, + AvailableBlockData::DataColumnsRecv(_) => None, + }; - let all_columns_received = expected_columns_opt - .is_some_and(|num_expected_columns| num_expected_columns == num_received_columns); + let AvailabilityPendingExecutedBlock { + block, + import_data, + payload_verification_outcome, + } = recover(block.clone())?; - all_blobs_received || all_columns_received + let available_block = AvailableBlock { + block_root: self.block_root, + block, + blob_data, + blobs_available_timestamp, + spec: spec.clone(), + }; + Ok(Some(AvailableExecutedBlock::new( + available_block, + import_data, + payload_verification_outcome, + ))) } /// Returns an empty `PendingComponents` object with the given block root. @@ -258,88 +287,6 @@ impl PendingComponents { } } - /// Verifies an `SignedBeaconBlock` against a set of KZG verified blobs. - /// This does not check whether a block *should* have blobs, these checks should have been - /// completed when producing the `AvailabilityPendingBlock`. - /// - /// WARNING: This function can potentially take a lot of time if the state needs to be - /// reconstructed from disk. Ensure you are not holding any write locks while calling this. - pub fn make_available( - self, - spec: &Arc, - recover: R, - ) -> Result, AvailabilityCheckError> - where - R: FnOnce( - DietAvailabilityPendingExecutedBlock, - ) -> Result, AvailabilityCheckError>, - { - let Self { - block_root, - verified_blobs, - verified_data_columns, - executed_block, - data_column_recv, - .. - } = self; - - let blobs_available_timestamp = verified_blobs - .iter() - .flatten() - .map(|blob| blob.seen_timestamp()) - .max(); - - let Some(diet_executed_block) = executed_block else { - return Err(AvailabilityCheckError::Unexpected); - }; - - let is_peer_das_enabled = spec.is_peer_das_enabled_for_epoch(diet_executed_block.epoch()); - let (blobs, data_columns) = if is_peer_das_enabled { - let data_columns = verified_data_columns - .into_iter() - .map(|d| d.into_inner()) - .collect::>(); - (None, Some(data_columns)) - } else { - let num_blobs_expected = diet_executed_block.num_blobs_expected(); - let Some(verified_blobs) = verified_blobs - .into_iter() - .map(|b| b.map(|b| b.to_blob())) - .take(num_blobs_expected) - .collect::>>() - .map(Into::into) - else { - return Err(AvailabilityCheckError::Unexpected); - }; - let max_len = spec.max_blobs_per_block(diet_executed_block.as_block().epoch()) as usize; - ( - Some(RuntimeVariableList::new(verified_blobs, max_len)?), - None, - ) - }; - let executed_block = recover(diet_executed_block)?; - - let AvailabilityPendingExecutedBlock { - block, - mut import_data, - payload_verification_outcome, - } = executed_block; - - import_data.data_column_recv = data_column_recv; - - let available_block = AvailableBlock { - block_root, - block, - blobs, - data_columns, - blobs_available_timestamp, - spec: spec.clone(), - }; - Ok(Availability::Available(Box::new( - AvailableExecutedBlock::new(available_block, import_data, payload_verification_outcome), - ))) - } - /// Returns the epoch of the block if it is cached, otherwise returns the epoch of the first blob. pub fn epoch(&self) -> Option { self.executed_block @@ -365,6 +312,41 @@ impl PendingComponents { None }) } + + pub fn status_str( + &self, + block_epoch: Epoch, + sampling_column_count: usize, + spec: &ChainSpec, + ) -> String { + let block_count = if self.executed_block.is_some() { 1 } else { 0 }; + if spec.is_peer_das_enabled_for_epoch(block_epoch) { + let data_column_recv_count = if self.data_column_recv.is_some() { + 1 + } else { + 0 + }; + format!( + "block {} data_columns {}/{} data_columns_recv {}", + block_count, + self.verified_data_columns.len(), + sampling_column_count, + data_column_recv_count, + ) + } else { + let num_expected_blobs = if let Some(block) = self.get_cached_block() { + &block.num_blobs_expected().to_string() + } else { + "?" + }; + format!( + "block {} blobs {}/{}", + block_count, + self.verified_blobs.len(), + num_expected_blobs + ) + } + } } /// This is the main struct for this module. Outside methods should @@ -385,7 +367,7 @@ pub struct DataAvailabilityCheckerInner { // the current usage, as it's deconstructed immediately. #[allow(clippy::large_enum_variant)] pub(crate) enum ReconstructColumnsDecision { - Yes(PendingComponents), + Yes(Vec>), No(&'static str), } @@ -466,17 +448,10 @@ impl DataAvailabilityCheckerInner { } /// Puts the KZG verified blobs into the availability cache as pending components. - /// - /// The `data_column_recv` parameter is an optional `Receiver` for data columns that are - /// computed asynchronously. This method remains **used** after PeerDAS activation, because - /// blocks can be made available if the EL already has the blobs and returns them via the - /// `getBlobsV1` engine method. More details in [fetch_blobs.rs](https://github.com/sigp/lighthouse/blob/44f8add41ea2252769bb967864af95b3c13af8ca/beacon_node/beacon_chain/src/fetch_blobs.rs). pub fn put_kzg_verified_blobs>>( &self, block_root: Hash256, kzg_verified_blobs: I, - data_column_recv: Option>>, - log: &Logger, ) -> Result, AvailabilityCheckError> { let mut kzg_verified_blobs = kzg_verified_blobs.into_iter().peekable(); @@ -485,7 +460,7 @@ impl DataAvailabilityCheckerInner { .map(|verified_blob| verified_blob.as_blob().epoch()) else { // Verified blobs list should be non-empty. - return Err(AvailabilityCheckError::Unexpected); + return Err(AvailabilityCheckError::Unexpected("empty blobs")); }; let mut fixed_blobs = @@ -510,21 +485,23 @@ impl DataAvailabilityCheckerInner { // Merge in the blobs. pending_components.merge_blobs(fixed_blobs); - if data_column_recv.is_some() { - // If `data_column_recv` is `Some`, it means we have all the blobs from engine, and have - // started computing data columns. We store the receiver in `PendingComponents` for - // later use when importing the block. - pending_components.data_column_recv = data_column_recv; - } + debug!( + component = "blobs", + ?block_root, + status = pending_components.status_str(epoch, self.sampling_column_count, &self.spec), + "Component added to data availability checker" + ); - if pending_components.is_available(self.sampling_column_count, log) { + if let Some(available_block) = + pending_components.make_available(self.sampling_column_count, &self.spec, |block| { + self.state_cache.recover_pending_executed_block(block) + })? + { // We keep the pending components in the availability cache during block import (#5845). // `data_column_recv` is returned as part of the available block and is no longer needed here. - write_lock.put(block_root, pending_components.clone_without_column_recv()); + write_lock.put(block_root, pending_components); drop(write_lock); - pending_components.make_available(&self.spec, |diet_block| { - self.state_cache.recover_pending_executed_block(diet_block) - }) + Ok(Availability::Available(Box::new(available_block))) } else { write_lock.put(block_root, pending_components); Ok(Availability::MissingComponents(block_root)) @@ -538,7 +515,6 @@ impl DataAvailabilityCheckerInner { &self, block_root: Hash256, kzg_verified_data_columns: I, - log: &Logger, ) -> Result, AvailabilityCheckError> { let mut kzg_verified_data_columns = kzg_verified_data_columns.into_iter().peekable(); let Some(epoch) = kzg_verified_data_columns @@ -546,7 +522,7 @@ impl DataAvailabilityCheckerInner { .map(|verified_blob| verified_blob.as_data_column().epoch()) else { // Verified data_columns list should be non-empty. - return Err(AvailabilityCheckError::Unexpected); + return Err(AvailabilityCheckError::Unexpected("empty columns")); }; let mut write_lock = self.critical.write(); @@ -562,14 +538,74 @@ impl DataAvailabilityCheckerInner { // Merge in the data columns. pending_components.merge_data_columns(kzg_verified_data_columns)?; - if pending_components.is_available(self.sampling_column_count, log) { + debug!( + component = "data_columns", + ?block_root, + status = pending_components.status_str(epoch, self.sampling_column_count, &self.spec), + "Component added to data availability checker" + ); + + if let Some(available_block) = + pending_components.make_available(self.sampling_column_count, &self.spec, |block| { + self.state_cache.recover_pending_executed_block(block) + })? + { // We keep the pending components in the availability cache during block import (#5845). // `data_column_recv` is returned as part of the available block and is no longer needed here. - write_lock.put(block_root, pending_components.clone_without_column_recv()); + write_lock.put(block_root, pending_components); drop(write_lock); - pending_components.make_available(&self.spec, |diet_block| { - self.state_cache.recover_pending_executed_block(diet_block) - }) + Ok(Availability::Available(Box::new(available_block))) + } else { + write_lock.put(block_root, pending_components); + Ok(Availability::MissingComponents(block_root)) + } + } + + /// The `data_column_recv` parameter is a `Receiver` for data columns that are computed + /// asynchronously. This method is used if the EL already has the blobs and returns them via the + /// `getBlobsV1` engine method. More details in [fetch_blobs.rs](https://github.com/sigp/lighthouse/blob/44f8add41ea2252769bb967864af95b3c13af8ca/beacon_node/beacon_chain/src/fetch_blobs.rs). + pub fn put_computed_data_columns_recv( + &self, + block_root: Hash256, + block_epoch: Epoch, + data_column_recv: oneshot::Receiver>, + ) -> Result, AvailabilityCheckError> { + let mut write_lock = self.critical.write(); + + // Grab existing entry or create a new entry. + let mut pending_components = write_lock + .pop_entry(&block_root) + .map(|(_, v)| v) + .unwrap_or_else(|| { + PendingComponents::empty( + block_root, + self.spec.max_blobs_per_block(block_epoch) as usize, + ) + }); + + // We have all the blobs from engine, and have started computing data columns. We store the + // receiver in `PendingComponents` for later use when importing the block. + // TODO(das): Error or log if we overwrite a prior receiver https://github.com/sigp/lighthouse/issues/6764 + pending_components.data_column_recv = Some(data_column_recv); + + debug!( + component = "data_columns_recv", + ?block_root, + status = + pending_components.status_str(block_epoch, self.sampling_column_count, &self.spec), + "Component added to data availability checker" + ); + + if let Some(available_block) = + pending_components.make_available(self.sampling_column_count, &self.spec, |block| { + self.state_cache.recover_pending_executed_block(block) + })? + { + // We keep the pending components in the availability cache during block import (#5845). + // `data_column_recv` is returned as part of the available block and is no longer needed here. + write_lock.put(block_root, pending_components); + drop(write_lock); + Ok(Availability::Available(Box::new(available_block))) } else { write_lock.put(block_root, pending_components); Ok(Availability::MissingComponents(block_root)) @@ -614,7 +650,7 @@ impl DataAvailabilityCheckerInner { } pending_components.reconstruction_started = true; - ReconstructColumnsDecision::Yes(pending_components.clone_without_column_recv()) + ReconstructColumnsDecision::Yes(pending_components.verified_data_columns.clone()) } /// This could mean some invalid data columns made it through to the `DataAvailabilityChecker`. @@ -632,7 +668,6 @@ impl DataAvailabilityCheckerInner { pub fn put_pending_executed_block( &self, executed_block: AvailabilityPendingExecutedBlock, - log: &Logger, ) -> Result, AvailabilityCheckError> { let mut write_lock = self.critical.write(); let epoch = executed_block.as_block().epoch(); @@ -654,15 +689,24 @@ impl DataAvailabilityCheckerInner { // Merge in the block. pending_components.merge_block(diet_executed_block); + debug!( + component = "block", + ?block_root, + status = pending_components.status_str(epoch, self.sampling_column_count, &self.spec), + "Component added to data availability checker" + ); + // Check if we have all components and entire set is consistent. - if pending_components.is_available(self.sampling_column_count, log) { + if let Some(available_block) = + pending_components.make_available(self.sampling_column_count, &self.spec, |block| { + self.state_cache.recover_pending_executed_block(block) + })? + { // We keep the pending components in the availability cache during block import (#5845). // `data_column_recv` is returned as part of the available block and is no longer needed here. - write_lock.put(block_root, pending_components.clone_without_column_recv()); + write_lock.put(block_root, pending_components); drop(write_lock); - pending_components.make_available(&self.spec, |diet_block| { - self.state_cache.recover_pending_executed_block(diet_block) - }) + Ok(Availability::Available(Box::new(available_block))) } else { write_lock.put(block_root, pending_components); Ok(Availability::MissingComponents(block_root)) @@ -726,13 +770,12 @@ mod test { test_utils::{BaseHarnessType, BeaconChainHarness, DiskHarnessType}, }; use fork_choice::PayloadVerificationStatus; - - use logging::test_logger; - use slog::{info, Logger}; + use logging::create_test_tracing_subscriber; use state_processing::ConsensusContext; use std::collections::VecDeque; use store::{database::interface::BeaconNodeBackend, HotColdDB, ItemStore, StoreConfig}; use tempfile::{tempdir, TempDir}; + use tracing::info; use types::non_zero_usize::new_non_zero_usize; use types::{ExecPayload, MinimalEthSpec}; @@ -742,7 +785,6 @@ mod test { fn get_store_with_spec( db_path: &TempDir, spec: Arc, - log: Logger, ) -> Arc, BeaconNodeBackend>> { let hot_path = db_path.path().join("hot_db"); let cold_path = db_path.path().join("cold_db"); @@ -756,14 +798,12 @@ mod test { |_, _, _| Ok(()), config, spec, - log, ) .expect("disk store should initialize") } // get a beacon chain harness advanced to just before deneb fork async fn get_deneb_chain( - log: Logger, db_path: &TempDir, ) -> BeaconChainHarness> { let altair_fork_epoch = Epoch::new(1); @@ -780,12 +820,11 @@ mod test { spec.deneb_fork_epoch = Some(deneb_fork_epoch); let spec = Arc::new(spec); - let chain_store = get_store_with_spec::(db_path, spec.clone(), log.clone()); + let chain_store = get_store_with_spec::(db_path, spec.clone()); let validators_keypairs = types::test_utils::generate_deterministic_keypairs(LOW_VALIDATOR_COUNT); let harness = BeaconChainHarness::builder(E::default()) .spec(spec.clone()) - .logger(log.clone()) .keypairs(validators_keypairs) .fresh_disk_store(chain_store) .mock_execution_layer() @@ -828,7 +867,6 @@ mod test { Cold: ItemStore, { let chain = &harness.chain; - let log = chain.log.clone(); let head = chain.head_snapshot(); let parent_state = head.beacon_state.clone(); @@ -856,7 +894,7 @@ mod test { ); // log kzg commitments - info!(log, "printing kzg commitments"); + info!("printing kzg commitments"); for comm in Vec::from( block .message() @@ -865,9 +903,9 @@ mod test { .expect("should be deneb fork") .clone(), ) { - info!(log, "kzg commitment"; "commitment" => ?comm); + info!(commitment = ?comm, "kzg commitment"); } - info!(log, "done printing kzg commitments"); + info!("done printing kzg commitments"); let gossip_verified_blobs = if let Some((kzg_proofs, blobs)) = maybe_blobs { let sidecars = @@ -893,7 +931,6 @@ mod test { parent_eth1_finalization_data, confirmed_state_roots: vec![], consensus_context, - data_column_recv: None, }; let payload_verification_outcome = PayloadVerificationOutcome { @@ -925,9 +962,9 @@ mod test { EthSpec = E, >, { - let log = test_logger(); + create_test_tracing_subscriber(); let chain_db_path = tempdir().expect("should get temp dir"); - let harness = get_deneb_chain(log.clone(), &chain_db_path).await; + let harness = get_deneb_chain(&chain_db_path).await; let spec = harness.spec.clone(); let test_store = harness.chain.store.clone(); let capacity_non_zero = new_non_zero_usize(capacity); @@ -961,7 +998,7 @@ mod test { ); assert!(cache.critical.read().is_empty(), "cache should be empty"); let availability = cache - .put_pending_executed_block(pending_block, harness.logger()) + .put_pending_executed_block(pending_block) .expect("should put block"); if blobs_expected == 0 { assert!( @@ -1000,7 +1037,7 @@ mod test { for (blob_index, gossip_blob) in blobs.into_iter().enumerate() { kzg_verified_blobs.push(gossip_blob.into_inner()); let availability = cache - .put_kzg_verified_blobs(root, kzg_verified_blobs.clone(), None, harness.logger()) + .put_kzg_verified_blobs(root, kzg_verified_blobs.clone()) .expect("should put blob"); if blob_index == blobs_expected - 1 { assert!(matches!(availability, Availability::Available(_))); @@ -1028,17 +1065,16 @@ mod test { for gossip_blob in blobs { kzg_verified_blobs.push(gossip_blob.into_inner()); let availability = cache - .put_kzg_verified_blobs(root, kzg_verified_blobs.clone(), None, harness.logger()) + .put_kzg_verified_blobs(root, kzg_verified_blobs.clone()) .expect("should put blob"); - assert_eq!( - availability, - Availability::MissingComponents(root), + assert!( + matches!(availability, Availability::MissingComponents(_)), "should be pending block" ); assert_eq!(cache.critical.read().len(), 1); } let availability = cache - .put_pending_executed_block(pending_block, harness.logger()) + .put_pending_executed_block(pending_block) .expect("should put block"); assert!( matches!(availability, Availability::Available(_)), @@ -1106,7 +1142,7 @@ mod test { // put the block in the cache let availability = cache - .put_pending_executed_block(pending_block, harness.logger()) + .put_pending_executed_block(pending_block) .expect("should put block"); // grab the diet block from the cache for later testing @@ -1284,7 +1320,6 @@ mod pending_components_tests { }, confirmed_state_roots: vec![], consensus_context: ConsensusContext::new(Slot::new(0)), - data_column_recv: None, }, payload_verification_outcome: PayloadVerificationOutcome { payload_verification_status: PayloadVerificationStatus::Verified, diff --git a/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs index 2a2a0431cc..5b9b7c7023 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs @@ -136,7 +136,6 @@ impl StateLRUCache { consensus_context: diet_executed_block .consensus_context .into_consensus_context(), - data_column_recv: None, }, payload_verification_outcome: diet_executed_block.payload_verification_outcome, }) diff --git a/beacon_node/beacon_chain/src/data_column_verification.rs b/beacon_node/beacon_chain/src/data_column_verification.rs index 1262fcdeb8..2f95d834b5 100644 --- a/beacon_node/beacon_chain/src/data_column_verification.rs +++ b/beacon_node/beacon_chain/src/data_column_verification.rs @@ -10,12 +10,12 @@ use fork_choice::ProtoBlock; use kzg::{Error as KzgError, Kzg}; use proto_array::Block; use slasher::test_utils::E; -use slog::debug; use slot_clock::SlotClock; use ssz_derive::{Decode, Encode}; use std::iter; use std::marker::PhantomData; use std::sync::Arc; +use tracing::debug; use types::data_column_sidecar::{ColumnIndex, DataColumnIdentifier}; use types::{ BeaconStateError, ChainSpec, DataColumnSidecar, DataColumnSubnetId, EthSpec, Hash256, @@ -580,10 +580,9 @@ fn verify_proposer_and_signature( (proposer.index, proposer.fork) } else { debug!( - chain.log, - "Proposer shuffling cache miss for column verification"; - "block_root" => %block_root, - "index" => %column_index, + %block_root, + index = %column_index, + "Proposer shuffling cache miss for column verification" ); let (parent_state_root, mut parent_state) = chain .store diff --git a/beacon_node/beacon_chain/src/early_attester_cache.rs b/beacon_node/beacon_chain/src/early_attester_cache.rs index c94ea0e941..a90911026c 100644 --- a/beacon_node/beacon_chain/src/early_attester_cache.rs +++ b/beacon_node/beacon_chain/src/early_attester_cache.rs @@ -1,4 +1,4 @@ -use crate::data_availability_checker::AvailableBlock; +use crate::data_availability_checker::{AvailableBlock, AvailableBlockData}; use crate::{ attester_cache::{CommitteeLengths, Error}, metrics, @@ -52,7 +52,7 @@ impl EarlyAttesterCache { pub fn add_head_block( &self, beacon_block_root: Hash256, - block: AvailableBlock, + block: &AvailableBlock, proto_block: ProtoBlock, state: &BeaconState, spec: &ChainSpec, @@ -70,14 +70,23 @@ impl EarlyAttesterCache { }, }; - let (_, block, blobs, data_columns) = block.deconstruct(); + let (blobs, data_columns) = match block.data() { + AvailableBlockData::NoData => (None, None), + AvailableBlockData::Blobs(blobs) => (Some(blobs.clone()), None), + AvailableBlockData::DataColumns(data_columns) => (None, Some(data_columns.clone())), + // TODO(das): Once the columns are received, they will not be available in + // the early attester cache. If someone does a query to us via RPC we + // will get downscored. + AvailableBlockData::DataColumnsRecv(_) => (None, None), + }; + let item = CacheItem { epoch, committee_lengths, beacon_block_root, source, target, - block, + block: block.block_cloned(), blobs, data_columns, proto_block, diff --git a/beacon_node/beacon_chain/src/eth1_chain.rs b/beacon_node/beacon_chain/src/eth1_chain.rs index ad4f106517..43429b726c 100644 --- a/beacon_node/beacon_chain/src/eth1_chain.rs +++ b/beacon_node/beacon_chain/src/eth1_chain.rs @@ -3,7 +3,6 @@ use eth1::{Config as Eth1Config, Eth1Block, Service as HttpService}; use eth2::lighthouse::Eth1SyncStatusData; use ethereum_hashing::hash; use int_to_bytes::int_to_bytes32; -use slog::{debug, error, trace, Logger}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use state_processing::per_block_processing::get_new_eth1_data; @@ -14,6 +13,7 @@ use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use store::{DBColumn, Error as StoreError, StoreItem}; use task_executor::TaskExecutor; +use tracing::{debug, error, trace}; use types::{ BeaconState, BeaconStateError, ChainSpec, Deposit, Eth1Data, EthSpec, Hash256, Slot, Unsigned, }; @@ -283,11 +283,9 @@ where pub fn from_ssz_container( ssz_container: &SszEth1, config: Eth1Config, - log: &Logger, spec: Arc, ) -> Result { - let backend = - Eth1ChainBackend::from_bytes(&ssz_container.backend_bytes, config, log.clone(), spec)?; + let backend = Eth1ChainBackend::from_bytes(&ssz_container.backend_bytes, config, spec)?; Ok(Self { use_dummy_backend: ssz_container.use_dummy_backend, backend, @@ -351,12 +349,7 @@ pub trait Eth1ChainBackend: Sized + Send + Sync { fn as_bytes(&self) -> Vec; /// Create a `Eth1ChainBackend` instance given encoded bytes. - fn from_bytes( - bytes: &[u8], - config: Eth1Config, - log: Logger, - spec: Arc, - ) -> Result; + fn from_bytes(bytes: &[u8], config: Eth1Config, spec: Arc) -> Result; } /// Provides a simple, testing-only backend that generates deterministic, meaningless eth1 data. @@ -412,7 +405,6 @@ impl Eth1ChainBackend for DummyEth1ChainBackend { fn from_bytes( _bytes: &[u8], _config: Eth1Config, - _log: Logger, _spec: Arc, ) -> Result { Ok(Self(PhantomData)) @@ -433,7 +425,6 @@ impl Default for DummyEth1ChainBackend { #[derive(Clone)] pub struct CachingEth1Backend { pub core: HttpService, - log: Logger, _phantom: PhantomData, } @@ -441,11 +432,10 @@ impl CachingEth1Backend { /// Instantiates `self` with empty caches. /// /// Does not connect to the eth1 node or start any tasks to keep the cache updated. - pub fn new(config: Eth1Config, log: Logger, spec: Arc) -> Result { + pub fn new(config: Eth1Config, spec: Arc) -> Result { Ok(Self { - core: HttpService::new(config, log.clone(), spec) + core: HttpService::new(config, spec) .map_err(|e| format!("Failed to create eth1 http service: {:?}", e))?, - log, _phantom: PhantomData, }) } @@ -458,7 +448,6 @@ impl CachingEth1Backend { /// Instantiates `self` from an existing service. pub fn from_service(service: HttpService) -> Self { Self { - log: service.log.clone(), core: service, _phantom: PhantomData, } @@ -481,9 +470,8 @@ impl Eth1ChainBackend for CachingEth1Backend { }; trace!( - self.log, - "Found eth1 data votes_to_consider"; - "votes_to_consider" => votes_to_consider.len(), + votes_to_consider = votes_to_consider.len(), + "Found eth1 data votes_to_consider" ); let valid_votes = collect_valid_votes(state, &votes_to_consider); @@ -500,22 +488,20 @@ impl Eth1ChainBackend for CachingEth1Backend { .map(|vote| { let vote = vote.0.clone(); debug!( - self.log, - "No valid eth1_data votes"; - "outcome" => "Casting vote corresponding to last candidate eth1 block", - "vote" => ?vote + outcome = "Casting vote corresponding to last candidate eth1 block", + ?vote, + "No valid eth1_data votes" ); vote }) .unwrap_or_else(|| { let vote = state.eth1_data().clone(); error!( - self.log, - "No valid eth1_data votes, `votes_to_consider` empty"; - "lowest_block_number" => self.core.lowest_block_number(), - "earliest_block_timestamp" => self.core.earliest_block_timestamp(), - "genesis_time" => state.genesis_time(), - "outcome" => "casting `state.eth1_data` as eth1 vote" + lowest_block_number = self.core.lowest_block_number(), + earliest_block_timestamp = self.core.earliest_block_timestamp(), + genesis_time = state.genesis_time(), + outcome = "casting `state.eth1_data` as eth1 vote", + "No valid eth1_data votes, `votes_to_consider` empty" ); metrics::inc_counter(&metrics::DEFAULT_ETH1_VOTES); vote @@ -523,11 +509,10 @@ impl Eth1ChainBackend for CachingEth1Backend { }; debug!( - self.log, - "Produced vote for eth1 chain"; - "deposit_root" => format!("{:?}", eth1_data.deposit_root), - "deposit_count" => eth1_data.deposit_count, - "block_hash" => format!("{:?}", eth1_data.block_hash), + deposit_root = ?eth1_data.deposit_root, + deposit_count = eth1_data.deposit_count, + block_hash = ?eth1_data.block_hash, + "Produced vote for eth1 chain" ); Ok(eth1_data) @@ -592,16 +577,10 @@ impl Eth1ChainBackend for CachingEth1Backend { } /// Recover the cached backend from encoded bytes. - fn from_bytes( - bytes: &[u8], - config: Eth1Config, - log: Logger, - spec: Arc, - ) -> Result { - let inner = HttpService::from_bytes(bytes, config, log.clone(), spec)?; + fn from_bytes(bytes: &[u8], config: Eth1Config, spec: Arc) -> Result { + let inner = HttpService::from_bytes(bytes, config, spec)?; Ok(Self { core: inner, - log, _phantom: PhantomData, }) } @@ -742,17 +721,18 @@ mod test { mod eth1_chain_json_backend { use super::*; use eth1::DepositLog; - use logging::test_logger; + use logging::create_test_tracing_subscriber; use types::{test_utils::generate_deterministic_keypair, MainnetEthSpec}; fn get_eth1_chain() -> Eth1Chain, E> { + create_test_tracing_subscriber(); + let eth1_config = Eth1Config { ..Eth1Config::default() }; - let log = test_logger(); Eth1Chain::new( - CachingEth1Backend::new(eth1_config, log, Arc::new(MainnetEthSpec::default_spec())) + CachingEth1Backend::new(eth1_config, Arc::new(MainnetEthSpec::default_spec())) .unwrap(), ) } diff --git a/beacon_node/beacon_chain/src/eth1_finalization_cache.rs b/beacon_node/beacon_chain/src/eth1_finalization_cache.rs index 24b6542eab..84618ceab0 100644 --- a/beacon_node/beacon_chain/src/eth1_finalization_cache.rs +++ b/beacon_node/beacon_chain/src/eth1_finalization_cache.rs @@ -1,7 +1,7 @@ -use slog::{debug, Logger}; use ssz_derive::{Decode, Encode}; use std::cmp; use std::collections::BTreeMap; +use tracing::debug; use types::{Checkpoint, Epoch, Eth1Data, Hash256 as Root}; /// The default size of the cache. @@ -104,28 +104,27 @@ pub struct Eth1FinalizationCache { by_checkpoint: CheckpointMap, pending_eth1: BTreeMap, last_finalized: Option, - log: Logger, +} + +impl Default for Eth1FinalizationCache { + fn default() -> Self { + Self { + by_checkpoint: CheckpointMap::new(), + pending_eth1: BTreeMap::new(), + last_finalized: None, + } + } } /// Provides a cache of `Eth1CacheData` at epoch boundaries. This is used to /// finalize deposits when a new epoch is finalized. /// impl Eth1FinalizationCache { - pub fn new(log: Logger) -> Self { - Eth1FinalizationCache { - by_checkpoint: CheckpointMap::new(), - pending_eth1: BTreeMap::new(), - last_finalized: None, - log, - } - } - - pub fn with_capacity(log: Logger, capacity: usize) -> Self { + pub fn with_capacity(capacity: usize) -> Self { Eth1FinalizationCache { by_checkpoint: CheckpointMap::with_capacity(capacity), pending_eth1: BTreeMap::new(), last_finalized: None, - log, } } @@ -136,10 +135,9 @@ impl Eth1FinalizationCache { eth1_finalization_data.eth1_data.clone(), ); debug!( - self.log, - "Eth1Cache: inserted pending eth1"; - "eth1_data.deposit_count" => eth1_finalization_data.eth1_data.deposit_count, - "eth1_deposit_index" => eth1_finalization_data.eth1_deposit_index, + eth1_data.deposit_count = eth1_finalization_data.eth1_data.deposit_count, + eth1_deposit_index = eth1_finalization_data.eth1_deposit_index, + "Eth1Cache: inserted pending eth1" ); } self.by_checkpoint @@ -154,10 +152,8 @@ impl Eth1FinalizationCache { if finalized_deposit_index >= pending_count { result = self.pending_eth1.remove(&pending_count); debug!( - self.log, - "Eth1Cache: dropped pending eth1"; - "pending_count" => pending_count, - "finalized_deposit_index" => finalized_deposit_index, + pending_count, + finalized_deposit_index, "Eth1Cache: dropped pending eth1" ); } else { break; @@ -172,9 +168,8 @@ impl Eth1FinalizationCache { self.last_finalized.clone() } else { debug!( - self.log, - "Eth1Cache: cache miss"; - "epoch" => checkpoint.epoch, + epoch = %checkpoint.epoch, + "Eth1Cache: cache miss" ); None } @@ -194,8 +189,6 @@ impl Eth1FinalizationCache { #[cfg(test)] pub mod tests { use super::*; - use sloggers::null::NullLoggerBuilder; - use sloggers::Build; use std::collections::HashMap; const SLOTS_PER_EPOCH: u64 = 32; @@ -203,8 +196,7 @@ pub mod tests { const EPOCHS_PER_ETH1_VOTING_PERIOD: u64 = 64; fn eth1cache() -> Eth1FinalizationCache { - let log_builder = NullLoggerBuilder; - Eth1FinalizationCache::new(log_builder.build().expect("should build log")) + Eth1FinalizationCache::default() } fn random_eth1_data(deposit_count: u64) -> Eth1Data { diff --git a/beacon_node/beacon_chain/src/events.rs b/beacon_node/beacon_chain/src/events.rs index 8c342893ae..d09b74e645 100644 --- a/beacon_node/beacon_chain/src/events.rs +++ b/beacon_node/beacon_chain/src/events.rs @@ -1,7 +1,7 @@ pub use eth2::types::{EventKind, SseBlock, SseFinalizedCheckpoint, SseHead}; -use slog::{trace, Logger}; use tokio::sync::broadcast; use tokio::sync::broadcast::{error::SendError, Receiver, Sender}; +use tracing::trace; use types::EthSpec; const DEFAULT_CHANNEL_CAPACITY: usize = 16; @@ -25,18 +25,14 @@ pub struct ServerSentEventHandler { attester_slashing_tx: Sender>, bls_to_execution_change_tx: Sender>, block_gossip_tx: Sender>, - log: Logger, } impl ServerSentEventHandler { - pub fn new(log: Logger, capacity_multiplier: usize) -> Self { - Self::new_with_capacity( - log, - capacity_multiplier.saturating_mul(DEFAULT_CHANNEL_CAPACITY), - ) + pub fn new(capacity_multiplier: usize) -> Self { + Self::new_with_capacity(capacity_multiplier.saturating_mul(DEFAULT_CHANNEL_CAPACITY)) } - pub fn new_with_capacity(log: Logger, capacity: usize) -> Self { + pub fn new_with_capacity(capacity: usize) -> Self { let (attestation_tx, _) = broadcast::channel(capacity); let (single_attestation_tx, _) = broadcast::channel(capacity); let (block_tx, _) = broadcast::channel(capacity); @@ -75,17 +71,15 @@ impl ServerSentEventHandler { attester_slashing_tx, bls_to_execution_change_tx, block_gossip_tx, - log, } } pub fn register(&self, kind: EventKind) { let log_count = |name, count| { trace!( - self.log, - "Registering server-sent event"; - "kind" => name, - "receiver_count" => count + kind = name, + receiver_count = count, + "Registering server-sent event" ); }; let result = match &kind { @@ -163,7 +157,7 @@ impl ServerSentEventHandler { .map(|count| log_count("block gossip", count)), }; if let Err(SendError(event)) = result { - trace!(self.log, "No receivers registered to listen for event"; "event" => ?event); + trace!(?event, "No receivers registered to listen for event"); } } diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 720f98e298..1da8cb413b 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -17,7 +17,6 @@ use execution_layer::{ }; use fork_choice::{InvalidationOperation, PayloadVerificationStatus}; use proto_array::{Block as ProtoBlock, ExecutionStatus}; -use slog::{debug, warn}; use slot_clock::SlotClock; use state_processing::per_block_processing::{ compute_timestamp_at_slot, get_expected_withdrawals, is_execution_enabled, @@ -25,6 +24,7 @@ use state_processing::per_block_processing::{ }; use std::sync::Arc; use tokio::task::JoinHandle; +use tracing::{debug, warn}; use tree_hash::TreeHash; use types::payload::BlockProductionVersion; use types::*; @@ -85,11 +85,10 @@ impl PayloadNotifier { block_message.try_into()?; if let Err(e) = new_payload_request.perform_optimistic_sync_verifications() { warn!( - chain.log, - "Falling back to slow block hash verification"; - "block_number" => ?block_message.execution_payload().map(|payload| payload.block_number()), - "info" => "you can silence this warning with --disable-optimistic-finalized-sync", - "error" => ?e, + block_number = ?block_message.execution_payload().map(|payload| payload.block_number()), + info = "you can silence this warning with --disable-optimistic-finalized-sync", + error = ?e, + "Falling back to slow block hash verification" ); None } else { @@ -150,16 +149,15 @@ async fn notify_new_payload( ref validation_error, } => { warn!( - chain.log, - "Invalid execution payload"; - "validation_error" => ?validation_error, - "latest_valid_hash" => ?latest_valid_hash, - "execution_block_hash" => ?execution_block_hash, - "root" => ?block.tree_hash_root(), - "graffiti" => block.body().graffiti().as_utf8_lossy(), - "proposer_index" => block.proposer_index(), - "slot" => block.slot(), - "method" => "new_payload", + ?validation_error, + ?latest_valid_hash, + ?execution_block_hash, + root = ?block.tree_hash_root(), + graffiti = block.body().graffiti().as_utf8_lossy(), + proposer_index = block.proposer_index(), + slot = %block.slot(), + method = "new_payload", + "Invalid execution payload" ); // Only trigger payload invalidation in fork choice if the @@ -197,15 +195,14 @@ async fn notify_new_payload( ref validation_error, } => { warn!( - chain.log, - "Invalid execution payload block hash"; - "validation_error" => ?validation_error, - "execution_block_hash" => ?execution_block_hash, - "root" => ?block.tree_hash_root(), - "graffiti" => block.body().graffiti().as_utf8_lossy(), - "proposer_index" => block.proposer_index(), - "slot" => block.slot(), - "method" => "new_payload", + ?validation_error, + ?execution_block_hash, + root = ?block.tree_hash_root(), + graffiti = block.body().graffiti().as_utf8_lossy(), + proposer_index = block.proposer_index(), + slot = %block.slot(), + method = "new_payload", + "Invalid execution payload block hash" ); // Returning an error here should be sufficient to invalidate the block. We have no @@ -278,10 +275,9 @@ pub async fn validate_merge_block( None => { if allow_optimistic_import == AllowOptimisticImport::Yes { debug!( - chain.log, - "Optimistically importing merge transition block"; - "block_hash" => ?execution_payload.parent_hash(), - "msg" => "the terminal block/parent was unavailable" + block_hash = ?execution_payload.parent_hash(), + msg = "the terminal block/parent was unavailable", + "Optimistically importing merge transition block" ); Ok(()) } else { diff --git a/beacon_node/beacon_chain/src/fetch_blobs.rs b/beacon_node/beacon_chain/src/fetch_blobs.rs index 6e365f936d..f1da1ffc2f 100644 --- a/beacon_node/beacon_chain/src/fetch_blobs.rs +++ b/beacon_node/beacon_chain/src/fetch_blobs.rs @@ -14,11 +14,11 @@ use crate::{metrics, AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes use execution_layer::json_structures::BlobAndProofV1; use execution_layer::Error as ExecutionLayerError; use metrics::{inc_counter, inc_counter_by, TryExt}; -use slog::{debug, error, o, Logger}; use ssz_types::FixedVector; use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash; use std::sync::Arc; use tokio::sync::oneshot; +use tracing::{debug, error}; use types::blob_sidecar::{BlobSidecarError, FixedBlobSidecarList}; use types::{ BeaconStateError, BlobSidecar, ChainSpec, DataColumnSidecar, DataColumnSidecarList, EthSpec, @@ -50,11 +50,6 @@ pub async fn fetch_and_process_engine_blobs( block: Arc>>, publish_fn: impl Fn(BlobsOrDataColumns) + Send + 'static, ) -> Result, FetchEngineBlobError> { - let block_root_str = format!("{:?}", block_root); - let log = chain - .log - .new(o!("service" => "fetch_engine_blobs", "block_root" => block_root_str)); - let versioned_hashes = if let Some(kzg_commitments) = block .message() .body() @@ -67,10 +62,7 @@ pub async fn fetch_and_process_engine_blobs( .map(kzg_commitment_to_versioned_hash) .collect::>() } else { - debug!( - log, - "Fetch blobs not triggered - none required"; - ); + debug!("Fetch blobs not triggered - none required"); return Ok(None); }; @@ -81,22 +73,14 @@ pub async fn fetch_and_process_engine_blobs( .as_ref() .ok_or(FetchEngineBlobError::ExecutionLayerMissing)?; - debug!( - log, - "Fetching blobs from the EL"; - "num_expected_blobs" => num_expected_blobs, - ); + debug!(num_expected_blobs, "Fetching blobs from the EL"); let response = execution_layer .get_blobs(versioned_hashes) .await .map_err(FetchEngineBlobError::RequestFailed)?; if response.is_empty() || response.iter().all(|opt| opt.is_none()) { - debug!( - log, - "No blobs fetched from the EL"; - "num_expected_blobs" => num_expected_blobs, - ); + debug!(num_expected_blobs, "No blobs fetched from the EL"); inc_counter(&metrics::BLOBS_FROM_EL_MISS_TOTAL); return Ok(None); } else { @@ -154,11 +138,8 @@ pub async fn fetch_and_process_engine_blobs( // Partial blobs response isn't useful for PeerDAS, so we don't bother building and publishing data columns. if num_fetched_blobs != num_expected_blobs { debug!( - log, - "Not all blobs fetched from the EL"; - "info" => "Unable to compute data columns", - "num_fetched_blobs" => num_fetched_blobs, - "num_expected_blobs" => num_expected_blobs, + info = "Unable to compute data columns", + num_fetched_blobs, num_expected_blobs, "Not all blobs fetched from the EL" ); return Ok(None); } @@ -170,9 +151,21 @@ pub async fn fetch_and_process_engine_blobs( { // Avoid computing columns if block has already been imported. debug!( - log, - "Ignoring EL blobs response"; - "info" => "block has already been imported", + info = "block has already been imported", + "Ignoring EL blobs response" + ); + return Ok(None); + } + + if chain + .canonical_head + .fork_choice_read_lock() + .contains_block(&block_root) + { + // Avoid computing columns if block has already been imported. + debug!( + info = "block has already been imported", + "Ignoring EL blobs response" ); return Ok(None); } @@ -182,7 +175,6 @@ pub async fn fetch_and_process_engine_blobs( block.clone(), fixed_blob_sidecar_list.clone(), publish_fn, - log.clone(), ); Some(data_columns_receiver) @@ -194,11 +186,7 @@ pub async fn fetch_and_process_engine_blobs( None }; - debug!( - log, - "Processing engine blobs"; - "num_fetched_blobs" => num_fetched_blobs, - ); + debug!(num_fetched_blobs, "Processing engine blobs"); let availability_processing_status = chain .process_engine_blobs( @@ -226,7 +214,6 @@ fn spawn_compute_and_publish_data_columns_task( block: Arc>>, blobs: FixedBlobSidecarList, publish_fn: impl Fn(BlobsOrDataColumns) + Send + 'static, - log: Logger, ) -> oneshot::Receiver>>> { let chain_cloned = chain.clone(); let (data_columns_sender, data_columns_receiver) = oneshot::channel(); @@ -254,9 +241,8 @@ fn spawn_compute_and_publish_data_columns_task( Ok(d) => d, Err(e) => { error!( - log, - "Failed to build data column sidecars from blobs"; - "error" => ?e + error = ?e, + "Failed to build data column sidecars from blobs" ); return; } @@ -266,10 +252,7 @@ fn spawn_compute_and_publish_data_columns_task( // Data column receiver have been dropped - block may have already been imported. // This race condition exists because gossip columns may arrive and trigger block // import during the computation. Here we just drop the computed columns. - debug!( - log, - "Failed to send computed data columns"; - ); + debug!("Failed to send computed data columns"); return; }; diff --git a/beacon_node/beacon_chain/src/fork_revert.rs b/beacon_node/beacon_chain/src/fork_revert.rs index 8d1c29f46f..c500e1b4b6 100644 --- a/beacon_node/beacon_chain/src/fork_revert.rs +++ b/beacon_node/beacon_chain/src/fork_revert.rs @@ -1,7 +1,6 @@ use crate::{BeaconForkChoiceStore, BeaconSnapshot}; use fork_choice::{ForkChoice, PayloadVerificationStatus}; use itertools::process_results; -use slog::{info, warn, Logger}; use state_processing::state_advance::complete_state_advance; use state_processing::{ per_block_processing, per_block_processing::BlockSignatureStrategy, ConsensusContext, @@ -10,6 +9,7 @@ use state_processing::{ use std::sync::Arc; use std::time::Duration; use store::{iter::ParentRootBlockIterator, HotColdDB, ItemStore}; +use tracing::{info, warn}; use types::{BeaconState, ChainSpec, EthSpec, ForkName, Hash256, SignedBeaconBlock, Slot}; const CORRUPT_DB_MESSAGE: &str = "The database could be corrupt. Check its file permissions or \ @@ -27,7 +27,6 @@ pub fn revert_to_fork_boundary, Cold: ItemStore head_block_root: Hash256, store: Arc>, spec: &ChainSpec, - log: &Logger, ) -> Result<(Hash256, SignedBeaconBlock), String> { let current_fork = spec.fork_name_at_slot::(current_slot); let fork_epoch = spec @@ -42,10 +41,9 @@ pub fn revert_to_fork_boundary, Cold: ItemStore } warn!( - log, - "Reverting invalid head block"; - "target_fork" => %current_fork, - "fork_epoch" => fork_epoch, + target_fork = %current_fork, + %fork_epoch, + "Reverting invalid head block" ); let block_iter = ParentRootBlockIterator::fork_tolerant(&store, head_block_root); @@ -55,10 +53,9 @@ pub fn revert_to_fork_boundary, Cold: ItemStore Some((block_root, block)) } else { info!( - log, - "Reverting block"; - "block_root" => ?block_root, - "slot" => block.slot(), + ?block_root, + slot = %block.slot(), + "Reverting block" ); None } diff --git a/beacon_node/beacon_chain/src/graffiti_calculator.rs b/beacon_node/beacon_chain/src/graffiti_calculator.rs index 8692d374ed..23d1d69b1c 100644 --- a/beacon_node/beacon_chain/src/graffiti_calculator.rs +++ b/beacon_node/beacon_chain/src/graffiti_calculator.rs @@ -1,11 +1,12 @@ use crate::BeaconChain; use crate::BeaconChainTypes; use execution_layer::{http::ENGINE_GET_CLIENT_VERSION_V1, CommitPrefix, ExecutionLayer}; +use logging::crit; use serde::{Deserialize, Serialize}; -use slog::{crit, debug, error, warn, Logger}; use slot_clock::SlotClock; use std::{fmt::Debug, time::Duration}; use task_executor::TaskExecutor; +use tracing::{debug, error, warn}; use types::{EthSpec, Graffiti, GRAFFITI_BYTES_LEN}; const ENGINE_VERSION_AGE_LIMIT_EPOCH_MULTIPLE: u32 = 6; // 6 epochs @@ -51,7 +52,6 @@ pub struct GraffitiCalculator { pub beacon_graffiti: GraffitiOrigin, execution_layer: Option>, pub epoch_duration: Duration, - log: Logger, } impl GraffitiCalculator { @@ -59,13 +59,11 @@ impl GraffitiCalculator { beacon_graffiti: GraffitiOrigin, execution_layer: Option>, epoch_duration: Duration, - log: Logger, ) -> Self { Self { beacon_graffiti, execution_layer, epoch_duration, - log, } } @@ -86,7 +84,7 @@ impl GraffitiCalculator { let Some(execution_layer) = self.execution_layer.as_ref() else { // Return default graffiti if there is no execution layer. This // shouldn't occur if we're actually producing blocks. - crit!(self.log, "No execution layer available for graffiti calculation during block production!"); + crit!("No execution layer available for graffiti calculation during block production!"); return default_graffiti; }; @@ -101,7 +99,7 @@ impl GraffitiCalculator { { Ok(engine_versions) => engine_versions, Err(el_error) => { - warn!(self.log, "Failed to determine execution engine version for graffiti"; "error" => ?el_error); + warn!(error = ?el_error, "Failed to determine execution engine version for graffiti"); return default_graffiti; } }; @@ -109,9 +107,8 @@ impl GraffitiCalculator { let Some(engine_version) = engine_versions.first() else { // Got an empty array which indicates the EL doesn't support the method debug!( - self.log, "Using default lighthouse graffiti: EL does not support {} method", - ENGINE_GET_CLIENT_VERSION_V1; + ENGINE_GET_CLIENT_VERSION_V1 ); return default_graffiti; }; @@ -119,19 +116,20 @@ impl GraffitiCalculator { // More than one version implies lighthouse is connected to // an EL multiplexer. We don't support modifying the graffiti // with these configurations. - warn!( - self.log, - "Execution Engine multiplexer detected, using default graffiti" - ); + warn!("Execution Engine multiplexer detected, using default graffiti"); return default_graffiti; } - let lighthouse_commit_prefix = CommitPrefix::try_from(lighthouse_version::COMMIT_PREFIX.to_string()) - .unwrap_or_else(|error_message| { - // This really shouldn't happen but we want to definitly log if it does - crit!(self.log, "Failed to parse lighthouse commit prefix"; "error" => error_message); - CommitPrefix("00000000".to_string()) - }); + let lighthouse_commit_prefix = + CommitPrefix::try_from(lighthouse_version::COMMIT_PREFIX.to_string()) + .unwrap_or_else(|error_message| { + // This really shouldn't happen but we want to definitly log if it does + crit!( + error = error_message, + "Failed to parse lighthouse commit prefix" + ); + CommitPrefix("00000000".to_string()) + }); engine_version.calculate_graffiti(lighthouse_commit_prefix) } @@ -144,36 +142,24 @@ pub fn start_engine_version_cache_refresh_service( executor: TaskExecutor, ) { let Some(el_ref) = chain.execution_layer.as_ref() else { - debug!( - chain.log, - "No execution layer configured, not starting engine version cache refresh service" - ); + debug!("No execution layer configured, not starting engine version cache refresh service"); return; }; if matches!( chain.graffiti_calculator.beacon_graffiti, GraffitiOrigin::UserSpecified(_) ) { - debug!( - chain.log, - "Graffiti is user-specified, not starting engine version cache refresh service" - ); + debug!("Graffiti is user-specified, not starting engine version cache refresh service"); return; } let execution_layer = el_ref.clone(); - let log = chain.log.clone(); let slot_clock = chain.slot_clock.clone(); let epoch_duration = chain.graffiti_calculator.epoch_duration; executor.spawn( async move { - engine_version_cache_refresh_service::( - execution_layer, - slot_clock, - epoch_duration, - log, - ) - .await + engine_version_cache_refresh_service::(execution_layer, slot_clock, epoch_duration) + .await }, "engine_version_cache_refresh_service", ); @@ -183,13 +169,15 @@ async fn engine_version_cache_refresh_service( execution_layer: ExecutionLayer, slot_clock: T::SlotClock, epoch_duration: Duration, - log: Logger, ) { // Preload the engine version cache after a brief delay to allow for EL initialization. // This initial priming ensures cache readiness before the service's regular update cycle begins. tokio::time::sleep(ENGINE_VERSION_CACHE_PRELOAD_STARTUP_DELAY).await; if let Err(e) = execution_layer.get_engine_version(None).await { - debug!(log, "Failed to preload engine version cache"; "error" => format!("{:?}", e)); + debug!( + error = ?e, + "Failed to preload engine version cache" + ); } // this service should run 3/8 of the way through the epoch @@ -203,18 +191,14 @@ async fn engine_version_cache_refresh_service( let firing_delay = partial_firing_delay + duration_to_next_epoch + epoch_delay; tokio::time::sleep(firing_delay).await; - debug!( - log, - "Engine version cache refresh service firing"; - ); + debug!("Engine version cache refresh service firing"); match execution_layer.get_engine_version(None).await { - Err(e) => warn!(log, "Failed to populate engine version cache"; "error" => ?e), + Err(e) => warn!( error = ?e, "Failed to populate engine version cache"), Ok(versions) => { if versions.is_empty() { // Empty array indicates the EL doesn't support the method debug!( - log, "EL does not support {} method. Sleeping twice as long before retry", ENGINE_GET_CLIENT_VERSION_V1 ); @@ -227,7 +211,7 @@ async fn engine_version_cache_refresh_service( } } None => { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. tokio::time::sleep(slot_clock.slot_duration()).await; } @@ -241,10 +225,10 @@ mod tests { use crate::ChainConfig; use execution_layer::test_utils::{DEFAULT_CLIENT_VERSION, DEFAULT_ENGINE_CAPABILITIES}; use execution_layer::EngineCapabilities; - use slog::info; use std::sync::Arc; use std::sync::LazyLock; use std::time::Duration; + use tracing::info; use types::{ChainSpec, Graffiti, Keypair, MinimalEthSpec, GRAFFITI_BYTES_LEN}; const VALIDATOR_COUNT: usize = 48; @@ -261,7 +245,6 @@ mod tests { .spec(spec) .chain_config(chain_config.unwrap_or_default()) .keypairs(KEYPAIRS[0..validator_count].to_vec()) - .logger(logging::test_logger()) .fresh_ephemeral_store() .mock_execution_layer() .build(); @@ -302,7 +285,10 @@ mod tests { let graffiti_str = std::str::from_utf8(graffiti_slice).expect("bytes should convert nicely to ascii"); - info!(harness.chain.log, "results"; "lighthouse_version" => lighthouse_version::VERSION, "graffiti_str" => graffiti_str); + info!( + lighthouse_version = lighthouse_version::VERSION, + graffiti_str, "results" + ); println!("lighthouse_version: '{}'", lighthouse_version::VERSION); println!("graffiti_str: '{}'", graffiti_str); @@ -339,7 +325,7 @@ mod tests { std::str::from_utf8(&found_graffiti_bytes[..expected_graffiti_prefix_len]) .expect("bytes should convert nicely to ascii"); - info!(harness.chain.log, "results"; "expected_graffiti_string" => &expected_graffiti_string, "found_graffiti_string" => &found_graffiti_string); + info!(expected_graffiti_string, found_graffiti_string, "results"); println!("expected_graffiti_string: '{}'", expected_graffiti_string); println!("found_graffiti_string: '{}'", found_graffiti_string); diff --git a/beacon_node/beacon_chain/src/historical_blocks.rs b/beacon_node/beacon_chain/src/historical_blocks.rs index a48f32e7b4..7169c86174 100644 --- a/beacon_node/beacon_chain/src/historical_blocks.rs +++ b/beacon_node/beacon_chain/src/historical_blocks.rs @@ -1,7 +1,6 @@ -use crate::data_availability_checker::AvailableBlock; +use crate::data_availability_checker::{AvailableBlock, AvailableBlockData}; use crate::{metrics, BeaconChain, BeaconChainTypes}; use itertools::Itertools; -use slog::debug; use state_processing::{ per_block_processing::ParallelSignatureSets, signature_sets::{block_proposal_signature_set_from_parts, Error as SignatureSetError}, @@ -12,6 +11,7 @@ use std::time::Duration; use store::metadata::DataColumnInfo; use store::{AnchorInfo, BlobInfo, DBColumn, Error as StoreError, KeyValueStore, KeyValueStoreOp}; use strum::IntoStaticStr; +use tracing::debug; use types::{FixedBytesExtended, Hash256, Slot}; /// Use a longer timeout on the pubkey cache. @@ -82,11 +82,10 @@ impl BeaconChain { if blocks_to_import.len() != total_blocks { debug!( - self.log, - "Ignoring some historic blocks"; - "oldest_block_slot" => anchor_info.oldest_block_slot, - "total_blocks" => total_blocks, - "ignored" => total_blocks.saturating_sub(blocks_to_import.len()), + oldest_block_slot = %anchor_info.oldest_block_slot, + total_blocks, + ignored = total_blocks.saturating_sub(blocks_to_import.len()), + "Ignoring some historic blocks" ); } @@ -105,7 +104,7 @@ impl BeaconChain { let blob_batch_size = blocks_to_import .iter() - .filter(|available_block| available_block.blobs().is_some()) + .filter(|available_block| available_block.has_blobs()) .count() .saturating_mul(n_blob_ops_per_block); @@ -114,14 +113,13 @@ impl BeaconChain { let mut new_oldest_blob_slot = blob_info.oldest_blob_slot; let mut new_oldest_data_column_slot = data_column_info.oldest_data_column_slot; - let mut blob_batch = Vec::with_capacity(blob_batch_size); + let mut blob_batch = Vec::::with_capacity(blob_batch_size); let mut cold_batch = Vec::with_capacity(blocks_to_import.len()); let mut hot_batch = Vec::with_capacity(blocks_to_import.len()); let mut signed_blocks = Vec::with_capacity(blocks_to_import.len()); for available_block in blocks_to_import.into_iter().rev() { - let (block_root, block, maybe_blobs, maybe_data_columns) = - available_block.deconstruct(); + let (block_root, block, block_data) = available_block.deconstruct(); if block_root != expected_block_root { return Err(HistoricalBlockError::MismatchedBlockRoot { @@ -144,17 +142,26 @@ impl BeaconChain { ); } - // Store the blobs too - if let Some(blobs) = maybe_blobs { - new_oldest_blob_slot = Some(block.slot()); - self.store - .blobs_as_kv_store_ops(&block_root, blobs, &mut blob_batch); + match &block_data { + AvailableBlockData::NoData => {} + AvailableBlockData::Blobs(..) => { + new_oldest_blob_slot = Some(block.slot()); + } + AvailableBlockData::DataColumns(_) | AvailableBlockData::DataColumnsRecv(_) => { + new_oldest_data_column_slot = Some(block.slot()); + } } - // Store the data columns too - if let Some(data_columns) = maybe_data_columns { - new_oldest_data_column_slot = Some(block.slot()); - self.store - .data_columns_as_kv_store_ops(&block_root, data_columns, &mut blob_batch); + + // Store the blobs or data columns too + if let Some(op) = self + .get_blobs_or_columns_store_op(block_root, block_data) + .map_err(|e| { + HistoricalBlockError::StoreError(StoreError::DBError { + message: format!("get_blobs_or_columns_store_op error {e:?}"), + }) + })? + { + blob_batch.extend(self.store.convert_to_kv_batch(vec![op])?); } // Store block roots, including at all skip slots in the freezer DB. diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index 78442d8df0..c9173dc0d7 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -2,12 +2,12 @@ use crate::errors::BeaconChainError; use crate::{metrics, BeaconChainTypes, BeaconStore}; use parking_lot::{Mutex, RwLock}; use safe_arith::SafeArith; -use slog::{debug, Logger}; use ssz::Decode; use std::num::NonZeroUsize; use std::sync::Arc; use store::DBColumn; use store::KeyValueStore; +use tracing::debug; use tree_hash::TreeHash; use types::non_zero_usize::new_non_zero_usize; use types::{ @@ -82,7 +82,6 @@ impl LightClientServerCache { block_slot: Slot, block_parent_root: &Hash256, sync_aggregate: &SyncAggregate, - log: &Logger, chain_spec: &ChainSpec, ) -> Result<(), BeaconChainError> { metrics::inc_counter(&metrics::LIGHT_CLIENT_SERVER_CACHE_PROCESSING_REQUESTS); @@ -170,9 +169,8 @@ impl LightClientServerCache { )?); } else { debug!( - log, - "Finalized block not available in store for light_client server"; - "finalized_block_root" => format!("{}", cached_parts.finalized_block_root), + finalized_block_root = %cached_parts.finalized_block_root, + "Finalized block not available in store for light_client server" ); } } diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index bc4b8e1ed8..b64da00e76 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -3,7 +3,6 @@ use crate::errors::BeaconChainError; use crate::head_tracker::{HeadTracker, SszHeadTracker}; use crate::persisted_beacon_chain::{PersistedBeaconChain, DUMMY_CANONICAL_HEAD_BLOCK_ROOT}; use parking_lot::Mutex; -use slog::{debug, error, info, warn, Logger}; use std::collections::{HashMap, HashSet}; use std::mem; use std::sync::{mpsc, Arc}; @@ -13,6 +12,7 @@ use store::hot_cold_store::{migrate_database, HotColdDBError}; use store::iter::RootsIterator; use store::{Error, ItemStore, StoreItem, StoreOp}; pub use store::{HotColdDB, MemoryStore}; +use tracing::{debug, error, info, warn}; use types::{ BeaconState, BeaconStateError, BeaconStateHash, Checkpoint, Epoch, EthSpec, FixedBytesExtended, Hash256, SignedBeaconBlockHash, Slot, @@ -44,7 +44,6 @@ pub struct BackgroundMigrator, Cold: ItemStore> tx_thread: Option, thread::JoinHandle<()>)>>, /// Genesis block root, for persisting the `PersistedBeaconChain`. genesis_block_root: Hash256, - log: Logger, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -140,7 +139,6 @@ impl, Cold: ItemStore> BackgroundMigrator>, config: MigratorConfig, genesis_block_root: Hash256, - log: Logger, ) -> Self { // Estimate last migration run from DB split slot. let prev_migration = Arc::new(Mutex::new(PrevMigration { @@ -150,14 +148,13 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator>, opt_tx: Option>, - log: &Logger, ) { match db.reconstruct_historic_states(Some(BLOCKS_PER_RECONSTRUCTION)) { Ok(()) => { @@ -221,9 +217,8 @@ impl, Cold: ItemStore> BackgroundMigrator ?e + error = ?e, + "Unable to requeue reconstruction notification" ); } } @@ -231,24 +226,18 @@ impl, Cold: ItemStore> BackgroundMigrator { error!( - log, - "State reconstruction failed"; - "error" => ?e, + error = ?e, + "State reconstruction failed" ); } } } - pub fn run_prune_blobs( - db: Arc>, - data_availability_boundary: Epoch, - log: &Logger, - ) { + pub fn run_prune_blobs(db: Arc>, data_availability_boundary: Epoch) { if let Err(e) = db.try_prune_blobs(false, data_availability_boundary) { error!( - log, - "Blob pruning failed"; - "error" => ?e, + error = ?e, + "Blob pruning failed" ); } } @@ -264,7 +253,7 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator format!("{:?}", thread_err) + reason = ?thread_err, + "Migration thread died, so it was restarted" ); } @@ -290,21 +278,16 @@ impl, Cold: ItemStore> BackgroundMigrator>, - notif: FinalizationNotification, - log: &Logger, - ) { + fn run_migration(db: Arc>, notif: FinalizationNotification) { // Do not run too frequently. let epoch = notif.finalized_checkpoint.epoch; let mut prev_migration = notif.prev_migration.lock(); if epoch < prev_migration.epoch + prev_migration.epochs_per_migration { debug!( - log, - "Database consolidation deferred"; - "last_finalized_epoch" => prev_migration.epoch, - "new_finalized_epoch" => epoch, - "epochs_per_migration" => prev_migration.epochs_per_migration, + last_finalized_epoch = %prev_migration.epoch, + new_finalized_epoch = %epoch, + epochs_per_migration = prev_migration.epochs_per_migration, + "Database consolidation deferred" ); return; } @@ -315,7 +298,7 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator state, other => { error!( - log, - "Migrator failed to load state"; - "state_root" => ?finalized_state_root, - "error" => ?other + state_root = ?finalized_state_root, + error = ?other, + "Migrator failed to load state" ); return; } @@ -340,16 +322,14 @@ impl, Cold: ItemStore> BackgroundMigrator old_finalized_checkpoint, Ok(PruningOutcome::DeferredConcurrentHeadTrackerMutation) => { warn!( - log, - "Pruning deferred because of a concurrent mutation"; - "message" => "this is expected only very rarely!" + message = "this is expected only very rarely!", + "Pruning deferred because of a concurrent mutation" ); return; } @@ -358,16 +338,15 @@ impl, Cold: ItemStore> BackgroundMigrator { warn!( - log, - "Ignoring out of order finalization request"; - "old_finalized_epoch" => old_finalized_checkpoint.epoch, - "new_finalized_epoch" => new_finalized_checkpoint.epoch, - "message" => "this is expected occasionally due to a (harmless) race condition" + old_finalized_epoch = %old_finalized_checkpoint.epoch, + new_finalized_epoch = %new_finalized_checkpoint.epoch, + message = "this is expected occasionally due to a (harmless) race condition", + "Ignoring out of order finalization request" ); return; } Err(e) => { - warn!(log, "Block pruning failed"; "error" => ?e); + warn!(error = ?e,"Block pruning failed"); return; } }; @@ -381,17 +360,12 @@ impl, Cold: ItemStore> BackgroundMigrator {} Err(Error::HotColdDBError(HotColdDBError::FreezeSlotUnaligned(slot))) => { debug!( - log, - "Database migration postponed, unaligned finalized block"; - "slot" => slot.as_u64() + slot = slot.as_u64(), + "Database migration postponed, unaligned finalized block" ); } Err(e) => { - warn!( - log, - "Database migration failed"; - "error" => format!("{:?}", e) - ); + warn!(error = ?e, "Database migration failed"); return; } }; @@ -401,12 +375,11 @@ impl, Cold: ItemStore> BackgroundMigrator format!("{:?}", e)); + warn!(error = ?e, "Database compaction failed"); } - debug!(log, "Database consolidation complete"); + debug!("Database consolidation complete"); } /// Spawn a new child thread to run the migration process. @@ -414,7 +387,6 @@ impl, Cold: ItemStore> BackgroundMigrator>, - log: Logger, ) -> (mpsc::Sender, thread::JoinHandle<()>) { let (tx, rx) = mpsc::channel(); let inner_tx = tx.clone(); @@ -452,13 +424,13 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator, new_finalized_checkpoint: Checkpoint, genesis_block_root: Hash256, - log: &Logger, ) -> Result { let old_finalized_checkpoint = store @@ -515,10 +486,9 @@ impl, Cold: ItemStore> BackgroundMigrator old_finalized_checkpoint.epoch, - "new_finalized_epoch" => new_finalized_checkpoint.epoch, + old_finalized_epoch = %old_finalized_checkpoint.epoch, + new_finalized_epoch = %new_finalized_checkpoint.epoch, + "Starting database pruning" ); // For each slot between the new finalized checkpoint and the old finalized checkpoint, // collect the beacon block root and state root of the canonical chain. @@ -546,11 +516,10 @@ impl, Cold: ItemStore> BackgroundMigrator format!("{:?}", old_finalized_checkpoint.root), - "new_finalized_root" => format!("{:?}", new_finalized_checkpoint.root), - "head_count" => heads.len(), + old_finalized_root = ?old_finalized_checkpoint.root, + new_finalized_root = ?new_finalized_checkpoint.root, + head_count = heads.len(), + "Extra pruning information" ); for (head_hash, head_slot) in heads { @@ -565,10 +534,9 @@ impl, Cold: ItemStore> BackgroundMigrator { warn!( - log, - "Forgetting invalid head block"; - "block_root" => ?head_hash, - "error" => ?e, + block_root = ?head_hash, + error = ?e, + "Forgetting invalid head block" ); abandoned_heads.insert(head_hash); continue; @@ -606,10 +574,9 @@ impl, Cold: ItemStore> BackgroundMigrator format!("{:?}", head_hash), - "head_slot" => head_slot, + head_block_root = ?head_hash, + %head_slot, + "Found a chain that should already have been pruned" ); potentially_abandoned_head.take(); break; @@ -663,10 +630,9 @@ impl, Cold: ItemStore> BackgroundMigrator format!("{:?}", abandoned_head), - "head_slot" => head_slot, + head_block_root = ?abandoned_head, + %head_slot, + "Pruning head" ); abandoned_heads.insert(abandoned_head); abandoned_blocks.extend( @@ -740,7 +706,7 @@ impl, Cold: ItemStore> BackgroundMigrator, Cold: ItemStore> BackgroundMigrator>, old_finalized_epoch: Epoch, new_finalized_epoch: Epoch, - log: &Logger, ) -> Result<(), Error> { if !db.compact_on_prune() { return Ok(()); @@ -775,10 +740,9 @@ impl, Cold: ItemStore> BackgroundMigrator MIN_COMPACTION_PERIOD_SECONDS) { info!( - log, - "Starting database compaction"; - "old_finalized_epoch" => old_finalized_epoch, - "new_finalized_epoch" => new_finalized_epoch, + %old_finalized_epoch, + %new_finalized_epoch, + "Starting database compaction" ); db.compact()?; @@ -787,7 +751,7 @@ impl, Cold: ItemStore> BackgroundMigrator::EthSpec> + pub fn from_block(block: BeaconBlockRef) -> Self { + Self { + root: block.tree_hash_root(), + slot: block.slot(), + } + } + + pub fn root(&self) -> &Hash256 { + &self.root + } + + pub fn slot(&self) -> &Slot { + &self.slot + } + + pub fn persist_in_store(&self, store: A) -> Result<(), StoreError> + where + T: BeaconChainTypes, + A: AsRef>, + { + if store + .as_ref() + .item_exists::(&self.root)? + { + Ok(()) + } else { + store.as_ref().put_item(&self.root, self) + } + } + + pub fn remove_from_store(&self, store: A) -> Result<(), StoreError> + where + T: BeaconChainTypes, + A: AsRef>, + { + store + .as_ref() + .hot_db + .key_delete(OTBColumn.into(), self.root.as_slice()) + } + + fn is_canonical( + &self, + chain: &BeaconChain, + ) -> Result { + Ok(chain + .forwards_iter_block_roots_until(self.slot, self.slot)? + .next() + .transpose()? + .map(|(root, _)| root) + == Some(self.root)) + } +} + +impl StoreItem for OptimisticTransitionBlock { + fn db_column() -> DBColumn { + OTBColumn + } + + fn as_store_bytes(&self) -> Vec { + self.as_ssz_bytes() + } + + fn from_store_bytes(bytes: &[u8]) -> Result { + Ok(Self::from_ssz_bytes(bytes)?) + } +} + +/// The routine is expected to run once per epoch, 1/4th through the epoch. +pub const EPOCH_DELAY_FACTOR: u32 = 4; + +/// Spawns a routine which checks the validity of any optimistically imported transition blocks +/// +/// This routine will run once per epoch, at `epoch_duration / EPOCH_DELAY_FACTOR` after +/// the start of each epoch. +/// +/// The service will not be started if there is no `execution_layer` on the `chain`. +pub fn start_otb_verification_service( + executor: TaskExecutor, + chain: Arc>, +) { + // Avoid spawning the service if there's no EL, it'll just error anyway. + if chain.execution_layer.is_some() { + executor.spawn( + async move { otb_verification_service(chain).await }, + "otb_verification_service", + ); + } +} + +pub fn load_optimistic_transition_blocks( + chain: &BeaconChain, +) -> Result, StoreError> { + process_results( + chain.store.hot_db.iter_column::(OTBColumn), + |iter| { + iter.map(|(_, bytes)| OptimisticTransitionBlock::from_store_bytes(&bytes)) + .collect() + }, + )? +} + +#[derive(Debug)] +pub enum Error { + ForkChoice(String), + BeaconChain(BeaconChainError), + StoreError(StoreError), + NoBlockFound(OptimisticTransitionBlock), +} + +pub async fn validate_optimistic_transition_blocks( + chain: &Arc>, + otbs: Vec, +) -> Result<(), Error> { + let finalized_slot = chain + .canonical_head + .fork_choice_read_lock() + .get_finalized_block() + .map_err(|e| Error::ForkChoice(format!("{:?}", e)))? + .slot; + + // separate otbs into + // non-canonical + // finalized canonical + // unfinalized canonical + let mut non_canonical_otbs = vec![]; + let (finalized_canonical_otbs, unfinalized_canonical_otbs) = process_results( + otbs.into_iter().map(|otb| { + otb.is_canonical(chain) + .map(|is_canonical| (otb, is_canonical)) + }), + |pair_iter| { + pair_iter + .filter_map(|(otb, is_canonical)| { + if is_canonical { + Some(otb) + } else { + non_canonical_otbs.push(otb); + None + } + }) + .partition::, _>(|otb| *otb.slot() <= finalized_slot) + }, + ) + .map_err(Error::BeaconChain)?; + + // remove non-canonical blocks that conflict with finalized checkpoint from the database + for otb in non_canonical_otbs { + if *otb.slot() <= finalized_slot { + otb.remove_from_store::(&chain.store) + .map_err(Error::StoreError)?; + } + } + + // ensure finalized canonical otb are valid, otherwise kill client + for otb in finalized_canonical_otbs { + match chain.get_block(otb.root()).await { + Ok(Some(block)) => { + match validate_merge_block(chain, block.message(), AllowOptimisticImport::No).await + { + Ok(()) => { + // merge transition block is valid, remove it from OTB + otb.remove_from_store::(&chain.store) + .map_err(Error::StoreError)?; + info!( + block_root = %otb.root(), + "type" = "finalized", + "Validated merge transition block" + ); + } + // The block was not able to be verified by the EL. Leave the OTB in the + // database since the EL is likely still syncing and may verify the block + // later. + Err(BlockError::ExecutionPayloadError( + ExecutionPayloadError::UnverifiedNonOptimisticCandidate, + )) => (), + Err(BlockError::ExecutionPayloadError( + ExecutionPayloadError::InvalidTerminalPoWBlock { .. }, + )) => { + // Finalized Merge Transition Block is Invalid! Kill the Client! + crit!( + msg = "You must use the `--purge-db` flag to clear the database and restart sync. \ + You may be on a hostile network.", + block_hash = ?block.canonical_root(), + "Finalized merge transition block is invalid!" + ); + let mut shutdown_sender = chain.shutdown_sender(); + if let Err(e) = shutdown_sender.try_send(ShutdownReason::Failure( + INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON, + )) { + crit!( + error = ?e, + shutdown_reason = INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON, + "Failed to shut down client" + ); + } + } + _ => {} + } + } + Ok(None) => return Err(Error::NoBlockFound(otb)), + // Our database has pruned the payload and the payload was unavailable on the EL since + // the EL is still syncing or the payload is non-canonical. + Err(BeaconChainError::BlockHashMissingFromExecutionLayer(_)) => (), + Err(e) => return Err(Error::BeaconChain(e)), + } + } + + // attempt to validate any non-finalized canonical otb blocks + for otb in unfinalized_canonical_otbs { + match chain.get_block(otb.root()).await { + Ok(Some(block)) => { + match validate_merge_block(chain, block.message(), AllowOptimisticImport::No).await + { + Ok(()) => { + // merge transition block is valid, remove it from OTB + otb.remove_from_store::(&chain.store) + .map_err(Error::StoreError)?; + info!( + block_root = ?otb.root(), + "type" = "not finalized", + "Validated merge transition block" + ); + } + // The block was not able to be verified by the EL. Leave the OTB in the + // database since the EL is likely still syncing and may verify the block + // later. + Err(BlockError::ExecutionPayloadError( + ExecutionPayloadError::UnverifiedNonOptimisticCandidate, + )) => (), + Err(BlockError::ExecutionPayloadError( + ExecutionPayloadError::InvalidTerminalPoWBlock { .. }, + )) => { + // Unfinalized Merge Transition Block is Invalid -> Run process_invalid_execution_payload + warn!( + block_root = ?otb.root(), + "Merge transition block invalid" + ); + chain + .process_invalid_execution_payload( + &InvalidationOperation::InvalidateOne { + block_root: *otb.root(), + }, + ) + .await + .map_err(|e| { + warn!( + error = ?e, + location = "process_invalid_execution_payload", + "Error checking merge transition block" + ); + Error::BeaconChain(e) + })?; + } + _ => {} + } + } + Ok(None) => return Err(Error::NoBlockFound(otb)), + // Our database has pruned the payload and the payload was unavailable on the EL since + // the EL is still syncing or the payload is non-canonical. + Err(BeaconChainError::BlockHashMissingFromExecutionLayer(_)) => (), + Err(e) => return Err(Error::BeaconChain(e)), + } + } + + Ok(()) +} + +/// Loop until any optimistically imported merge transition blocks have been verified and +/// the merge has been finalized. +async fn otb_verification_service(chain: Arc>) { + let epoch_duration = chain.slot_clock.slot_duration() * T::EthSpec::slots_per_epoch() as u32; + loop { + match chain + .slot_clock + .duration_to_next_epoch(T::EthSpec::slots_per_epoch()) + { + Some(duration) => { + let additional_delay = epoch_duration / EPOCH_DELAY_FACTOR; + sleep(duration + additional_delay).await; + + debug!("OTB verification service firing"); + + if !is_merge_transition_complete( + &chain.canonical_head.cached_head().snapshot.beacon_state, + ) { + // We are pre-merge. Nothing to do yet. + continue; + } + + // load all optimistically imported transition blocks from the database + match load_optimistic_transition_blocks(chain.as_ref()) { + Ok(otbs) => { + if otbs.is_empty() { + if chain + .canonical_head + .fork_choice_read_lock() + .get_finalized_block() + .map_or(false, |block| { + block.execution_status.is_execution_enabled() + }) + { + // there are no optimistic blocks in the database, we can exit + // the service since the merge transition is finalized and we'll + // never see another transition block + break; + } else { + debug!( + info = "waiting for the merge transition to finalize", + "No optimistic transition blocks" + ) + } + } + if let Err(e) = validate_optimistic_transition_blocks(&chain, otbs).await { + warn!( + error = ?e, + "Error while validating optimistic transition blocks" + ); + } + } + Err(e) => { + error!( + error = ?e, + "Error loading optimistic transition blocks" + ); + } + }; + } + None => { + error!("Failed to read slot clock"); + // If we can't read the slot clock, just wait another slot. + sleep(chain.slot_clock.slot_duration()).await; + } + }; + } + debug!( + msg = "shutting down OTB verification service", + "No optimistic transition blocks in database" + ); +} diff --git a/beacon_node/beacon_chain/src/pre_finalization_cache.rs b/beacon_node/beacon_chain/src/pre_finalization_cache.rs index 22b76e026c..5bd45dc59f 100644 --- a/beacon_node/beacon_chain/src/pre_finalization_cache.rs +++ b/beacon_node/beacon_chain/src/pre_finalization_cache.rs @@ -2,9 +2,9 @@ use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; use itertools::process_results; use lru::LruCache; use parking_lot::Mutex; -use slog::debug; use std::num::NonZeroUsize; use std::time::Duration; +use tracing::debug; use types::non_zero_usize::new_non_zero_usize; use types::Hash256; @@ -87,10 +87,7 @@ impl BeaconChain { // blocks have been flushed out. Solving this issue isn't as simple as hooking the // beacon processor's functions that handle failed blocks because we need the block root // and it has been erased from the `BlockError` by that point. - debug!( - self.log, - "Pre-finalization lookup cache is full"; - ); + debug!("Pre-finalization lookup cache is full"); } Ok(false) } diff --git a/beacon_node/beacon_chain/src/proposer_prep_service.rs b/beacon_node/beacon_chain/src/proposer_prep_service.rs index 140a9659fc..14f7414abc 100644 --- a/beacon_node/beacon_chain/src/proposer_prep_service.rs +++ b/beacon_node/beacon_chain/src/proposer_prep_service.rs @@ -1,9 +1,9 @@ use crate::{BeaconChain, BeaconChainTypes}; -use slog::{debug, error}; use slot_clock::SlotClock; use std::sync::Arc; use task_executor::TaskExecutor; use tokio::time::sleep; +use tracing::{debug, error}; /// Spawns a routine which ensures the EL is provided advance notice of any block producers. /// @@ -38,10 +38,7 @@ async fn proposer_prep_service( slot_duration.saturating_sub(chain.config.prepare_payload_lookahead); sleep(duration + additional_delay).await; - debug!( - chain.log, - "Proposer prepare routine firing"; - ); + debug!("Proposer prepare routine firing"); let inner_chain = chain.clone(); executor.spawn( @@ -50,20 +47,19 @@ async fn proposer_prep_service( if let Err(e) = inner_chain.prepare_beacon_proposer(current_slot).await { error!( - inner_chain.log, - "Proposer prepare routine failed"; - "error" => ?e + error = ?e, + "Proposer prepare routine failed" ); } } else { - debug!(inner_chain.log, "No slot for proposer prepare routine"); + debug!("No slot for proposer prepare routine"); } }, "proposer_prep_update", ); } None => { - error!(chain.log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; } diff --git a/beacon_node/beacon_chain/src/schema_change.rs b/beacon_node/beacon_chain/src/schema_change.rs index 9504901229..ccfae1b182 100644 --- a/beacon_node/beacon_chain/src/schema_change.rs +++ b/beacon_node/beacon_chain/src/schema_change.rs @@ -4,7 +4,6 @@ mod migration_schema_v21; mod migration_schema_v22; use crate::beacon_chain::BeaconChainTypes; -use slog::Logger; use std::sync::Arc; use store::hot_cold_store::{HotColdDB, HotColdDBError}; use store::metadata::{SchemaVersion, CURRENT_SCHEMA_VERSION}; @@ -17,7 +16,6 @@ pub fn migrate_schema( genesis_state_root: Option, from: SchemaVersion, to: SchemaVersion, - log: Logger, ) -> Result<(), StoreError> { match (from, to) { // Migrating from the current schema version to itself is always OK, a no-op. @@ -25,39 +23,39 @@ pub fn migrate_schema( // Upgrade across multiple versions by recursively migrating one step at a time. (_, _) if from.as_u64() + 1 < to.as_u64() => { let next = SchemaVersion(from.as_u64() + 1); - migrate_schema::(db.clone(), genesis_state_root, from, next, log.clone())?; - migrate_schema::(db, genesis_state_root, next, to, log) + migrate_schema::(db.clone(), genesis_state_root, from, next)?; + migrate_schema::(db, genesis_state_root, next, to) } // Downgrade across multiple versions by recursively migrating one step at a time. (_, _) if to.as_u64() + 1 < from.as_u64() => { let next = SchemaVersion(from.as_u64() - 1); - migrate_schema::(db.clone(), genesis_state_root, from, next, log.clone())?; - migrate_schema::(db, genesis_state_root, next, to, log) + migrate_schema::(db.clone(), genesis_state_root, from, next)?; + migrate_schema::(db, genesis_state_root, next, to) } // // Migrations from before SchemaVersion(19) are deprecated. // (SchemaVersion(19), SchemaVersion(20)) => { - let ops = migration_schema_v20::upgrade_to_v20::(db.clone(), log)?; + let ops = migration_schema_v20::upgrade_to_v20::(db.clone())?; db.store_schema_version_atomically(to, ops) } (SchemaVersion(20), SchemaVersion(19)) => { - let ops = migration_schema_v20::downgrade_from_v20::(db.clone(), log)?; + let ops = migration_schema_v20::downgrade_from_v20::(db.clone())?; db.store_schema_version_atomically(to, ops) } (SchemaVersion(20), SchemaVersion(21)) => { - let ops = migration_schema_v21::upgrade_to_v21::(db.clone(), log)?; + let ops = migration_schema_v21::upgrade_to_v21::(db.clone())?; db.store_schema_version_atomically(to, ops) } (SchemaVersion(21), SchemaVersion(20)) => { - let ops = migration_schema_v21::downgrade_from_v21::(db.clone(), log)?; + let ops = migration_schema_v21::downgrade_from_v21::(db.clone())?; db.store_schema_version_atomically(to, ops) } (SchemaVersion(21), SchemaVersion(22)) => { // This migration needs to sync data between hot and cold DBs. The schema version is // bumped inside the upgrade_to_v22 fn - migration_schema_v22::upgrade_to_v22::(db.clone(), genesis_state_root, log) + migration_schema_v22::upgrade_to_v22::(db.clone(), genesis_state_root) } // Anything else is an error. (_, _) => Err(HotColdDBError::UnsupportedSchemaVersion { diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs index d556d5988d..13fde349f5 100644 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs @@ -2,16 +2,15 @@ use crate::beacon_chain::{BeaconChainTypes, OP_POOL_DB_KEY}; use operation_pool::{ PersistedOperationPool, PersistedOperationPoolV15, PersistedOperationPoolV20, }; -use slog::{debug, info, Logger}; use std::sync::Arc; use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem}; +use tracing::{debug, info}; use types::Attestation; pub fn upgrade_to_v20( db: Arc>, - log: Logger, ) -> Result, Error> { - info!(log, "Upgrading from v19 to v20"); + info!("Upgrading from v19 to v20"); // Load a V15 op pool and transform it to V20. let Some(PersistedOperationPoolV15:: { @@ -24,7 +23,7 @@ pub fn upgrade_to_v20( capella_bls_change_broadcast_indices, }) = db.get_item(&OP_POOL_DB_KEY)? else { - debug!(log, "Nothing to do, no operation pool stored"); + debug!("Nothing to do, no operation pool stored"); return Ok(vec![]); }; @@ -52,9 +51,8 @@ pub fn upgrade_to_v20( pub fn downgrade_from_v20( db: Arc>, - log: Logger, ) -> Result, Error> { - info!(log, "Downgrading from v20 to v19"); + info!("Downgrading from v20 to v19"); // Load a V20 op pool and transform it to V15. let Some(PersistedOperationPoolV20:: { @@ -67,7 +65,7 @@ pub fn downgrade_from_v20( capella_bls_change_broadcast_indices, }) = db.get_item(&OP_POOL_DB_KEY)? else { - debug!(log, "Nothing to do, no operation pool stored"); + debug!("Nothing to do, no operation pool stored"); return Ok(vec![]); }; @@ -77,7 +75,10 @@ pub fn downgrade_from_v20( if let Attestation::Base(attestation) = attestation.into() { Some((attestation, indices)) } else { - info!(log, "Dropping attestation during downgrade"; "reason" => "not a base attestation"); + info!( + reason = "not a base attestation", + "Dropping attestation during downgrade" + ); None } }) @@ -88,7 +89,10 @@ pub fn downgrade_from_v20( .filter_map(|slashing| match slashing.try_into() { Ok(slashing) => Some(slashing), Err(_) => { - info!(log, "Dropping attester slashing during downgrade"; "reason" => "not a base attester slashing"); + info!( + reason = "not a base attester slashing", + "Dropping attester slashing during downgrade" + ); None } }) diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v21.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v21.rs index f02f5ee6f3..d73660cf3c 100644 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v21.rs +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v21.rs @@ -1,18 +1,17 @@ use crate::beacon_chain::BeaconChainTypes; use crate::validator_pubkey_cache::DatabasePubkey; -use slog::{info, Logger}; use ssz::{Decode, Encode}; use std::sync::Arc; use store::{DBColumn, Error, HotColdDB, KeyValueStore, KeyValueStoreOp, StoreItem}; +use tracing::info; use types::{Hash256, PublicKey}; const LOG_EVERY: usize = 200_000; pub fn upgrade_to_v21( db: Arc>, - log: Logger, ) -> Result, Error> { - info!(log, "Upgrading from v20 to v21"); + info!("Upgrading from v20 to v21"); let mut ops = vec![]; @@ -29,22 +28,20 @@ pub fn upgrade_to_v21( if i > 0 && i % LOG_EVERY == 0 { info!( - log, - "Public key decompression in progress"; - "keys_decompressed" => i + keys_decompressed = i, + "Public key decompression in progress" ); } } - info!(log, "Public key decompression complete"); + info!("Public key decompression complete"); Ok(ops) } pub fn downgrade_from_v21( db: Arc>, - log: Logger, ) -> Result, Error> { - info!(log, "Downgrading from v21 to v20"); + info!("Downgrading from v21 to v20"); let mut ops = vec![]; @@ -67,15 +64,11 @@ pub fn downgrade_from_v21( )); if i > 0 && i % LOG_EVERY == 0 { - info!( - log, - "Public key compression in progress"; - "keys_compressed" => i - ); + info!(keys_compressed = i, "Public key compression in progress"); } } - info!(log, "Public key compression complete"); + info!("Public key compression complete"); Ok(ops) } diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v22.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v22.rs index 982c3ded46..0b64fdbe08 100644 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v22.rs +++ b/beacon_node/beacon_chain/src/schema_change/migration_schema_v22.rs @@ -1,5 +1,4 @@ use crate::beacon_chain::BeaconChainTypes; -use slog::{info, Logger}; use std::sync::Arc; use store::chunked_iter::ChunkedVectorIter; use store::{ @@ -10,6 +9,7 @@ use store::{ partial_beacon_state::PartialBeaconState, AnchorInfo, DBColumn, Error, HotColdDB, KeyValueStore, KeyValueStoreOp, }; +use tracing::info; use types::{BeaconState, Hash256, Slot}; const LOG_EVERY: usize = 200_000; @@ -40,9 +40,8 @@ fn load_old_schema_frozen_state( pub fn upgrade_to_v22( db: Arc>, genesis_state_root: Option, - log: Logger, ) -> Result<(), Error> { - info!(log, "Upgrading from v21 to v22"); + info!("Upgrading from v21 to v22"); let old_anchor = db.get_anchor_info(); @@ -71,9 +70,8 @@ pub fn upgrade_to_v22( // this write. if split_slot > 0 { info!( - log, - "Re-storing genesis state"; - "state_root" => ?genesis_state_root, + state_root = ?genesis_state_root, + "Re-storing genesis state" ); db.store_cold_state(&genesis_state_root, &genesis_state, &mut cold_ops)?; } @@ -87,7 +85,6 @@ pub fn upgrade_to_v22( oldest_block_slot, split_slot, &mut cold_ops, - &log, )?; // Commit this first batch of non-destructive cold database ops. @@ -107,14 +104,13 @@ pub fn upgrade_to_v22( db.store_schema_version_atomically(SchemaVersion(22), hot_ops)?; // Finally, clean up the old-format data from the freezer database. - delete_old_schema_freezer_data::(&db, &log)?; + delete_old_schema_freezer_data::(&db)?; Ok(()) } pub fn delete_old_schema_freezer_data( db: &Arc>, - log: &Logger, ) -> Result<(), Error> { let mut cold_ops = vec![]; @@ -140,11 +136,7 @@ pub fn delete_old_schema_freezer_data( } let delete_ops = cold_ops.len(); - info!( - log, - "Deleting historic states"; - "delete_ops" => delete_ops, - ); + info!(delete_ops, "Deleting historic states"); db.cold_db.do_atomically(cold_ops)?; // In order to reclaim space, we need to compact the freezer DB as well. @@ -159,13 +151,11 @@ pub fn write_new_schema_block_roots( oldest_block_slot: Slot, split_slot: Slot, cold_ops: &mut Vec, - log: &Logger, ) -> Result<(), Error> { info!( - log, - "Starting beacon block root migration"; - "oldest_block_slot" => oldest_block_slot, - "genesis_block_root" => ?genesis_block_root, + %oldest_block_slot, + ?genesis_block_root, + "Starting beacon block root migration" ); // Store the genesis block root if it would otherwise not be stored. @@ -196,9 +186,8 @@ pub fn write_new_schema_block_roots( if i > 0 && i % LOG_EVERY == 0 { info!( - log, - "Beacon block root migration in progress"; - "roots_migrated" => i + roots_migrated = i, + "Beacon block root migration in progress" ); } } diff --git a/beacon_node/beacon_chain/src/shuffling_cache.rs b/beacon_node/beacon_chain/src/shuffling_cache.rs index 67ca72254b..1aa23c28fc 100644 --- a/beacon_node/beacon_chain/src/shuffling_cache.rs +++ b/beacon_node/beacon_chain/src/shuffling_cache.rs @@ -2,9 +2,8 @@ use std::collections::HashMap; use std::sync::Arc; use itertools::Itertools; -use slog::{debug, Logger}; - use oneshot_broadcast::{oneshot, Receiver, Sender}; +use tracing::debug; use types::{ beacon_state::CommitteeCache, AttestationShufflingId, BeaconState, Epoch, EthSpec, Hash256, RelativeEpoch, @@ -61,16 +60,14 @@ pub struct ShufflingCache { cache: HashMap, cache_size: usize, head_shuffling_ids: BlockShufflingIds, - logger: Logger, } impl ShufflingCache { - pub fn new(cache_size: usize, head_shuffling_ids: BlockShufflingIds, logger: Logger) -> Self { + pub fn new(cache_size: usize, head_shuffling_ids: BlockShufflingIds) -> Self { Self { cache: HashMap::new(), cache_size, head_shuffling_ids, - logger, } } @@ -138,7 +135,7 @@ impl ShufflingCache { .get(&key) // Replace the committee if it's not present or if it's a promise. A bird in the hand is // worth two in the promise-bush! - .map_or(true, CacheItem::is_promise) + .is_none_or(CacheItem::is_promise) { self.insert_cache_item( key, @@ -179,10 +176,9 @@ impl ShufflingCache { for shuffling_id in shuffling_ids_to_prune.iter() { debug!( - self.logger, - "Removing old shuffling from cache"; - "shuffling_epoch" => shuffling_id.shuffling_epoch, - "shuffling_decision_block" => ?shuffling_id.shuffling_decision_block + shuffling_epoch = %shuffling_id.shuffling_epoch, + shuffling_decision_block = ?shuffling_id.shuffling_decision_block, + "Removing old shuffling from cache" ); self.cache.remove(shuffling_id); } @@ -294,10 +290,10 @@ impl BlockShufflingIds { #[cfg(not(debug_assertions))] #[cfg(test)] mod test { - use task_executor::test_utils::test_logger; use types::*; use crate::test_utils::EphemeralHarnessType; + use logging::create_test_tracing_subscriber; use super::*; @@ -308,6 +304,8 @@ mod test { // Creates a new shuffling cache for testing fn new_shuffling_cache() -> ShufflingCache { + create_test_tracing_subscriber(); + let current_epoch = 8; let head_shuffling_ids = BlockShufflingIds { current: shuffling_id(current_epoch), @@ -315,8 +313,8 @@ mod test { previous: Some(shuffling_id(current_epoch - 1)), block_root: Hash256::from_low_u64_le(0), }; - let logger = test_logger(); - ShufflingCache::new(TEST_CACHE_SIZE, head_shuffling_ids, logger) + + ShufflingCache::new(TEST_CACHE_SIZE, head_shuffling_ids) } /// Returns two different committee caches for testing. diff --git a/beacon_node/beacon_chain/src/state_advance_timer.rs b/beacon_node/beacon_chain/src/state_advance_timer.rs index 1d8bfff216..f4216ef76d 100644 --- a/beacon_node/beacon_chain/src/state_advance_timer.rs +++ b/beacon_node/beacon_chain/src/state_advance_timer.rs @@ -17,7 +17,6 @@ use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOC use crate::{ chain_config::FORK_CHOICE_LOOKAHEAD_FACTOR, BeaconChain, BeaconChainError, BeaconChainTypes, }; -use slog::{debug, error, warn, Logger}; use slot_clock::SlotClock; use state_processing::per_slot_processing; use std::sync::{ @@ -27,6 +26,7 @@ use std::sync::{ use store::KeyValueStore; use task_executor::TaskExecutor; use tokio::time::{sleep, sleep_until, Instant}; +use tracing::{debug, error, warn}; use types::{AttestationShufflingId, BeaconStateError, EthSpec, Hash256, RelativeEpoch, Slot}; /// If the head slot is more than `MAX_ADVANCE_DISTANCE` from the current slot, then don't perform @@ -107,10 +107,9 @@ impl Lock { pub fn spawn_state_advance_timer( executor: TaskExecutor, beacon_chain: Arc>, - log: Logger, ) { executor.spawn( - state_advance_timer(executor.clone(), beacon_chain, log), + state_advance_timer(executor.clone(), beacon_chain), "state_advance_timer", ); } @@ -119,7 +118,6 @@ pub fn spawn_state_advance_timer( async fn state_advance_timer( executor: TaskExecutor, beacon_chain: Arc>, - log: Logger, ) { let is_running = Lock::new(); let slot_clock = &beacon_chain.slot_clock; @@ -127,7 +125,7 @@ async fn state_advance_timer( loop { let Some(duration_to_next_slot) = beacon_chain.slot_clock.duration_to_next_slot() else { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; continue; @@ -161,9 +159,8 @@ async fn state_advance_timer( Ok(slot) => slot, Err(e) => { warn!( - log, - "Unable to determine slot in state advance timer"; - "error" => ?e + error = ?e, + "Unable to determine slot in state advance timer" ); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; @@ -173,37 +170,27 @@ async fn state_advance_timer( // Only spawn the state advance task if the lock was previously free. if !is_running.lock() { - let log = log.clone(); let beacon_chain = beacon_chain.clone(); let is_running = is_running.clone(); executor.spawn_blocking( move || { - match advance_head(&beacon_chain, &log) { + match advance_head(&beacon_chain) { Ok(()) => (), Err(Error::BeaconChain(e)) => error!( - log, - "Failed to advance head state"; - "error" => ?e - ), - Err(Error::StateAlreadyAdvanced { block_root }) => debug!( - log, - "State already advanced on slot"; - "block_root" => ?block_root + error = ?e, + "Failed to advance head state" ), + Err(Error::StateAlreadyAdvanced { block_root }) => { + debug!(?block_root, "State already advanced on slot") + } Err(Error::MaxDistanceExceeded { current_slot, head_slot, - }) => debug!( - log, - "Refused to advance head state"; - "head_slot" => head_slot, - "current_slot" => current_slot, - ), + }) => debug!(%head_slot, %current_slot, "Refused to advance head state"), other => warn!( - log, - "Did not advance head state"; - "reason" => ?other + reason = ?other, + "Did not advance head state" ), }; @@ -214,9 +201,8 @@ async fn state_advance_timer( ); } else { warn!( - log, - "State advance routine overloaded"; - "msg" => "system resources may be overloaded" + msg = "system resources may be overloaded", + "State advance routine overloaded" ) } @@ -225,7 +211,6 @@ async fn state_advance_timer( // Wait for the fork choice instant (which may already be past). sleep_until(fork_choice_instant).await; - let log = log.clone(); let beacon_chain = beacon_chain.clone(); let next_slot = current_slot + 1; executor.spawn( @@ -245,10 +230,9 @@ async fn state_advance_timer( .await .unwrap_or_else(|e| { warn!( - log, - "Unable to prepare proposer with lookahead"; - "error" => ?e, - "slot" => next_slot, + error = ?e, + slot = %next_slot, + "Unable to prepare proposer with lookahead" ); None }); @@ -261,10 +245,9 @@ async fn state_advance_timer( if let Some(tx) = &beacon_chain.fork_choice_signal_tx { if let Err(e) = tx.notify_fork_choice_complete(next_slot) { warn!( - log, - "Error signalling fork choice waiter"; - "error" => ?e, - "slot" => next_slot, + error = ?e, + slot = %next_slot, + "Error signalling fork choice waiter" ); } } @@ -282,10 +265,7 @@ async fn state_advance_timer( /// slot then placed in the `state_cache` to be used for block verification. /// /// See the module-level documentation for rationale. -fn advance_head( - beacon_chain: &Arc>, - log: &Logger, -) -> Result<(), Error> { +fn advance_head(beacon_chain: &Arc>) -> Result<(), Error> { let current_slot = beacon_chain.slot()?; // These brackets ensure that the `head_slot` value is dropped before we run fork choice and @@ -344,10 +324,9 @@ fn advance_head( // Expose Prometheus metrics. if let Err(e) = summary.observe_metrics() { error!( - log, - "Failed to observe epoch summary metrics"; - "src" => "state_advance_timer", - "error" => ?e + src = "state_advance_timer", + error = ?e, + "Failed to observe epoch summary metrics" ); } @@ -362,20 +341,18 @@ fn advance_head( .process_validator_statuses(state.current_epoch(), &summary, &beacon_chain.spec) { error!( - log, - "Unable to process validator statuses"; - "error" => ?e + error = ?e, + "Unable to process validator statuses" ); } } } debug!( - log, - "Advanced head state one slot"; - "head_block_root" => ?head_block_root, - "state_slot" => state.slot(), - "current_slot" => current_slot, + ?head_block_root, + state_slot = %state.slot(), + %current_slot, + "Advanced head state one slot" ); // Build the current epoch cache, to prepare to compute proposer duties. @@ -420,12 +397,11 @@ fn advance_head( .insert_committee_cache(shuffling_id.clone(), committee_cache); debug!( - log, - "Primed proposer and attester caches"; - "head_block_root" => ?head_block_root, - "next_epoch_shuffling_root" => ?shuffling_id.shuffling_decision_block, - "state_epoch" => state.current_epoch(), - "current_epoch" => current_slot.epoch(T::EthSpec::slots_per_epoch()), + ?head_block_root, + next_epoch_shuffling_root = ?shuffling_id.shuffling_decision_block, + state_epoch = %state.current_epoch(), + current_epoch = %current_slot.epoch(T::EthSpec::slots_per_epoch()), + "Primed proposer and attester caches" ); } @@ -447,13 +423,12 @@ fn advance_head( let current_slot = beacon_chain.slot()?; if starting_slot < current_slot { warn!( - log, - "State advance too slow"; - "head_block_root" => %head_block_root, - "advanced_slot" => final_slot, - "current_slot" => current_slot, - "starting_slot" => starting_slot, - "msg" => "system resources may be overloaded", + %head_block_root, + advanced_slot = %final_slot, + %current_slot, + %starting_slot, + msg = "system resources may be overloaded", + "State advance too slow" ); } @@ -473,11 +448,10 @@ fn advance_head( drop(txn_lock); debug!( - log, - "Completed state advance"; - "head_block_root" => ?head_block_root, - "advanced_slot" => final_slot, - "initial_slot" => initial_slot, + ?head_block_root, + advanced_slot = %final_slot, + %initial_slot, + "Completed state advance" ); Ok(()) diff --git a/beacon_node/beacon_chain/src/sync_committee_rewards.rs b/beacon_node/beacon_chain/src/sync_committee_rewards.rs index 9b35cff943..e3ff5f4ab2 100644 --- a/beacon_node/beacon_chain/src/sync_committee_rewards.rs +++ b/beacon_node/beacon_chain/src/sync_committee_rewards.rs @@ -2,10 +2,10 @@ use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; use eth2::lighthouse::SyncCommitteeReward; use safe_arith::SafeArith; -use slog::error; use state_processing::per_block_processing::altair::sync_committee::compute_sync_aggregate_rewards; use std::collections::HashMap; use store::RelativeEpoch; +use tracing::error; use types::{AbstractExecPayload, BeaconBlockRef, BeaconState}; impl BeaconChain { @@ -31,8 +31,8 @@ impl BeaconChain { let (participant_reward_value, proposer_reward_per_bit) = compute_sync_aggregate_rewards(state, spec).map_err(|e| { error!( - self.log, "Error calculating sync aggregate rewards"; - "error" => ?e + error = ?e, + "Error calculating sync aggregate rewards" ); BeaconChainError::SyncCommitteeRewardsSyncError })?; diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 8c9e3929f6..457687fa21 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -35,6 +35,7 @@ pub use genesis::{InteropGenesisBuilder, DEFAULT_ETH1_BLOCK_HASH}; use int_to_bytes::int_to_bytes32; use kzg::trusted_setup::get_trusted_setup; use kzg::{Kzg, TrustedSetup}; +use logging::create_test_tracing_subscriber; use merkle_proof::MerkleTree; use operation_pool::ReceivedPreCapella; use parking_lot::Mutex; @@ -44,17 +45,12 @@ use rand::Rng; use rand::SeedableRng; use rayon::prelude::*; use sensitive_url::SensitiveUrl; -use slog::{o, Drain, Logger}; -use slog_async::Async; -use slog_term::{FullFormat, PlainSyncDecorator, TermDecorator}; use slot_clock::{SlotClock, TestingSlotClock}; use state_processing::per_block_processing::compute_timestamp_at_slot; use state_processing::state_advance::complete_state_advance; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::fmt; -use std::fs::{File, OpenOptions}; -use std::io::BufWriter; use std::str::FromStr; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, LazyLock}; @@ -235,7 +231,6 @@ pub struct Builder { genesis_state_builder: Option>, import_all_data_columns: bool, runtime: TestRuntime, - log: Logger, } impl Builder> { @@ -247,12 +242,8 @@ impl Builder> { .expect("cannot build without validator keypairs"); let store = Arc::new( - HotColdDB::open_ephemeral( - self.store_config.clone().unwrap_or_default(), - spec.clone(), - self.log.clone(), - ) - .unwrap(), + HotColdDB::open_ephemeral(self.store_config.clone().unwrap_or_default(), spec.clone()) + .unwrap(), ); let genesis_state_builder = self.genesis_state_builder.take().unwrap_or_else(|| { // Set alternating withdrawal credentials if no builder is specified. @@ -283,12 +274,8 @@ impl Builder> { let spec = self.spec.as_ref().expect("cannot build without spec"); let store = Arc::new( - HotColdDB::open_ephemeral( - self.store_config.clone().unwrap_or_default(), - spec.clone(), - self.log.clone(), - ) - .unwrap(), + HotColdDB::open_ephemeral(self.store_config.clone().unwrap_or_default(), spec.clone()) + .unwrap(), ); let mutator = move |builder: BeaconChainBuilder<_>| { builder @@ -372,7 +359,6 @@ where { pub fn new(eth_spec_instance: E) -> Self { let runtime = TestRuntime::default(); - let log = runtime.log.clone(); Self { eth_spec_instance, @@ -391,7 +377,6 @@ where genesis_state_builder: None, import_all_data_columns: false, runtime, - log, } } @@ -439,12 +424,6 @@ where self } - pub fn logger(mut self, log: Logger) -> Self { - self.log = log.clone(); - self.runtime.set_logger(log); - self - } - /// This mutator will be run before the `store_mutator`. pub fn initial_mutator(mut self, mutator: BoxedMutator) -> Self { assert!( @@ -501,12 +480,8 @@ where suggested_fee_recipient: Some(Address::repeat_byte(42)), ..Default::default() }; - let execution_layer = ExecutionLayer::from_config( - config, - self.runtime.task_executor.clone(), - self.log.clone(), - ) - .unwrap(); + let execution_layer = + ExecutionLayer::from_config(config, self.runtime.task_executor.clone()).unwrap(); self.execution_layer = Some(execution_layer); self @@ -586,7 +561,6 @@ where pub fn build(self) -> BeaconChainHarness> { let (shutdown_tx, shutdown_receiver) = futures::channel::mpsc::channel(1); - let log = self.log; let spec = self.spec.expect("cannot build without spec"); let seconds_per_slot = spec.seconds_per_slot; let validator_keypairs = self @@ -599,7 +573,6 @@ where let chain_config = self.chain_config.unwrap_or_default(); let mut builder = BeaconChainBuilder::new(self.eth_spec_instance, kzg.clone()) - .logger(log.clone()) .custom_spec(spec.clone()) .store(self.store.expect("cannot build without store")) .store_migrator_config( @@ -614,10 +587,7 @@ where .shutdown_sender(shutdown_tx) .chain_config(chain_config) .import_all_data_columns(self.import_all_data_columns) - .event_handler(Some(ServerSentEventHandler::new_with_capacity( - log.clone(), - 5, - ))) + .event_handler(Some(ServerSentEventHandler::new_with_capacity(5))) .validator_monitor_config(validator_monitor_config); builder = if let Some(mutator) = self.initial_mutator { @@ -737,13 +707,10 @@ where Cold: ItemStore, { pub fn builder(eth_spec_instance: E) -> Builder> { + create_test_tracing_subscriber(); Builder::new(eth_spec_instance) } - pub fn logger(&self) -> &slog::Logger { - &self.chain.log - } - pub fn execution_block_generator(&self) -> RwLockWriteGuard<'_, ExecutionBlockGenerator> { self.mock_execution_layer .as_ref() @@ -779,6 +746,7 @@ where SensitiveUrl::parse(format!("http://127.0.0.1:{port}").as_str()).unwrap(), None, None, + false, ) .unwrap(); @@ -2617,7 +2585,6 @@ where return; } - let log = self.logger(); let contributions = self.make_sync_contributions(state, block_root, slot, RelativeSyncCommittee::Current); @@ -2648,7 +2615,6 @@ where slot, &block_root, &sync_aggregate, - log, &self.spec, ); } @@ -2712,16 +2678,16 @@ where let mut block_hash_from_slot: HashMap = HashMap::new(); let mut state_hash_from_slot: HashMap = HashMap::new(); for slot in slots { - let (block_hash, new_state) = self - .add_attested_block_at_slot_with_sync( - *slot, - state, - state_root, - validators, - sync_committee_strategy, - ) - .await - .unwrap(); + // Using a `Box::pin` to reduce the stack size. Clippy was raising a lints. + let (block_hash, new_state) = Box::pin(self.add_attested_block_at_slot_with_sync( + *slot, + state, + state_root, + validators, + sync_committee_strategy, + )) + .await + .unwrap(); state = new_state; @@ -3158,58 +3124,6 @@ pub struct MakeAttestationOptions { pub fork: Fork, } -pub enum LoggerType { - Test, - // The logs are output to files for each test. - CI, - // No logs will be printed. - Null, -} - -fn ci_decorator() -> PlainSyncDecorator> { - let log_dir = std::env::var(CI_LOGGER_DIR_ENV_VAR).unwrap_or_else(|e| { - panic!("{CI_LOGGER_DIR_ENV_VAR} env var must be defined when using ci_logger: {e:?}"); - }); - let fork_name = std::env::var(FORK_NAME_ENV_VAR) - .map(|s| format!("{s}_")) - .unwrap_or_default(); - // The current test name can be got via the thread name. - let test_name = std::thread::current() - .name() - .unwrap() - .to_string() - // Colons are not allowed in files that are uploaded to GitHub Artifacts. - .replace("::", "_"); - let log_path = format!("/{log_dir}/{fork_name}{test_name}.log"); - let file = OpenOptions::new() - .create(true) - .append(true) - .open(log_path) - .unwrap(); - let file = BufWriter::new(file); - PlainSyncDecorator::new(file) -} - -pub fn build_log(level: slog::Level, logger_type: LoggerType) -> Logger { - match logger_type { - LoggerType::Test => { - let drain = FullFormat::new(TermDecorator::new().build()).build().fuse(); - let drain = Async::new(drain).chan_size(10_000).build().fuse(); - Logger::root(drain.filter_level(level).fuse(), o!()) - } - LoggerType::CI => { - let drain = FullFormat::new(ci_decorator()).build().fuse(); - let drain = Async::new(drain).chan_size(10_000).build().fuse(); - Logger::root(drain.filter_level(level).fuse(), o!()) - } - LoggerType::Null => { - let drain = FullFormat::new(TermDecorator::new().build()).build().fuse(); - let drain = Async::new(drain).build().fuse(); - Logger::root(drain.filter(|_| false).fuse(), o!()) - } - } -} - pub enum NumBlobs { Random, Number(usize), diff --git a/beacon_node/beacon_chain/src/validator_monitor.rs b/beacon_node/beacon_chain/src/validator_monitor.rs index f8a483c621..16f4e3f143 100644 --- a/beacon_node/beacon_chain/src/validator_monitor.rs +++ b/beacon_node/beacon_chain/src/validator_monitor.rs @@ -5,9 +5,9 @@ use crate::beacon_proposer_cache::{BeaconProposerCache, TYPICAL_SLOTS_PER_EPOCH}; use crate::metrics; use itertools::Itertools; +use logging::crit; use parking_lot::{Mutex, RwLock}; use serde::{Deserialize, Serialize}; -use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use smallvec::SmallVec; use state_processing::common::get_attestation_participation_flag_indices; @@ -21,6 +21,7 @@ use std::str::Utf8Error; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use store::AbstractExecPayload; +use tracing::{debug, error, info, instrument, warn}; use types::consts::altair::{ TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX, }; @@ -30,7 +31,6 @@ use types::{ IndexedAttestationRef, ProposerSlashing, PublicKeyBytes, SignedAggregateAndProof, SignedContributionAndProof, Slot, SyncCommitteeMessage, VoluntaryExit, }; - /// Used for Prometheus labels. /// /// We've used `total` for this value to align with Nimbus, as per: @@ -401,15 +401,18 @@ pub struct ValidatorMonitor { beacon_proposer_cache: Arc>, // Unaggregated attestations generated by the committee index at each slot. unaggregated_attestations: HashMap>, - log: Logger, _phantom: PhantomData, } impl ValidatorMonitor { + #[instrument(parent = None, + level = "info", + name = "validator_monitor", + skip_all + )] pub fn new( config: ValidatorMonitorConfig, beacon_proposer_cache: Arc>, - log: Logger, ) -> Self { let ValidatorMonitorConfig { auto_register, @@ -425,7 +428,6 @@ impl ValidatorMonitor { missed_blocks: <_>::default(), beacon_proposer_cache, unaggregated_attestations: <_>::default(), - log, _phantom: PhantomData, }; for pubkey in validators { @@ -437,11 +439,23 @@ impl ValidatorMonitor { /// Returns `true` when the validator count is sufficiently low enough to /// emit metrics and logs on a per-validator basis (rather than just an /// aggregated basis). + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn individual_tracking(&self) -> bool { self.validators.len() <= self.individual_tracking_threshold } /// Add some validators to `self` for additional monitoring. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn add_validator_pubkey(&mut self, pubkey: PublicKeyBytes) { let index_opt = self .indices @@ -449,18 +463,22 @@ impl ValidatorMonitor { .find(|(_, candidate_pk)| **candidate_pk == pubkey) .map(|(index, _)| *index); - let log = self.log.clone(); self.validators.entry(pubkey).or_insert_with(|| { info!( - log, - "Started monitoring validator"; - "pubkey" => %pubkey, + %pubkey, + "Started monitoring validator" ); MonitoredValidator::new(pubkey, index_opt) }); } /// Add an unaggregated attestation + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn set_unaggregated_attestation(&mut self, attestation: Attestation) { let unaggregated_attestations = &mut self.unaggregated_attestations; @@ -474,12 +492,24 @@ impl ValidatorMonitor { self.unaggregated_attestations.insert(slot, attestation); } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_unaggregated_attestation(&self, slot: Slot) -> Option<&Attestation> { self.unaggregated_attestations.get(&slot) } /// Reads information from the given `state`. The `state` *must* be valid (i.e, able to be /// imported). + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn process_valid_state( &mut self, current_epoch: Epoch, @@ -592,6 +622,12 @@ impl ValidatorMonitor { } /// Add missed non-finalized blocks for the monitored validators + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn add_validators_missed_blocks(&mut self, state: &BeaconState) { // Define range variables let current_slot = state.slot(); @@ -628,7 +664,7 @@ impl ValidatorMonitor { // the proposer shuffling cache lock when there are lots of missed blocks. if proposers_per_epoch .as_ref() - .map_or(true, |(_, cached_epoch)| *cached_epoch != slot_epoch) + .is_none_or(|(_, cached_epoch)| *cached_epoch != slot_epoch) { proposers_per_epoch = self .get_proposers_by_epoch_from_cache( @@ -661,28 +697,25 @@ impl ValidatorMonitor { ); }); error!( - self.log, - "Validator missed a block"; - "index" => i, - "slot" => slot, - "parent block root" => ?prev_block_root, + index = i, + %slot, + ?prev_block_root, + "Validator missed a block" ); } } } else { warn!( - self.log, - "Missing validator index"; - "info" => "potentially inconsistency in the validator manager", - "index" => i, + info = "potentially inconsistency in the validator manager", + index = i, + "Missing validator index" ) } } else { debug!( - self.log, - "Could not get proposers from cache"; - "epoch" => ?slot_epoch, - "decision_root" => ?shuffling_decision_block, + epoch = ?slot_epoch, + decision_root = ?shuffling_decision_block, + "Could not get proposers from cache" ); } } @@ -691,6 +724,12 @@ impl ValidatorMonitor { } } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn get_proposers_by_epoch_from_cache( &mut self, epoch: Epoch, @@ -704,6 +743,12 @@ impl ValidatorMonitor { /// Process the unaggregated attestations generated by the service `attestation_simulator_service` /// and check if the attestation qualifies for a reward matching the flags source/target/head + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn process_unaggregated_attestations(&mut self, state: &BeaconState, spec: &ChainSpec) { let current_slot = state.slot(); @@ -744,27 +789,23 @@ impl ValidatorMonitor { let head_hit = flag_indices.contains(&TIMELY_HEAD_FLAG_INDEX); let target_hit = flag_indices.contains(&TIMELY_TARGET_FLAG_INDEX); let source_hit = flag_indices.contains(&TIMELY_SOURCE_FLAG_INDEX); - register_simulated_attestation( - data, head_hit, target_hit, source_hit, &self.log, - ) + register_simulated_attestation(data, head_hit, target_hit, source_hit) } Err(BeaconStateError::IncorrectAttestationSource) => { - register_simulated_attestation(data, false, false, false, &self.log) + register_simulated_attestation(data, false, false, false) } Err(err) => { error!( - self.log, - "Failed to get attestation participation flag indices"; - "error" => ?err, - "unaggregated_attestation" => ?unaggregated_attestation, + error = ?err, + ?unaggregated_attestation, + "Failed to get attestation participation flag indices" ); } } } else { error!( - self.log, - "Failed to remove unaggregated attestation from the hashmap"; - "slot" => ?slot, + ?slot, + "Failed to remove unaggregated attestation from the hashmap" ); } } @@ -780,6 +821,12 @@ impl ValidatorMonitor { /// /// We allow disabling tracking metrics on an individual validator basis /// since it can result in untenable cardinality with high validator counts. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn aggregatable_metric(&self, individual_id: &str, func: F) { func(TOTAL_LABEL); @@ -788,6 +835,12 @@ impl ValidatorMonitor { } } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn process_validator_statuses( &self, epoch: Epoch, @@ -867,13 +920,12 @@ impl ValidatorMonitor { attestation_success.push(id); if self.individual_tracking() { debug!( - self.log, - "Previous epoch attestation success"; - "matched_source" => previous_epoch_matched_source, - "matched_target" => previous_epoch_matched_target, - "matched_head" => previous_epoch_matched_head, - "epoch" => prev_epoch, - "validator" => id, + matched_source = previous_epoch_matched_source, + matched_target = previous_epoch_matched_target, + matched_head = previous_epoch_matched_head, + epoch = %prev_epoch, + validator = id, + "Previous epoch attestation success" ) } } else { @@ -886,10 +938,9 @@ impl ValidatorMonitor { attestation_miss.push(id); if self.individual_tracking() { debug!( - self.log, - "Previous epoch attestation missing"; - "epoch" => prev_epoch, - "validator" => id, + epoch = %prev_epoch, + validator = id, + "Previous epoch attestation missing" ) } } @@ -912,10 +963,9 @@ impl ValidatorMonitor { head_miss.push(id); if self.individual_tracking() { debug!( - self.log, - "Attestation failed to match head"; - "epoch" => prev_epoch, - "validator" => id, + epoch = %prev_epoch, + validator = id, + "Attestation failed to match head" ); } } @@ -938,10 +988,9 @@ impl ValidatorMonitor { target_miss.push(id); if self.individual_tracking() { debug!( - self.log, - "Attestation failed to match target"; - "epoch" => prev_epoch, - "validator" => id, + epoch = %prev_epoch, + validator = id, + "Attestation failed to match target" ); } } @@ -960,12 +1009,11 @@ impl ValidatorMonitor { suboptimal_inclusion.push(id); if self.individual_tracking() { debug!( - self.log, - "Potential sub-optimal inclusion delay"; - "optimal" => spec.min_attestation_inclusion_delay, - "delay" => inclusion_delay, - "epoch" => prev_epoch, - "validator" => id, + optimal = spec.min_attestation_inclusion_delay, + delay = inclusion_delay, + epoch = %prev_epoch, + validator = id, + "Potential sub-optimal inclusion delay" ); } } @@ -1003,12 +1051,11 @@ impl ValidatorMonitor { // logs that can be generated is capped by the size // of the sync committee. info!( - self.log, - "Current epoch sync signatures"; - "included" => summary.sync_signature_block_inclusions, - "expected" => E::slots_per_epoch(), - "epoch" => current_epoch, - "validator" => id, + included = summary.sync_signature_block_inclusions, + expected = E::slots_per_epoch(), + epoch = %current_epoch, + validator = id, + "Current epoch sync signatures" ); } } else if self.individual_tracking() { @@ -1018,10 +1065,9 @@ impl ValidatorMonitor { 0, ); debug!( - self.log, - "Validator isn't part of the current sync committee"; - "epoch" => current_epoch, - "validator" => id, + epoch = %current_epoch, + validator = id, + "Validator isn't part of the current sync committee" ); } } @@ -1032,51 +1078,52 @@ impl ValidatorMonitor { // for all validators managed by the validator monitor. if !attestation_success.is_empty() { info!( - self.log, - "Previous epoch attestation(s) success"; - "epoch" => prev_epoch, - "validators" => ?attestation_success, + epoch = %prev_epoch, + validators = ?attestation_success, + "Previous epoch attestation(s) success" ); } if !attestation_miss.is_empty() { info!( - self.log, - "Previous epoch attestation(s) missing"; - "epoch" => prev_epoch, - "validators" => ?attestation_miss, + epoch = %prev_epoch, + validators = ?attestation_miss, + "Previous epoch attestation(s) missing" ); } if !head_miss.is_empty() { info!( - self.log, - "Previous epoch attestation(s) failed to match head"; - "epoch" => prev_epoch, - "validators" => ?head_miss, + epoch = %prev_epoch, + validators = ?head_miss, + "Previous epoch attestation(s) failed to match head" ); } if !target_miss.is_empty() { info!( - self.log, - "Previous epoch attestation(s) failed to match target"; - "epoch" => prev_epoch, - "validators" => ?target_miss, + epoch = %prev_epoch, + validators = ?target_miss, + "Previous epoch attestation(s) failed to match target" ); } if !suboptimal_inclusion.is_empty() { info!( - self.log, - "Previous epoch attestation(s) had sub-optimal inclusion delay"; - "epoch" => prev_epoch, - "validators" => ?suboptimal_inclusion, + epoch = %prev_epoch, + validators = ?suboptimal_inclusion, + "Previous epoch attestation(s) had sub-optimal inclusion delay" ); } Ok(()) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn get_validator(&self, validator_index: u64) -> Option<&MonitoredValidator> { self.indices .get(&validator_index) @@ -1084,15 +1131,33 @@ impl ValidatorMonitor { } /// Returns the number of validators monitored by `self`. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn num_validators(&self) -> usize { self.validators.len() } - // Return the `id`'s of all monitored validators. + /// Return the `id`'s of all monitored validators. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_all_monitored_validators(&self) -> Vec { self.validators.values().map(|val| val.id.clone()).collect() } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_monitored_validator(&self, index: u64) -> Option<&MonitoredValidator> { if let Some(pubkey) = self.indices.get(&index) { self.validators.get(pubkey) @@ -1101,6 +1166,12 @@ impl ValidatorMonitor { } } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_monitored_validator_missed_block_count(&self, validator_index: u64) -> u64 { self.missed_blocks .iter() @@ -1108,12 +1179,24 @@ impl ValidatorMonitor { .count() as u64 } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn get_beacon_proposer_cache(&self) -> Arc> { self.beacon_proposer_cache.clone() } /// If `self.auto_register == true`, add the `validator_index` to `self.monitored_validators`. /// Otherwise, do nothing. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn auto_register_local_validator(&mut self, validator_index: u64) { if !self.auto_register { return; @@ -1122,10 +1205,9 @@ impl ValidatorMonitor { if let Some(pubkey) = self.indices.get(&validator_index) { if !self.validators.contains_key(pubkey) { info!( - self.log, - "Started monitoring validator"; - "pubkey" => %pubkey, - "validator" => %validator_index, + %pubkey, + validator = %validator_index, + "Started monitoring validator" ); self.validators.insert( @@ -1137,6 +1219,12 @@ impl ValidatorMonitor { } /// Process a block received on gossip. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_block( &self, seen_timestamp: Duration, @@ -1148,6 +1236,12 @@ impl ValidatorMonitor { } /// Process a block received on the HTTP API from a local validator. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_block( &self, seen_timestamp: Duration, @@ -1158,6 +1252,12 @@ impl ValidatorMonitor { self.register_beacon_block("api", seen_timestamp, block, block_root, slot_clock) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_beacon_block( &self, src: &str, @@ -1184,13 +1284,12 @@ impl ValidatorMonitor { }); info!( - self.log, - "Block from monitored validator"; - "root" => ?block_root, - "delay" => %delay.as_millis(), - "slot" => %block.slot(), - "src" => src, - "validator" => %id, + ?block_root, + delay = %delay.as_millis(), + slot = %block.slot(), + src, + validator = %id, + "Block from monitored validator" ); validator.with_epoch_summary(epoch, |summary| summary.register_block(delay)); @@ -1198,6 +1297,12 @@ impl ValidatorMonitor { } /// Register an attestation seen on the gossip network. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_unaggregated_attestation( &self, seen_timestamp: Duration, @@ -1213,6 +1318,12 @@ impl ValidatorMonitor { } /// Register an attestation seen on the HTTP API. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_unaggregated_attestation( &self, seen_timestamp: Duration, @@ -1227,6 +1338,12 @@ impl ValidatorMonitor { ) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_unaggregated_attestation( &self, src: &str, @@ -1261,15 +1378,14 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Unaggregated attestation"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %data.slot, - "src" => src, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + delay_ms = %delay.as_millis(), + %epoch, + slot = %data.slot, + src, + validator = %id, + "Unaggregated attestation" ); } @@ -1314,6 +1430,12 @@ impl ValidatorMonitor { ) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_aggregated_attestation( &self, src: &str, @@ -1349,15 +1471,14 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Aggregated attestation"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %data.slot, - "src" => src, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + delay_ms = %delay.as_millis(), + %epoch, + slot = %data.slot, + src, + validator = %id, + "Aggregated attestation" ); } @@ -1396,28 +1517,26 @@ impl ValidatorMonitor { if is_first_inclusion_aggregate { info!( - self.log, - "Attestation included in aggregate"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %data.slot, - "src" => src, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + delay_ms = %delay.as_millis(), + %epoch, + slot = %data.slot, + src, + validator = %id, + "Attestation included in aggregate" ); } else { // Downgrade to Debug for second and onwards of logging to reduce verbosity debug!( - self.log, - "Attestation included in aggregate"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %data.slot, - "src" => src, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + delay_ms = %delay.as_millis(), + %epoch, + slot = %data.slot, + src, + validator = %id, + "Attestation included in aggregate" ) }; } @@ -1435,6 +1554,11 @@ impl ValidatorMonitor { /// We use the parent slot instead of block slot to ignore skip slots when calculating inclusion distance. /// /// Note: Blocks that get orphaned will skew the inclusion distance calculation. + #[instrument(parent = None, + level = "info", + name = "validator_monitor", + skip_all + )] pub fn register_attestation_in_block( &self, indexed_attestation: IndexedAttestationRef<'_, E>, @@ -1480,26 +1604,24 @@ impl ValidatorMonitor { if is_first_inclusion_block { info!( - self.log, - "Attestation included in block"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "inclusion_lag" => format!("{} slot(s)", delay), - "epoch" => %epoch, - "slot" => %data.slot, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + inclusion_lag = format!("{} slot(s)", delay), + %epoch, + slot = %data.slot, + validator = %id, + "Attestation included in block" ); } else { // Downgrade to Debug for second and onwards of logging to reduce verbosity debug!( - self.log, - "Attestation included in block"; - "head" => ?data.beacon_block_root, - "index" => %data.index, - "inclusion_lag" => format!("{} slot(s)", delay), - "epoch" => %epoch, - "slot" => %data.slot, - "validator" => %id, + head = ?data.beacon_block_root, + index = %data.index, + inclusion_lag = format!("{} slot(s)", delay), + %epoch, + slot = %data.slot, + validator = %id, + "Attestation included in block" ); } } @@ -1512,6 +1634,12 @@ impl ValidatorMonitor { } /// Register a sync committee message received over gossip. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_sync_committee_message( &self, seen_timestamp: Duration, @@ -1527,6 +1655,12 @@ impl ValidatorMonitor { } /// Register a sync committee message received over the http api. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_sync_committee_message( &self, seen_timestamp: Duration, @@ -1542,6 +1676,12 @@ impl ValidatorMonitor { } /// Register a sync committee message. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_sync_committee_message( &self, src: &str, @@ -1574,14 +1714,13 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Sync committee message"; - "head" => %sync_committee_message.beacon_block_root, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %sync_committee_message.slot, - "src" => src, - "validator" => %id, + head = %sync_committee_message.beacon_block_root, + delay_ms = %delay.as_millis(), + %epoch, + slot = %sync_committee_message.slot, + src, + validator = %id, + "Sync committee message" ); } @@ -1592,6 +1731,12 @@ impl ValidatorMonitor { } /// Register a sync committee contribution received over gossip. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_sync_committee_contribution( &self, seen_timestamp: Duration, @@ -1609,6 +1754,12 @@ impl ValidatorMonitor { } /// Register a sync committee contribution received over the http api. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_sync_committee_contribution( &self, seen_timestamp: Duration, @@ -1626,6 +1777,12 @@ impl ValidatorMonitor { } /// Register a sync committee contribution. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_sync_committee_contribution( &self, src: &str, @@ -1662,14 +1819,13 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Sync contribution"; - "head" => %beacon_block_root, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %slot, - "src" => src, - "validator" => %id, + head = %beacon_block_root, + delay_ms = %delay.as_millis(), + %epoch, + %slot, + src, + validator = %id, + "Sync contribution" ); } @@ -1691,14 +1847,13 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Sync signature included in contribution"; - "head" => %beacon_block_root, - "delay_ms" => %delay.as_millis(), - "epoch" => %epoch, - "slot" => %slot, - "src" => src, - "validator" => %id, + head = %beacon_block_root, + delay_ms = %delay.as_millis(), + %epoch, + %slot, + src, + validator = %id, + "Sync signature included in contribution" ); } @@ -1710,6 +1865,12 @@ impl ValidatorMonitor { } /// Register that the `sync_aggregate` was included in a *valid* `BeaconBlock`. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_sync_aggregate_in_block( &self, slot: Slot, @@ -1731,12 +1892,11 @@ impl ValidatorMonitor { if self.individual_tracking() { info!( - self.log, - "Sync signature included in block"; - "head" => %beacon_block_root, - "epoch" => %epoch, - "slot" => %slot, - "validator" => %id, + head = %beacon_block_root, + %epoch, + %slot, + validator = %id, + "Sync signature included in block" ); } @@ -1748,20 +1908,44 @@ impl ValidatorMonitor { } /// Register an exit from the gossip network. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_voluntary_exit(&self, exit: &VoluntaryExit) { self.register_voluntary_exit("gossip", exit) } /// Register an exit from the HTTP API. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_voluntary_exit(&self, exit: &VoluntaryExit) { self.register_voluntary_exit("api", exit) } /// Register an exit included in a *valid* beacon block. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_block_voluntary_exit(&self, exit: &VoluntaryExit) { self.register_voluntary_exit("block", exit) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_voluntary_exit(&self, src: &str, exit: &VoluntaryExit) { if let Some(validator) = self.get_validator(exit.validator_index) { let id = &validator.id; @@ -1774,11 +1958,10 @@ impl ValidatorMonitor { // Not gated behind `self.individual_tracking()` since it's an // infrequent and interesting message. info!( - self.log, - "Voluntary exit"; - "epoch" => %epoch, - "validator" => %id, - "src" => src, + %epoch, + validator = %id, + src, + "Voluntary exit" ); validator.with_epoch_summary(epoch, |summary| summary.register_exit()); @@ -1786,20 +1969,44 @@ impl ValidatorMonitor { } /// Register a proposer slashing from the gossip network. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_proposer_slashing(&self, slashing: &ProposerSlashing) { self.register_proposer_slashing("gossip", slashing) } /// Register a proposer slashing from the HTTP API. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_proposer_slashing(&self, slashing: &ProposerSlashing) { self.register_proposer_slashing("api", slashing) } /// Register a proposer slashing included in a *valid* `BeaconBlock`. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_block_proposer_slashing(&self, slashing: &ProposerSlashing) { self.register_proposer_slashing("block", slashing) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_proposer_slashing(&self, src: &str, slashing: &ProposerSlashing) { let proposer = slashing.signed_header_1.message.proposer_index; let slot = slashing.signed_header_1.message.slot; @@ -1820,13 +2027,12 @@ impl ValidatorMonitor { // Not gated behind `self.individual_tracking()` since it's an // infrequent and interesting message. crit!( - self.log, - "Proposer slashing"; - "root_2" => %root_2, - "root_1" => %root_1, - "slot" => %slot, - "validator" => %id, - "src" => src, + %root_2, + %root_1, + %slot, + validator = %id, + src, + "Proposer slashing" ); validator.with_epoch_summary(epoch, |summary| summary.register_proposer_slashing()); @@ -1834,20 +2040,44 @@ impl ValidatorMonitor { } /// Register an attester slashing from the gossip network. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_gossip_attester_slashing(&self, slashing: AttesterSlashingRef<'_, E>) { self.register_attester_slashing("gossip", slashing) } /// Register an attester slashing from the HTTP API. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_api_attester_slashing(&self, slashing: AttesterSlashingRef<'_, E>) { self.register_attester_slashing("api", slashing) } /// Register an attester slashing included in a *valid* `BeaconBlock`. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn register_block_attester_slashing(&self, slashing: AttesterSlashingRef<'_, E>) { self.register_attester_slashing("block", slashing) } + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] fn register_attester_slashing(&self, src: &str, slashing: AttesterSlashingRef<'_, E>) { let data = slashing.attestation_1().data(); let attestation_1_indices: HashSet = slashing @@ -1875,12 +2105,11 @@ impl ValidatorMonitor { // Not gated behind `self.individual_tracking()` since it's an // infrequent and interesting message. crit!( - self.log, - "Attester slashing"; - "epoch" => %epoch, - "slot" => %data.slot, - "validator" => %id, - "src" => src, + %epoch, + slot = %data.slot, + validator = %id, + src, + "Attester slashing" ); validator.with_epoch_summary(epoch, |summary| summary.register_attester_slashing()); @@ -1890,6 +2119,12 @@ impl ValidatorMonitor { /// Scrape `self` for metrics. /// /// Should be called whenever Prometheus is scraping Lighthouse. + #[instrument(parent = None, + level = "info", + fields(service = "validator_monitor"), + name = "validator_monitor", + skip_all + )] pub fn scrape_metrics(&self, slot_clock: &S, spec: &ChainSpec) { metrics::set_gauge( &metrics::VALIDATOR_MONITOR_VALIDATORS_TOTAL, @@ -2074,7 +2309,6 @@ fn register_simulated_attestation( head_hit: bool, target_hit: bool, source_hit: bool, - log: &Logger, ) { if head_hit { metrics::inc_counter(&metrics::VALIDATOR_MONITOR_ATTESTATION_SIMULATOR_HEAD_ATTESTER_HIT); @@ -2097,15 +2331,14 @@ fn register_simulated_attestation( } debug!( - log, - "Simulated attestation evaluated"; - "attestation_source" => ?data.source.root, - "attestation_target" => ?data.target.root, - "attestation_head" => ?data.beacon_block_root, - "attestation_slot" => ?data.slot, - "source_hit" => source_hit, - "target_hit" => target_hit, - "head_hit" => head_hit, + attestation_source = ?data.source.root, + attestation_target = ?data.target.root, + attestation_head = ?data.beacon_block_root, + attestation_slot = ?data.slot, + source_hit, + target_hit, + head_hit, + "Simulated attestation evaluated" ); } diff --git a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs index 877c297a3b..39d2c2c2d7 100644 --- a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs +++ b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs @@ -210,7 +210,7 @@ impl DatabasePubkey { mod test { use super::*; use crate::test_utils::{BeaconChainHarness, EphemeralHarnessType}; - use logging::test_logger; + use logging::create_test_tracing_subscriber; use std::sync::Arc; use store::HotColdDB; use types::{EthSpec, Keypair, MainnetEthSpec}; @@ -231,10 +231,8 @@ mod test { } fn get_store() -> BeaconStore { - Arc::new( - HotColdDB::open_ephemeral(<_>::default(), Arc::new(E::default_spec()), test_logger()) - .unwrap(), - ) + create_test_tracing_subscriber(); + Arc::new(HotColdDB::open_ephemeral(<_>::default(), Arc::new(E::default_spec())).unwrap()) } #[allow(clippy::needless_range_loop)] diff --git a/beacon_node/beacon_chain/tests/attestation_production.rs b/beacon_node/beacon_chain/tests/attestation_production.rs index 621475a3ec..d89a8530e1 100644 --- a/beacon_node/beacon_chain/tests/attestation_production.rs +++ b/beacon_node/beacon_chain/tests/attestation_production.rs @@ -242,7 +242,7 @@ async fn produces_attestations() { .early_attester_cache .add_head_block( block_root, - available_block, + &available_block, proto_block, &state, &chain.spec, @@ -310,7 +310,7 @@ async fn early_attester_cache_old_request() { .early_attester_cache .add_head_block( head.beacon_block_root, - available_block, + &available_block, head_proto_block, &head.beacon_state, &harness.chain.spec, diff --git a/beacon_node/beacon_chain/tests/bellatrix.rs b/beacon_node/beacon_chain/tests/bellatrix.rs index 5080b0890b..3a424e73ba 100644 --- a/beacon_node/beacon_chain/tests/bellatrix.rs +++ b/beacon_node/beacon_chain/tests/bellatrix.rs @@ -50,7 +50,6 @@ async fn merge_with_terminal_block_hash_override() { let harness = BeaconChainHarness::builder(E::default()) .spec(spec.into()) - .logger(logging::test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() .mock_execution_layer() @@ -107,7 +106,6 @@ async fn base_altair_bellatrix_with_terminal_block_after_fork() { let harness = BeaconChainHarness::builder(E::default()) .spec(spec.into()) - .logger(logging::test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() .mock_execution_layer() diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 2a881b5b0f..5e39bf32c2 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -12,7 +12,7 @@ use beacon_chain::{ BeaconSnapshot, BlockError, ChainConfig, ChainSegmentResult, IntoExecutionPendingBlock, InvalidSignature, NotifyExecutionLayer, }; -use logging::test_logger; +use logging::create_test_tracing_subscriber; use slasher::{Config as SlasherConfig, Slasher}; use state_processing::{ common::{attesting_indices_base, attesting_indices_electra}, @@ -1295,15 +1295,11 @@ async fn verify_and_process_gossip_data_sidecars( #[tokio::test] async fn verify_block_for_gossip_slashing_detection() { + create_test_tracing_subscriber(); let slasher_dir = tempdir().unwrap(); let spec = Arc::new(test_spec::()); let slasher = Arc::new( - Slasher::open( - SlasherConfig::new(slasher_dir.path().into()), - spec.clone(), - test_logger(), - ) - .unwrap(), + Slasher::open(SlasherConfig::new(slasher_dir.path().into()), spec.clone()).unwrap(), ); let inner_slasher = slasher.clone(); diff --git a/beacon_node/beacon_chain/tests/capella.rs b/beacon_node/beacon_chain/tests/capella.rs index 3ce5702f2e..2c2ba8e01a 100644 --- a/beacon_node/beacon_chain/tests/capella.rs +++ b/beacon_node/beacon_chain/tests/capella.rs @@ -40,7 +40,6 @@ async fn base_altair_bellatrix_capella() { let harness = BeaconChainHarness::builder(E::default()) .spec(spec.into()) - .logger(logging::test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() .mock_execution_layer() diff --git a/beacon_node/beacon_chain/tests/op_verification.rs b/beacon_node/beacon_chain/tests/op_verification.rs index 44fb298d6c..86ab0cce80 100644 --- a/beacon_node/beacon_chain/tests/op_verification.rs +++ b/beacon_node/beacon_chain/tests/op_verification.rs @@ -9,7 +9,6 @@ use beacon_chain::{ }, BeaconChainError, }; -use sloggers::{null::NullLoggerBuilder, Build}; use state_processing::per_block_processing::errors::{ AttesterSlashingInvalid, BlockOperationError, ExitInvalid, ProposerSlashingInvalid, }; @@ -35,7 +34,6 @@ fn get_store(db_path: &TempDir) -> Arc { let cold_path = db_path.path().join("cold_db"); let blobs_path = db_path.path().join("blobs_db"); let config = StoreConfig::default(); - let log = NullLoggerBuilder.build().expect("logger should build"); HotColdDB::open( &hot_path, &cold_path, @@ -43,7 +41,6 @@ fn get_store(db_path: &TempDir) -> Arc { |_, _, _| Ok(()), config, spec, - log, ) .expect("disk store should initialize") } diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index 01b790bb25..f81fe482ef 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -12,7 +12,6 @@ use execution_layer::{ ExecutionLayer, ForkchoiceState, PayloadAttributes, }; use fork_choice::{Error as ForkChoiceError, InvalidationOperation, PayloadVerificationStatus}; -use logging::test_logger; use proto_array::{Error as ProtoArrayError, ExecutionStatus}; use slot_clock::SlotClock; use std::collections::HashMap; @@ -56,7 +55,6 @@ impl InvalidPayloadRig { reconstruct_historic_states: true, ..ChainConfig::default() }) - .logger(test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .mock_execution_layer() .fresh_ephemeral_store() diff --git a/beacon_node/beacon_chain/tests/rewards.rs b/beacon_node/beacon_chain/tests/rewards.rs index 41e6467b0f..06e0cf890e 100644 --- a/beacon_node/beacon_chain/tests/rewards.rs +++ b/beacon_node/beacon_chain/tests/rewards.rs @@ -328,8 +328,7 @@ async fn test_rewards_base_multi_inclusion() { .extend_slots(E::slots_per_epoch() as usize * 2 - 4) .await; - // pin to reduce stack size for clippy - Box::pin(check_all_base_rewards(&harness, initial_balances)).await; + check_all_base_rewards(&harness, initial_balances).await; } #[tokio::test] @@ -692,7 +691,8 @@ async fn check_all_base_rewards( harness: &BeaconChainHarness>, balances: Vec, ) { - check_all_base_rewards_for_subset(harness, balances, vec![]).await; + // The box reduces the size on the stack for a clippy lint. + Box::pin(check_all_base_rewards_for_subset(harness, balances, vec![])).await; } async fn check_all_base_rewards_for_subset( diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 7a2df76970..9212ed998d 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -14,7 +14,7 @@ use beacon_chain::{ migrate::MigratorConfig, BeaconChain, BeaconChainError, BeaconChainTypes, BeaconSnapshot, BlockError, ChainConfig, NotifyExecutionLayer, ServerSentEventHandler, WhenSlotSkipped, }; -use logging::test_logger; +use logging::create_test_tracing_subscriber; use maplit::hashset; use rand::Rng; use slot_clock::{SlotClock, TestingSlotClock}; @@ -59,10 +59,10 @@ fn get_store_generic( config: StoreConfig, spec: ChainSpec, ) -> Arc, BeaconNodeBackend>> { + create_test_tracing_subscriber(); let hot_path = db_path.path().join("chain_db"); let cold_path = db_path.path().join("freezer_db"); let blobs_path = db_path.path().join("blobs_db"); - let log = test_logger(); HotColdDB::open( &hot_path, @@ -71,7 +71,6 @@ fn get_store_generic( |_, _, _| Ok(()), config, spec.into(), - log, ) .expect("disk store should initialize") } @@ -109,7 +108,6 @@ fn get_harness_generic( let harness = TestHarness::builder(MinimalEthSpec) .spec(store.get_chain_spec().clone()) .keypairs(KEYPAIRS[0..validator_count].to_vec()) - .logger(store.logger().clone()) .fresh_disk_store(store) .mock_execution_layer() .chain_config(chain_config) @@ -2359,7 +2357,7 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { .await; let (shutdown_tx, _shutdown_rx) = futures::channel::mpsc::channel(1); - let log = harness.chain.logger().clone(); + let temp2 = tempdir().unwrap(); let store = get_store(&temp2); let spec = test_spec::(); @@ -2385,7 +2383,6 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { .store(store.clone()) .custom_spec(test_spec::().into()) .task_executor(harness.chain.task_executor.clone()) - .logger(log.clone()) .weak_subjectivity_state( wss_state, wss_block.clone(), @@ -2399,10 +2396,7 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { .slot_clock(slot_clock) .shutdown_sender(shutdown_tx) .chain_config(ChainConfig::default()) - .event_handler(Some(ServerSentEventHandler::new_with_capacity( - log.clone(), - 1, - ))) + .event_handler(Some(ServerSentEventHandler::new_with_capacity(1))) .execution_layer(Some(mock.el)) .build() .expect("should build"); @@ -2517,18 +2511,13 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { // Corrupt the signature on the 1st block to ensure that the backfill processor is checking // signatures correctly. Regression test for https://github.com/sigp/lighthouse/pull/5120. - let mut batch_with_invalid_first_block = available_blocks.clone(); + let mut batch_with_invalid_first_block = + available_blocks.iter().map(clone_block).collect::>(); batch_with_invalid_first_block[0] = { - let (block_root, block, blobs, data_columns) = available_blocks[0].clone().deconstruct(); + let (block_root, block, data) = clone_block(&available_blocks[0]).deconstruct(); let mut corrupt_block = (*block).clone(); *corrupt_block.signature_mut() = Signature::empty(); - AvailableBlock::__new_for_testing( - block_root, - Arc::new(corrupt_block), - blobs, - data_columns, - Arc::new(spec), - ) + AvailableBlock::__new_for_testing(block_root, Arc::new(corrupt_block), data, Arc::new(spec)) }; // Importing the invalid batch should error. @@ -2540,8 +2529,9 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { )); // Importing the batch with valid signatures should succeed. + let available_blocks_dup = available_blocks.iter().map(clone_block).collect::>(); beacon_chain - .import_historical_block_batch(available_blocks.clone()) + .import_historical_block_batch(available_blocks_dup) .unwrap(); assert_eq!(beacon_chain.store.get_oldest_block_slot(), 0); @@ -3058,7 +3048,6 @@ async fn schema_downgrade_to_min_version() { genesis_state_root, CURRENT_SCHEMA_VERSION, min_version, - store.logger().clone(), ) .expect("schema downgrade to minimum version should work"); @@ -3068,7 +3057,6 @@ async fn schema_downgrade_to_min_version() { genesis_state_root, min_version, CURRENT_SCHEMA_VERSION, - store.logger().clone(), ) .expect("schema upgrade from minimum version should work"); @@ -3076,7 +3064,6 @@ async fn schema_downgrade_to_min_version() { let harness = BeaconChainHarness::builder(MinimalEthSpec) .default_spec() .keypairs(KEYPAIRS[0..LOW_VALIDATOR_COUNT].to_vec()) - .logger(store.logger().clone()) .testing_slot_clock(slot_clock) .resumed_disk_store(store.clone()) .mock_execution_layer() @@ -3094,7 +3081,6 @@ async fn schema_downgrade_to_min_version() { genesis_state_root, CURRENT_SCHEMA_VERSION, min_version_sub_1, - harness.logger().clone(), ) .expect_err("should not downgrade below minimum version"); } @@ -3690,3 +3676,7 @@ fn get_blocks( .map(|checkpoint| checkpoint.beacon_block_root.into()) .collect() } + +fn clone_block(block: &AvailableBlock) -> AvailableBlock { + block.__clone_without_recv().unwrap() +} diff --git a/beacon_node/beacon_chain/tests/validator_monitor.rs b/beacon_node/beacon_chain/tests/validator_monitor.rs index 180db6d76d..bca37b4e6d 100644 --- a/beacon_node/beacon_chain/tests/validator_monitor.rs +++ b/beacon_node/beacon_chain/tests/validator_monitor.rs @@ -2,7 +2,6 @@ use beacon_chain::test_utils::{ AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, }; use beacon_chain::validator_monitor::{ValidatorMonitorConfig, MISSED_BLOCK_LAG_SLOTS}; -use logging::test_logger; use std::sync::LazyLock; use types::{Epoch, EthSpec, Keypair, MainnetEthSpec, PublicKeyBytes, Slot}; @@ -22,7 +21,6 @@ fn get_harness( let harness = BeaconChainHarness::builder(MainnetEthSpec) .default_spec() .keypairs(KEYPAIRS[0..validator_count].to_vec()) - .logger(test_logger()) .fresh_ephemeral_store() .mock_execution_layer() .validator_monitor_config(ValidatorMonitorConfig { diff --git a/beacon_node/beacon_processor/Cargo.toml b/beacon_node/beacon_processor/Cargo.toml index c96e0868d7..afd4660c9a 100644 --- a/beacon_node/beacon_processor/Cargo.toml +++ b/beacon_node/beacon_processor/Cargo.toml @@ -13,12 +13,12 @@ metrics = { workspace = true } num_cpus = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } strum = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } +tracing = { workspace = true } types = { workspace = true } [dev-dependencies] diff --git a/beacon_node/beacon_processor/src/lib.rs b/beacon_node/beacon_processor/src/lib.rs index 2743f93bb3..e864cb1fd9 100644 --- a/beacon_node/beacon_processor/src/lib.rs +++ b/beacon_node/beacon_processor/src/lib.rs @@ -44,10 +44,10 @@ use crate::work_reprocessing_queue::{ use futures::stream::{Stream, StreamExt}; use futures::task::Poll; use lighthouse_network::{MessageId, NetworkGlobals, PeerId}; +use logging::crit; use logging::TimeLatch; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; -use slog::{crit, debug, error, trace, warn, Logger}; use slot_clock::SlotClock; use std::cmp; use std::collections::{HashSet, VecDeque}; @@ -61,6 +61,7 @@ use strum::IntoStaticStr; use task_executor::TaskExecutor; use tokio::sync::mpsc; use tokio::sync::mpsc::error::TrySendError; +use tracing::{debug, error, trace, warn}; use types::{ Attestation, BeaconState, ChainSpec, EthSpec, Hash256, RelativeEpoch, SignedAggregateAndProof, SingleAttestation, Slot, SubnetId, @@ -109,8 +110,6 @@ pub struct BeaconProcessorQueueLengths { gossip_voluntary_exit_queue: usize, gossip_proposer_slashing_queue: usize, gossip_attester_slashing_queue: usize, - finality_update_queue: usize, - optimistic_update_queue: usize, unknown_light_client_update_queue: usize, unknown_block_sampling_request_queue: usize, rpc_block_queue: usize, @@ -132,9 +131,11 @@ pub struct BeaconProcessorQueueLengths { dcbroots_queue: usize, dcbrange_queue: usize, gossip_bls_to_execution_change_queue: usize, + lc_gossip_finality_update_queue: usize, + lc_gossip_optimistic_update_queue: usize, lc_bootstrap_queue: usize, - lc_optimistic_update_queue: usize, - lc_finality_update_queue: usize, + lc_rpc_optimistic_update_queue: usize, + lc_rpc_finality_update_queue: usize, lc_update_range_queue: usize, api_request_p0_queue: usize, api_request_p1_queue: usize, @@ -175,15 +176,13 @@ impl BeaconProcessorQueueLengths { gossip_voluntary_exit_queue: 4096, gossip_proposer_slashing_queue: 4096, gossip_attester_slashing_queue: 4096, - finality_update_queue: 1024, - optimistic_update_queue: 1024, - unknown_block_sampling_request_queue: 16384, unknown_light_client_update_queue: 128, rpc_block_queue: 1024, rpc_blob_queue: 1024, // TODO(das): Placeholder values rpc_custody_column_queue: 1000, rpc_verify_data_column_queue: 1000, + unknown_block_sampling_request_queue: 16384, sampling_result_queue: 1000, chain_segment_queue: 64, backfill_chain_segment: 64, @@ -200,9 +199,11 @@ impl BeaconProcessorQueueLengths { dcbroots_queue: 1024, dcbrange_queue: 1024, gossip_bls_to_execution_change_queue: 16384, + lc_gossip_finality_update_queue: 1024, + lc_gossip_optimistic_update_queue: 1024, lc_bootstrap_queue: 1024, - lc_optimistic_update_queue: 512, - lc_finality_update_queue: 512, + lc_rpc_optimistic_update_queue: 512, + lc_rpc_finality_update_queue: 512, lc_update_range_queue: 512, api_request_p0_queue: 1024, api_request_p1_queue: 1024, @@ -305,14 +306,13 @@ impl FifoQueue { /// Add a new item to the queue. /// /// Drops `item` if the queue is full. - pub fn push(&mut self, item: T, item_desc: &str, log: &Logger) { + pub fn push(&mut self, item: T, item_desc: &str) { if self.queue.len() == self.max_length { error!( - log, - "Work queue is full"; - "msg" => "the system has insufficient resources for load", - "queue_len" => self.max_length, - "queue" => item_desc, + msg = "the system has insufficient resources for load", + queue_len = self.max_length, + queue = item_desc, + "Work queue is full" ) } else { self.queue.push_back(item); @@ -827,7 +827,6 @@ pub struct BeaconProcessor { pub executor: TaskExecutor, pub current_workers: usize, pub config: BeaconProcessorConfig, - pub log: Logger, } impl BeaconProcessor { @@ -884,21 +883,16 @@ impl BeaconProcessor { let mut gossip_attester_slashing_queue = FifoQueue::new(queue_lengths.gossip_attester_slashing_queue); - // Using a FIFO queue for light client updates to maintain sequence order. - let mut finality_update_queue = FifoQueue::new(queue_lengths.finality_update_queue); - let mut optimistic_update_queue = FifoQueue::new(queue_lengths.optimistic_update_queue); - let mut unknown_light_client_update_queue = - FifoQueue::new(queue_lengths.unknown_light_client_update_queue); - let mut unknown_block_sampling_request_queue = - FifoQueue::new(queue_lengths.unknown_block_sampling_request_queue); - // Using a FIFO queue since blocks need to be imported sequentially. let mut rpc_block_queue = FifoQueue::new(queue_lengths.rpc_block_queue); let mut rpc_blob_queue = FifoQueue::new(queue_lengths.rpc_blob_queue); let mut rpc_custody_column_queue = FifoQueue::new(queue_lengths.rpc_custody_column_queue); let mut rpc_verify_data_column_queue = FifoQueue::new(queue_lengths.rpc_verify_data_column_queue); + // TODO(das): the sampling_request_queue is never read let mut sampling_result_queue = FifoQueue::new(queue_lengths.sampling_result_queue); + let mut unknown_block_sampling_request_queue = + FifoQueue::new(queue_lengths.unknown_block_sampling_request_queue); let mut chain_segment_queue = FifoQueue::new(queue_lengths.chain_segment_queue); let mut backfill_chain_segment = FifoQueue::new(queue_lengths.backfill_chain_segment); let mut gossip_block_queue = FifoQueue::new(queue_lengths.gossip_block_queue); @@ -917,10 +911,18 @@ impl BeaconProcessor { let mut gossip_bls_to_execution_change_queue = FifoQueue::new(queue_lengths.gossip_bls_to_execution_change_queue); + // Using FIFO queues for light client updates to maintain sequence order. + let mut lc_gossip_finality_update_queue = + FifoQueue::new(queue_lengths.lc_gossip_finality_update_queue); + let mut lc_gossip_optimistic_update_queue = + FifoQueue::new(queue_lengths.lc_gossip_optimistic_update_queue); + let mut unknown_light_client_update_queue = + FifoQueue::new(queue_lengths.unknown_light_client_update_queue); let mut lc_bootstrap_queue = FifoQueue::new(queue_lengths.lc_bootstrap_queue); - let mut lc_optimistic_update_queue = - FifoQueue::new(queue_lengths.lc_optimistic_update_queue); - let mut lc_finality_update_queue = FifoQueue::new(queue_lengths.lc_finality_update_queue); + let mut lc_rpc_optimistic_update_queue = + FifoQueue::new(queue_lengths.lc_rpc_optimistic_update_queue); + let mut lc_rpc_finality_update_queue = + FifoQueue::new(queue_lengths.lc_rpc_finality_update_queue); let mut lc_update_range_queue = FifoQueue::new(queue_lengths.lc_update_range_queue); let mut api_request_p0_queue = FifoQueue::new(queue_lengths.api_request_p0_queue); @@ -935,7 +937,6 @@ impl BeaconProcessor { work_reprocessing_rx, &self.executor, Arc::new(slot_clock), - self.log.clone(), maximum_gossip_clock_disparity, )?; @@ -966,9 +967,8 @@ impl BeaconProcessor { { Err(e) => { warn!( - self.log, - "Unable to queue backfill work event. Will try to process now."; - "error" => %e + error = %e, + "Unable to queue backfill work event. Will try to process now." ); match e { TrySendError::Full(reprocess_queue_message) @@ -979,9 +979,8 @@ impl BeaconProcessor { ) => Some(backfill_batch.into()), other => { crit!( - self.log, - "Unexpected queue message type"; - "message_type" => other.as_ref() + message_type = other.as_ref(), + "Unexpected queue message type" ); // This is an unhandled exception, drop the message. continue; @@ -1002,11 +1001,7 @@ impl BeaconProcessor { Some(InboundEvent::WorkEvent(event)) | Some(InboundEvent::ReprocessingWork(event)) => Some(event), None => { - debug!( - self.log, - "Gossip processor stopped"; - "msg" => "stream ended" - ); + debug!(msg = "stream ended", "Gossip processor stopped"); break; } }; @@ -1047,230 +1042,234 @@ impl BeaconProcessor { None if can_spawn => { // Check for chain segments first, they're the most efficient way to get // blocks into the system. - let work_event: Option> = if let Some(item) = - chain_segment_queue.pop() - { - Some(item) - // Check sync blocks before gossip blocks, since we've already explicitly - // requested these blocks. - } else if let Some(item) = rpc_block_queue.pop() { - Some(item) - } else if let Some(item) = rpc_blob_queue.pop() { - Some(item) - } else if let Some(item) = rpc_custody_column_queue.pop() { - Some(item) - // TODO(das): decide proper prioritization for sampling columns - } else if let Some(item) = rpc_custody_column_queue.pop() { - Some(item) - } else if let Some(item) = rpc_verify_data_column_queue.pop() { - Some(item) - } else if let Some(item) = sampling_result_queue.pop() { - Some(item) - // Check delayed blocks before gossip blocks, the gossip blocks might rely - // on the delayed ones. - } else if let Some(item) = delayed_block_queue.pop() { - Some(item) - // Check gossip blocks before gossip attestations, since a block might be - // required to verify some attestations. - } else if let Some(item) = gossip_block_queue.pop() { - Some(item) - } else if let Some(item) = gossip_blob_queue.pop() { - Some(item) - } else if let Some(item) = gossip_data_column_queue.pop() { - Some(item) - // Check the priority 0 API requests after blocks and blobs, but before attestations. - } else if let Some(item) = api_request_p0_queue.pop() { - Some(item) - // Check the aggregates, *then* the unaggregates since we assume that - // aggregates are more valuable to local validators and effectively give us - // more information with less signature verification time. - } else if aggregate_queue.len() > 0 { - let batch_size = cmp::min( - aggregate_queue.len(), - self.config.max_gossip_aggregate_batch_size, - ); + let work_event: Option> = + if let Some(item) = chain_segment_queue.pop() { + Some(item) + // Check sync blocks before gossip blocks, since we've already explicitly + // requested these blocks. + } else if let Some(item) = rpc_block_queue.pop() { + Some(item) + } else if let Some(item) = rpc_blob_queue.pop() { + Some(item) + } else if let Some(item) = rpc_custody_column_queue.pop() { + Some(item) + // TODO(das): decide proper prioritization for sampling columns + } else if let Some(item) = rpc_custody_column_queue.pop() { + Some(item) + } else if let Some(item) = rpc_verify_data_column_queue.pop() { + Some(item) + } else if let Some(item) = sampling_result_queue.pop() { + Some(item) + // Check delayed blocks before gossip blocks, the gossip blocks might rely + // on the delayed ones. + } else if let Some(item) = delayed_block_queue.pop() { + Some(item) + // Check gossip blocks before gossip attestations, since a block might be + // required to verify some attestations. + } else if let Some(item) = gossip_block_queue.pop() { + Some(item) + } else if let Some(item) = gossip_blob_queue.pop() { + Some(item) + } else if let Some(item) = gossip_data_column_queue.pop() { + Some(item) + // Check the priority 0 API requests after blocks and blobs, but before attestations. + } else if let Some(item) = api_request_p0_queue.pop() { + Some(item) + // Check the aggregates, *then* the unaggregates since we assume that + // aggregates are more valuable to local validators and effectively give us + // more information with less signature verification time. + } else if aggregate_queue.len() > 0 { + let batch_size = cmp::min( + aggregate_queue.len(), + self.config.max_gossip_aggregate_batch_size, + ); - if batch_size < 2 { - // One single aggregate is in the queue, process it individually. - aggregate_queue.pop() - } else { - // Collect two or more aggregates into a batch, so they can take - // advantage of batch signature verification. - // - // Note: this will convert the `Work::GossipAggregate` item into a - // `Work::GossipAggregateBatch` item. - let mut aggregates = Vec::with_capacity(batch_size); - let mut process_batch_opt = None; - for _ in 0..batch_size { - if let Some(item) = aggregate_queue.pop() { - match item { - Work::GossipAggregate { - aggregate, - process_individual: _, - process_batch, - } => { - aggregates.push(*aggregate); - if process_batch_opt.is_none() { - process_batch_opt = Some(process_batch); + if batch_size < 2 { + // One single aggregate is in the queue, process it individually. + aggregate_queue.pop() + } else { + // Collect two or more aggregates into a batch, so they can take + // advantage of batch signature verification. + // + // Note: this will convert the `Work::GossipAggregate` item into a + // `Work::GossipAggregateBatch` item. + let mut aggregates = Vec::with_capacity(batch_size); + let mut process_batch_opt = None; + for _ in 0..batch_size { + if let Some(item) = aggregate_queue.pop() { + match item { + Work::GossipAggregate { + aggregate, + process_individual: _, + process_batch, + } => { + aggregates.push(*aggregate); + if process_batch_opt.is_none() { + process_batch_opt = Some(process_batch); + } + } + _ => { + error!("Invalid item in aggregate queue"); } - } - _ => { - error!(self.log, "Invalid item in aggregate queue"); } } } - } - if let Some(process_batch) = process_batch_opt { - // Process all aggregates with a single worker. - Some(Work::GossipAggregateBatch { - aggregates, - process_batch, - }) - } else { - // There is no good reason for this to - // happen, it is a serious logic error. - // Since we only form batches when multiple - // work items exist, we should always have a - // work closure at this point. - crit!(self.log, "Missing aggregate work"); - None - } - } - // Check the unaggregated attestation queue. - // - // Potentially use batching. - } else if attestation_queue.len() > 0 { - let batch_size = cmp::min( - attestation_queue.len(), - self.config.max_gossip_attestation_batch_size, - ); - - if batch_size < 2 { - // One single attestation is in the queue, process it individually. - attestation_queue.pop() - } else { - // Collect two or more attestations into a batch, so they can take - // advantage of batch signature verification. - // - // Note: this will convert the `Work::GossipAttestation` item into a - // `Work::GossipAttestationBatch` item. - let mut attestations = Vec::with_capacity(batch_size); - let mut process_batch_opt = None; - for _ in 0..batch_size { - if let Some(item) = attestation_queue.pop() { - match item { - Work::GossipAttestation { - attestation, - process_individual: _, - process_batch, - } => { - attestations.push(*attestation); - if process_batch_opt.is_none() { - process_batch_opt = Some(process_batch); - } - } - _ => error!( - self.log, - "Invalid item in attestation queue" - ), - } + if let Some(process_batch) = process_batch_opt { + // Process all aggregates with a single worker. + Some(Work::GossipAggregateBatch { + aggregates, + process_batch, + }) + } else { + // There is no good reason for this to + // happen, it is a serious logic error. + // Since we only form batches when multiple + // work items exist, we should always have a + // work closure at this point. + crit!("Missing aggregate work"); + None } } + // Check the unaggregated attestation queue. + // + // Potentially use batching. + } else if attestation_queue.len() > 0 { + let batch_size = cmp::min( + attestation_queue.len(), + self.config.max_gossip_attestation_batch_size, + ); - if let Some(process_batch) = process_batch_opt { - // Process all attestations with a single worker. - Some(Work::GossipAttestationBatch { - attestations, - process_batch, - }) + if batch_size < 2 { + // One single attestation is in the queue, process it individually. + attestation_queue.pop() } else { - // There is no good reason for this to - // happen, it is a serious logic error. - // Since we only form batches when multiple - // work items exist, we should always have a - // work closure at this point. - crit!(self.log, "Missing attestations work"); - None + // Collect two or more attestations into a batch, so they can take + // advantage of batch signature verification. + // + // Note: this will convert the `Work::GossipAttestation` item into a + // `Work::GossipAttestationBatch` item. + let mut attestations = Vec::with_capacity(batch_size); + let mut process_batch_opt = None; + for _ in 0..batch_size { + if let Some(item) = attestation_queue.pop() { + match item { + Work::GossipAttestation { + attestation, + process_individual: _, + process_batch, + } => { + attestations.push(*attestation); + if process_batch_opt.is_none() { + process_batch_opt = Some(process_batch); + } + } + _ => error!("Invalid item in attestation queue"), + } + } + } + + if let Some(process_batch) = process_batch_opt { + // Process all attestations with a single worker. + Some(Work::GossipAttestationBatch { + attestations, + process_batch, + }) + } else { + // There is no good reason for this to + // happen, it is a serious logic error. + // Since we only form batches when multiple + // work items exist, we should always have a + // work closure at this point. + crit!("Missing attestations work"); + None + } } - } - // Convert any gossip attestations that need to be converted. - } else if let Some(item) = attestation_to_convert_queue.pop() { - Some(item) - // Check sync committee messages after attestations as their rewards are lesser - // and they don't influence fork choice. - } else if let Some(item) = sync_contribution_queue.pop() { - Some(item) - } else if let Some(item) = sync_message_queue.pop() { - Some(item) - // Aggregates and unaggregates queued for re-processing are older and we - // care about fresher ones, so check those first. - } else if let Some(item) = unknown_block_aggregate_queue.pop() { - Some(item) - } else if let Some(item) = unknown_block_attestation_queue.pop() { - Some(item) - // Check RPC methods next. Status messages are needed for sync so - // prioritize them over syncing requests from other peers (BlocksByRange - // and BlocksByRoot) - } else if let Some(item) = status_queue.pop() { - Some(item) - } else if let Some(item) = bbrange_queue.pop() { - Some(item) - } else if let Some(item) = bbroots_queue.pop() { - Some(item) - } else if let Some(item) = blbrange_queue.pop() { - Some(item) - } else if let Some(item) = blbroots_queue.pop() { - Some(item) - } else if let Some(item) = dcbroots_queue.pop() { - Some(item) - } else if let Some(item) = dcbrange_queue.pop() { - Some(item) - // Prioritize sampling requests after block syncing requests - } else if let Some(item) = unknown_block_sampling_request_queue.pop() { - Some(item) - // Check slashings after all other consensus messages so we prioritize - // following head. - // - // Check attester slashings before proposer slashings since they have the - // potential to slash multiple validators at once. - } else if let Some(item) = gossip_attester_slashing_queue.pop() { - Some(item) - } else if let Some(item) = gossip_proposer_slashing_queue.pop() { - Some(item) - // Check exits and address changes late since our validators don't get - // rewards from them. - } else if let Some(item) = gossip_voluntary_exit_queue.pop() { - Some(item) - } else if let Some(item) = gossip_bls_to_execution_change_queue.pop() { - Some(item) - // Check the priority 1 API requests after we've - // processed all the interesting things from the network - // and things required for us to stay in good repute - // with our P2P peers. - } else if let Some(item) = api_request_p1_queue.pop() { - Some(item) - // Handle backfill sync chain segments. - } else if let Some(item) = backfill_chain_segment.pop() { - Some(item) - // Handle light client requests. - } else if let Some(item) = lc_bootstrap_queue.pop() { - Some(item) - } else if let Some(item) = lc_optimistic_update_queue.pop() { - Some(item) - } else if let Some(item) = lc_finality_update_queue.pop() { - Some(item) - // This statement should always be the final else statement. - } else { - // Let the journal know that a worker is freed and there's nothing else - // for it to do. - if let Some(work_journal_tx) = &work_journal_tx { - // We don't care if this message was successfully sent, we only use the journal - // during testing. - let _ = work_journal_tx.try_send(NOTHING_TO_DO); - } - None - }; + // Convert any gossip attestations that need to be converted. + } else if let Some(item) = attestation_to_convert_queue.pop() { + Some(item) + // Check sync committee messages after attestations as their rewards are lesser + // and they don't influence fork choice. + } else if let Some(item) = sync_contribution_queue.pop() { + Some(item) + } else if let Some(item) = sync_message_queue.pop() { + Some(item) + // Aggregates and unaggregates queued for re-processing are older and we + // care about fresher ones, so check those first. + } else if let Some(item) = unknown_block_aggregate_queue.pop() { + Some(item) + } else if let Some(item) = unknown_block_attestation_queue.pop() { + Some(item) + // Check RPC methods next. Status messages are needed for sync so + // prioritize them over syncing requests from other peers (BlocksByRange + // and BlocksByRoot) + } else if let Some(item) = status_queue.pop() { + Some(item) + } else if let Some(item) = bbrange_queue.pop() { + Some(item) + } else if let Some(item) = bbroots_queue.pop() { + Some(item) + } else if let Some(item) = blbrange_queue.pop() { + Some(item) + } else if let Some(item) = blbroots_queue.pop() { + Some(item) + } else if let Some(item) = dcbroots_queue.pop() { + Some(item) + } else if let Some(item) = dcbrange_queue.pop() { + Some(item) + // Prioritize sampling requests after block syncing requests + } else if let Some(item) = unknown_block_sampling_request_queue.pop() { + Some(item) + // Check slashings after all other consensus messages so we prioritize + // following head. + // + // Check attester slashings before proposer slashings since they have the + // potential to slash multiple validators at once. + } else if let Some(item) = gossip_attester_slashing_queue.pop() { + Some(item) + } else if let Some(item) = gossip_proposer_slashing_queue.pop() { + Some(item) + // Check exits and address changes late since our validators don't get + // rewards from them. + } else if let Some(item) = gossip_voluntary_exit_queue.pop() { + Some(item) + } else if let Some(item) = gossip_bls_to_execution_change_queue.pop() { + Some(item) + // Check the priority 1 API requests after we've + // processed all the interesting things from the network + // and things required for us to stay in good repute + // with our P2P peers. + } else if let Some(item) = api_request_p1_queue.pop() { + Some(item) + // Handle backfill sync chain segments. + } else if let Some(item) = backfill_chain_segment.pop() { + Some(item) + // Handle light client requests. + } else if let Some(item) = lc_gossip_finality_update_queue.pop() { + Some(item) + } else if let Some(item) = lc_gossip_optimistic_update_queue.pop() { + Some(item) + } else if let Some(item) = unknown_light_client_update_queue.pop() { + Some(item) + } else if let Some(item) = lc_bootstrap_queue.pop() { + Some(item) + } else if let Some(item) = lc_rpc_optimistic_update_queue.pop() { + Some(item) + } else if let Some(item) = lc_rpc_finality_update_queue.pop() { + Some(item) + } else if let Some(item) = lc_update_range_queue.pop() { + Some(item) + // This statement should always be the final else statement. + } else { + // Let the journal know that a worker is freed and there's nothing else + // for it to do. + if let Some(work_journal_tx) = &work_journal_tx { + // We don't care if this message was successfully sent, we only use the journal + // during testing. + let _ = work_journal_tx.try_send(NOTHING_TO_DO); + } + None + }; if let Some(work_event) = work_event { let work_type = work_event.to_type(); @@ -1285,9 +1284,8 @@ impl BeaconProcessor { // I cannot see any good reason why this would happen. None => { warn!( - self.log, - "Unexpected gossip processor condition"; - "msg" => "no new work and cannot spawn worker" + msg = "no new work and cannot spawn worker", + "Unexpected gossip processor condition" ); None } @@ -1302,10 +1300,9 @@ impl BeaconProcessor { &[work_id], ); trace!( - self.log, - "Gossip processor skipping work"; - "msg" => "chain is syncing", - "work_id" => work_id + msg = "chain is syncing", + work_id = work_id, + "Gossip processor skipping work" ); None } @@ -1324,89 +1321,75 @@ impl BeaconProcessor { // Attestation batches are formed internally within the // `BeaconProcessor`, they are not sent from external services. Work::GossipAttestationBatch { .. } => crit!( - self.log, - "Unsupported inbound event"; - "type" => "GossipAttestationBatch" + work_type = "GossipAttestationBatch", + "Unsupported inbound event" ), Work::GossipAggregate { .. } => aggregate_queue.push(work), // Aggregate batches are formed internally within the `BeaconProcessor`, // they are not sent from external services. - Work::GossipAggregateBatch { .. } => crit!( - self.log, - "Unsupported inbound event"; - "type" => "GossipAggregateBatch" - ), - Work::GossipBlock { .. } => { - gossip_block_queue.push(work, work_id, &self.log) - } - Work::GossipBlobSidecar { .. } => { - gossip_blob_queue.push(work, work_id, &self.log) + Work::GossipAggregateBatch { .. } => { + crit!( + work_type = "GossipAggregateBatch", + "Unsupported inbound event" + ) } + Work::GossipBlock { .. } => gossip_block_queue.push(work, work_id), + Work::GossipBlobSidecar { .. } => gossip_blob_queue.push(work, work_id), Work::GossipDataColumnSidecar { .. } => { - gossip_data_column_queue.push(work, work_id, &self.log) + gossip_data_column_queue.push(work, work_id) } Work::DelayedImportBlock { .. } => { - delayed_block_queue.push(work, work_id, &self.log) + delayed_block_queue.push(work, work_id) } Work::GossipVoluntaryExit { .. } => { - gossip_voluntary_exit_queue.push(work, work_id, &self.log) + gossip_voluntary_exit_queue.push(work, work_id) } Work::GossipProposerSlashing { .. } => { - gossip_proposer_slashing_queue.push(work, work_id, &self.log) + gossip_proposer_slashing_queue.push(work, work_id) } Work::GossipAttesterSlashing { .. } => { - gossip_attester_slashing_queue.push(work, work_id, &self.log) + gossip_attester_slashing_queue.push(work, work_id) } Work::GossipSyncSignature { .. } => sync_message_queue.push(work), Work::GossipSyncContribution { .. } => { sync_contribution_queue.push(work) } Work::GossipLightClientFinalityUpdate { .. } => { - finality_update_queue.push(work, work_id, &self.log) + lc_gossip_finality_update_queue.push(work, work_id) } Work::GossipLightClientOptimisticUpdate { .. } => { - optimistic_update_queue.push(work, work_id, &self.log) + lc_gossip_optimistic_update_queue.push(work, work_id) } Work::RpcBlock { .. } | Work::IgnoredRpcBlock { .. } => { - rpc_block_queue.push(work, work_id, &self.log) + rpc_block_queue.push(work, work_id) } - Work::RpcBlobs { .. } => rpc_blob_queue.push(work, work_id, &self.log), + Work::RpcBlobs { .. } => rpc_blob_queue.push(work, work_id), Work::RpcCustodyColumn { .. } => { - rpc_custody_column_queue.push(work, work_id, &self.log) + rpc_custody_column_queue.push(work, work_id) } Work::RpcVerifyDataColumn(_) => { - rpc_verify_data_column_queue.push(work, work_id, &self.log) - } - Work::SamplingResult(_) => { - sampling_result_queue.push(work, work_id, &self.log) - } - Work::ChainSegment { .. } => { - chain_segment_queue.push(work, work_id, &self.log) + rpc_verify_data_column_queue.push(work, work_id) } + Work::SamplingResult(_) => sampling_result_queue.push(work, work_id), + Work::ChainSegment { .. } => chain_segment_queue.push(work, work_id), Work::ChainSegmentBackfill { .. } => { - backfill_chain_segment.push(work, work_id, &self.log) - } - Work::Status { .. } => status_queue.push(work, work_id, &self.log), - Work::BlocksByRangeRequest { .. } => { - bbrange_queue.push(work, work_id, &self.log) - } - Work::BlocksByRootsRequest { .. } => { - bbroots_queue.push(work, work_id, &self.log) - } - Work::BlobsByRangeRequest { .. } => { - blbrange_queue.push(work, work_id, &self.log) + backfill_chain_segment.push(work, work_id) } + Work::Status { .. } => status_queue.push(work, work_id), + Work::BlocksByRangeRequest { .. } => bbrange_queue.push(work, work_id), + Work::BlocksByRootsRequest { .. } => bbroots_queue.push(work, work_id), + Work::BlobsByRangeRequest { .. } => blbrange_queue.push(work, work_id), Work::LightClientBootstrapRequest { .. } => { - lc_bootstrap_queue.push(work, work_id, &self.log) + lc_bootstrap_queue.push(work, work_id) } Work::LightClientOptimisticUpdateRequest { .. } => { - lc_optimistic_update_queue.push(work, work_id, &self.log) + lc_rpc_optimistic_update_queue.push(work, work_id) } Work::LightClientFinalityUpdateRequest { .. } => { - lc_finality_update_queue.push(work, work_id, &self.log) + lc_rpc_finality_update_queue.push(work, work_id) } Work::LightClientUpdatesByRangeRequest { .. } => { - lc_update_range_queue.push(work, work_id, &self.log) + lc_update_range_queue.push(work, work_id) } Work::UnknownBlockAttestation { .. } => { unknown_block_attestation_queue.push(work) @@ -1415,29 +1398,23 @@ impl BeaconProcessor { unknown_block_aggregate_queue.push(work) } Work::GossipBlsToExecutionChange { .. } => { - gossip_bls_to_execution_change_queue.push(work, work_id, &self.log) - } - Work::BlobsByRootsRequest { .. } => { - blbroots_queue.push(work, work_id, &self.log) + gossip_bls_to_execution_change_queue.push(work, work_id) } + Work::BlobsByRootsRequest { .. } => blbroots_queue.push(work, work_id), Work::DataColumnsByRootsRequest { .. } => { - dcbroots_queue.push(work, work_id, &self.log) + dcbroots_queue.push(work, work_id) } Work::DataColumnsByRangeRequest { .. } => { - dcbrange_queue.push(work, work_id, &self.log) + dcbrange_queue.push(work, work_id) } Work::UnknownLightClientOptimisticUpdate { .. } => { - unknown_light_client_update_queue.push(work, work_id, &self.log) + unknown_light_client_update_queue.push(work, work_id) } Work::UnknownBlockSamplingRequest { .. } => { - unknown_block_sampling_request_queue.push(work, work_id, &self.log) - } - Work::ApiRequestP0 { .. } => { - api_request_p0_queue.push(work, work_id, &self.log) - } - Work::ApiRequestP1 { .. } => { - api_request_p1_queue.push(work, work_id, &self.log) + unknown_block_sampling_request_queue.push(work, work_id) } + Work::ApiRequestP0 { .. } => api_request_p0_queue.push(work, work_id), + Work::ApiRequestP1 { .. } => api_request_p1_queue.push(work, work_id), }; Some(work_type) } @@ -1472,9 +1449,11 @@ impl BeaconProcessor { WorkType::GossipAttesterSlashing => gossip_attester_slashing_queue.len(), WorkType::GossipSyncSignature => sync_message_queue.len(), WorkType::GossipSyncContribution => sync_contribution_queue.len(), - WorkType::GossipLightClientFinalityUpdate => finality_update_queue.len(), + WorkType::GossipLightClientFinalityUpdate => { + lc_gossip_finality_update_queue.len() + } WorkType::GossipLightClientOptimisticUpdate => { - optimistic_update_queue.len() + lc_gossip_optimistic_update_queue.len() } WorkType::RpcBlock => rpc_block_queue.len(), WorkType::RpcBlobs | WorkType::IgnoredRpcBlock => rpc_blob_queue.len(), @@ -1495,10 +1474,10 @@ impl BeaconProcessor { } WorkType::LightClientBootstrapRequest => lc_bootstrap_queue.len(), WorkType::LightClientOptimisticUpdateRequest => { - lc_optimistic_update_queue.len() + lc_rpc_optimistic_update_queue.len() } WorkType::LightClientFinalityUpdateRequest => { - lc_finality_update_queue.len() + lc_rpc_finality_update_queue.len() } WorkType::LightClientUpdatesByRangeRequest => lc_update_range_queue.len(), WorkType::ApiRequestP0 => api_request_p0_queue.len(), @@ -1513,19 +1492,17 @@ impl BeaconProcessor { if aggregate_queue.is_full() && aggregate_debounce.elapsed() { error!( - self.log, - "Aggregate attestation queue full"; - "msg" => "the system has insufficient resources for load", - "queue_len" => aggregate_queue.max_length, + msg = "the system has insufficient resources for load", + queue_len = aggregate_queue.max_length, + "Aggregate attestation queue full" ) } if attestation_queue.is_full() && attestation_debounce.elapsed() { error!( - self.log, - "Attestation queue full"; - "msg" => "the system has insufficient resources for load", - "queue_len" => attestation_queue.max_length, + msg = "the system has insufficient resources for load", + queue_len = attestation_queue.max_length, + "Attestation queue full" ) } } @@ -1556,7 +1533,6 @@ impl BeaconProcessor { let send_idle_on_drop = SendOnDrop { tx: idle_tx, _worker_timer: worker_timer, - log: self.log.clone(), }; let worker_id = self.current_workers; @@ -1565,10 +1541,9 @@ impl BeaconProcessor { let executor = self.executor.clone(); trace!( - self.log, - "Spawning beacon processor worker"; - "work" => work_id, - "worker" => worker_id, + work = work_id, + worker = worker_id, + "Spawning beacon processor worker" ); let task_spawner = TaskSpawner { @@ -1706,8 +1681,8 @@ impl TaskSpawner { } } -/// This struct will send a message on `self.tx` when it is dropped. An error will be logged on -/// `self.log` if the send fails (this happens when the node is shutting down). +/// This struct will send a message on `self.tx` when it is dropped. An error will be logged +/// if the send fails (this happens when the node is shutting down). /// /// ## Purpose /// @@ -1720,17 +1695,15 @@ pub struct SendOnDrop { tx: mpsc::Sender<()>, // The field is unused, but it's here to ensure the timer is dropped once the task has finished. _worker_timer: Option, - log: Logger, } impl Drop for SendOnDrop { fn drop(&mut self) { if let Err(e) = self.tx.try_send(()) { warn!( - self.log, - "Unable to free worker"; - "msg" => "did not free worker, shutdown may be underway", - "error" => %e + msg = "did not free worker, shutdown may be underway", + error = %e, + "Unable to free worker" ) } } diff --git a/beacon_node/beacon_processor/src/work_reprocessing_queue.rs b/beacon_node/beacon_processor/src/work_reprocessing_queue.rs index a43310ac83..a4f539aea0 100644 --- a/beacon_node/beacon_processor/src/work_reprocessing_queue.rs +++ b/beacon_node/beacon_processor/src/work_reprocessing_queue.rs @@ -16,8 +16,8 @@ use fnv::FnvHashMap; use futures::task::Poll; use futures::{Stream, StreamExt}; use itertools::Itertools; +use logging::crit; use logging::TimeLatch; -use slog::{crit, debug, error, trace, warn, Logger}; use slot_clock::SlotClock; use std::collections::{HashMap, HashSet}; use std::future::Future; @@ -29,6 +29,7 @@ use strum::AsRefStr; use task_executor::TaskExecutor; use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio_util::time::delay_queue::{DelayQueue, Key as DelayKey}; +use tracing::{debug, error, trace, warn}; use types::{EthSpec, Hash256, Slot}; const TASK_NAME: &str = "beacon_processor_reprocess_queue"; @@ -374,7 +375,6 @@ pub fn spawn_reprocess_scheduler( work_reprocessing_rx: Receiver, executor: &TaskExecutor, slot_clock: Arc, - log: Logger, maximum_gossip_clock_disparity: Duration, ) -> Result<(), String> { // Sanity check @@ -386,14 +386,10 @@ pub fn spawn_reprocess_scheduler( executor.spawn( async move { while let Some(msg) = queue.next().await { - queue.handle_message(msg, &log); + queue.handle_message(msg); } - debug!( - log, - "Re-process queue stopped"; - "msg" => "shutting down" - ); + debug!(msg = "shutting down", "Re-process queue stopped"); }, TASK_NAME, ); @@ -436,7 +432,7 @@ impl ReprocessQueue { } } - fn handle_message(&mut self, msg: InboundEvent, log: &Logger) { + fn handle_message(&mut self, msg: InboundEvent) { use ReprocessQueueMessage::*; match msg { // Some block has been indicated as "early" and should be processed when the @@ -455,10 +451,9 @@ impl ReprocessQueue { if self.queued_gossip_block_roots.len() >= MAXIMUM_QUEUED_BLOCKS { if self.early_block_debounce.elapsed() { warn!( - log, - "Early blocks queue is full"; - "queue_size" => MAXIMUM_QUEUED_BLOCKS, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_BLOCKS, + msg = "check system clock", + "Early blocks queue is full" ); } // Drop the block. @@ -490,10 +485,7 @@ impl ReprocessQueue { .try_send(ReadyWork::Block(early_block)) .is_err() { - error!( - log, - "Failed to send block"; - ); + error!("Failed to send block"); } } } @@ -507,10 +499,9 @@ impl ReprocessQueue { if self.rpc_block_delay_queue.len() >= MAXIMUM_QUEUED_BLOCKS { if self.rpc_block_debounce.elapsed() { warn!( - log, - "RPC blocks queue is full"; - "queue_size" => MAXIMUM_QUEUED_BLOCKS, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_BLOCKS, + msg = "check system clock", + "RPC blocks queue is full" ); } // Return the block to the beacon processor signalling to @@ -522,10 +513,7 @@ impl ReprocessQueue { })) .is_err() { - error!( - log, - "Failed to send rpc block to beacon processor"; - ); + error!("Failed to send rpc block to beacon processor"); } return; } @@ -536,29 +524,24 @@ impl ReprocessQueue { } InboundEvent::ReadyRpcBlock(queued_rpc_block) => { debug!( - log, - "Sending rpc block for reprocessing"; - "block_root" => %queued_rpc_block.beacon_block_root + %queued_rpc_block.beacon_block_root, + "Sending rpc block for reprocessing" ); if self .ready_work_tx .try_send(ReadyWork::RpcBlock(queued_rpc_block)) .is_err() { - error!( - log, - "Failed to send rpc block to beacon processor"; - ); + error!("Failed to send rpc block to beacon processor"); } } InboundEvent::Msg(UnknownBlockAggregate(queued_aggregate)) => { if self.attestations_delay_queue.len() >= MAXIMUM_QUEUED_ATTESTATIONS { if self.attestation_delay_debounce.elapsed() { error!( - log, - "Aggregate attestation delay queue is full"; - "queue_size" => MAXIMUM_QUEUED_ATTESTATIONS, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_ATTESTATIONS, + msg = "check system clock", + "Aggregate attestation delay queue is full" ); } // Drop the attestation. @@ -588,10 +571,9 @@ impl ReprocessQueue { if self.attestations_delay_queue.len() >= MAXIMUM_QUEUED_ATTESTATIONS { if self.attestation_delay_debounce.elapsed() { error!( - log, - "Attestation delay queue is full"; - "queue_size" => MAXIMUM_QUEUED_ATTESTATIONS, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_ATTESTATIONS, + msg = "check system clock", + "Attestation delay queue is full" ); } // Drop the attestation. @@ -623,10 +605,9 @@ impl ReprocessQueue { if self.lc_updates_delay_queue.len() >= MAXIMUM_QUEUED_LIGHT_CLIENT_UPDATES { if self.lc_update_delay_debounce.elapsed() { error!( - log, - "Light client updates delay queue is full"; - "queue_size" => MAXIMUM_QUEUED_LIGHT_CLIENT_UPDATES, - "msg" => "check system clock" + queue_size = MAXIMUM_QUEUED_LIGHT_CLIENT_UPDATES, + msg = "check system clock", + "Light client updates delay queue is full" ); } // Drop the light client update. @@ -658,9 +639,8 @@ impl ReprocessQueue { if self.sampling_requests_delay_queue.len() >= MAXIMUM_QUEUED_SAMPLING_REQUESTS { if self.sampling_request_delay_debounce.elapsed() { error!( - log, - "Sampling requests delay queue is full"; - "queue_size" => MAXIMUM_QUEUED_SAMPLING_REQUESTS, + queue_size = MAXIMUM_QUEUED_SAMPLING_REQUESTS, + "Sampling requests delay queue is full" ); } // Drop the inbound message. @@ -724,23 +704,21 @@ impl ReprocessQueue { // There is a mismatch between the attestation ids registered for this // root and the queued attestations. This should never happen. error!( - log, - "Unknown queued attestation for block root"; - "block_root" => ?block_root, - "att_id" => ?id, + ?block_root, + att_id = ?id, + "Unknown queued attestation for block root" ); } } if failed_to_send_count > 0 { error!( - log, - "Ignored scheduled attestation(s) for block"; - "hint" => "system may be overloaded", - "parent_root" => ?parent_root, - "block_root" => ?block_root, - "failed_count" => failed_to_send_count, - "sent_count" => sent_count, + hint = "system may be overloaded", + ?parent_root, + ?block_root, + failed_count = failed_to_send_count, + sent_count, + "Ignored scheduled attestation(s) for block" ); } } @@ -772,18 +750,17 @@ impl ReprocessQueue { } } else { // This should never happen. - error!(log, "Unknown sampling request for block root"; "block_root" => ?block_root, "id" => ?id); + error!(?block_root, ?id, "Unknown sampling request for block root"); } } if failed_to_send_count > 0 { error!( - log, - "Ignored scheduled sampling requests for block"; - "hint" => "system may be overloaded", - "block_root" => ?block_root, - "failed_count" => failed_to_send_count, - "sent_count" => sent_count, + hint = "system may be overloaded", + ?block_root, + failed_to_send_count, + sent_count, + "Ignored scheduled sampling requests for block" ); } } @@ -795,10 +772,9 @@ impl ReprocessQueue { .remove(&parent_root) { debug!( - log, - "Dequeuing light client optimistic updates"; - "parent_root" => %parent_root, - "count" => queued_lc_id.len(), + %parent_root, + count = queued_lc_id.len(), + "Dequeuing light client optimistic updates" ); for lc_id in queued_lc_id { @@ -818,23 +794,16 @@ impl ReprocessQueue { // Send the work match self.ready_work_tx.try_send(work) { - Ok(_) => trace!( - log, - "reprocessing light client update sent"; - ), - Err(_) => error!( - log, - "Failed to send scheduled light client update"; - ), + Ok(_) => trace!("reprocessing light client update sent"), + Err(_) => error!("Failed to send scheduled light client update"), } } else { // There is a mismatch between the light client update ids registered for this // root and the queued light client updates. This should never happen. error!( - log, - "Unknown queued light client update for parent root"; - "parent_root" => ?parent_root, - "lc_id" => ?lc_id, + ?parent_root, + ?lc_id, + "Unknown queued light client update for parent root" ); } } @@ -855,11 +824,7 @@ impl ReprocessQueue { if !self.queued_gossip_block_roots.remove(&block_root) { // Log an error to alert that we've made a bad assumption about how this // program works, but still process the block anyway. - error!( - log, - "Unknown block in delay queue"; - "block_root" => ?block_root - ); + error!(?block_root, "Unknown block in delay queue"); } if self @@ -867,10 +832,7 @@ impl ReprocessQueue { .try_send(ReadyWork::Block(ready_block)) .is_err() { - error!( - log, - "Failed to pop queued block"; - ); + error!("Failed to pop queued block"); } } InboundEvent::ReadyAttestation(queued_id) => { @@ -901,10 +863,9 @@ impl ReprocessQueue { } { if self.ready_work_tx.try_send(work).is_err() { error!( - log, - "Ignored scheduled attestation"; - "hint" => "system may be overloaded", - "beacon_block_root" => ?root + hint = "system may be overloaded", + beacon_block_root = ?root, + "Ignored scheduled attestation" ); } @@ -929,10 +890,7 @@ impl ReprocessQueue { }, ) { if self.ready_work_tx.try_send(work).is_err() { - error!( - log, - "Failed to send scheduled light client optimistic update"; - ); + error!("Failed to send scheduled light client optimistic update"); } if let Some(queued_lc_updates) = self @@ -955,11 +913,7 @@ impl ReprocessQueue { duration.as_millis().to_string() }); - debug!( - log, - "Sending scheduled backfill work"; - "millis_from_slot_start" => millis_from_slot_start - ); + debug!(%millis_from_slot_start, "Sending scheduled backfill work"); match self .ready_work_tx @@ -971,9 +925,8 @@ impl ReprocessQueue { Err(mpsc::error::TrySendError::Full(ReadyWork::BackfillSync(batch))) | Err(mpsc::error::TrySendError::Closed(ReadyWork::BackfillSync(batch))) => { error!( - log, - "Failed to send scheduled backfill work"; - "info" => "sending work back to queue" + info = "sending work back to queue", + "Failed to send scheduled backfill work" ); self.queued_backfill_batches.insert(0, batch); @@ -984,10 +937,7 @@ impl ReprocessQueue { } // The message was not sent and we didn't get the correct // return result. This is a logic error. - _ => crit!( - log, - "Unexpected return from try_send error"; - ), + _ => crit!("Unexpected return from try_send error"), } } } @@ -1057,7 +1007,7 @@ impl ReprocessQueue { #[cfg(test)] mod tests { use super::*; - use logging::test_logger; + use logging::create_test_tracing_subscriber; use slot_clock::{ManualSlotClock, TestingSlotClock}; use std::ops::Add; use std::sync::Arc; @@ -1105,8 +1055,8 @@ mod tests { // See: https://github.com/sigp/lighthouse/issues/5504#issuecomment-2050930045 #[tokio::test] async fn backfill_schedule_failed_should_reschedule() { + create_test_tracing_subscriber(); let runtime = TestRuntime::default(); - let log = test_logger(); let (work_reprocessing_tx, work_reprocessing_rx) = mpsc::channel(1); let (ready_work_tx, mut ready_work_rx) = mpsc::channel(1); let slot_duration = 12; @@ -1117,7 +1067,6 @@ mod tests { work_reprocessing_rx, &runtime.task_executor, slot_clock.clone(), - log, Duration::from_millis(500), ) .unwrap(); diff --git a/beacon_node/builder_client/src/lib.rs b/beacon_node/builder_client/src/lib.rs index 5f64ac7e43..6d82542cef 100644 --- a/beacon_node/builder_client/src/lib.rs +++ b/beacon_node/builder_client/src/lib.rs @@ -29,6 +29,11 @@ pub const DEFAULT_GET_HEADER_TIMEOUT_MILLIS: u64 = 1000; /// Default user agent for HTTP requests. pub const DEFAULT_USER_AGENT: &str = lighthouse_version::VERSION; +/// The value we set on the `ACCEPT` http header to indicate a preference for ssz response. +pub const PREFERENCE_ACCEPT_VALUE: &str = "application/octet-stream;q=1.0,application/json;q=0.9"; +/// Only accept json responses. +pub const JSON_ACCEPT_VALUE: &str = "application/json"; + #[derive(Clone)] pub struct Timeouts { get_header: Duration, @@ -57,7 +62,12 @@ pub struct BuilderHttpClient { server: SensitiveUrl, timeouts: Timeouts, user_agent: String, - ssz_enabled: Arc, + /// Only use json for all requests/responses types. + disable_ssz: bool, + /// Indicates that the `get_header` response had content-type ssz + /// so we can set content-type header to ssz to make the `submit_blinded_blocks` + /// request. + ssz_available: Arc, } impl BuilderHttpClient { @@ -65,6 +75,7 @@ impl BuilderHttpClient { server: SensitiveUrl, user_agent: Option, builder_header_timeout: Option, + disable_ssz: bool, ) -> Result { let user_agent = user_agent.unwrap_or(DEFAULT_USER_AGENT.to_string()); let client = reqwest::Client::builder().user_agent(&user_agent).build()?; @@ -73,7 +84,8 @@ impl BuilderHttpClient { server, timeouts: Timeouts::new(builder_header_timeout), user_agent, - ssz_enabled: Arc::new(false.into()), + disable_ssz, + ssz_available: Arc::new(false.into()), }) } @@ -124,7 +136,7 @@ impl BuilderHttpClient { let Ok(Some(fork_name)) = self.fork_name_from_header(&headers) else { // if no fork version specified, attempt to fallback to JSON - self.ssz_enabled.store(false, Ordering::SeqCst); + self.ssz_available.store(false, Ordering::SeqCst); return serde_json::from_slice(&response_bytes).map_err(Error::InvalidJson); }; @@ -132,7 +144,7 @@ impl BuilderHttpClient { match content_type { ContentType::Ssz => { - self.ssz_enabled.store(true, Ordering::SeqCst); + self.ssz_available.store(true, Ordering::SeqCst); T::from_ssz_bytes_by_fork(&response_bytes, fork_name) .map(|data| ForkVersionedResponse { version: Some(fork_name), @@ -142,15 +154,17 @@ impl BuilderHttpClient { .map_err(Error::InvalidSsz) } ContentType::Json => { - self.ssz_enabled.store(false, Ordering::SeqCst); + self.ssz_available.store(false, Ordering::SeqCst); serde_json::from_slice(&response_bytes).map_err(Error::InvalidJson) } } } /// Return `true` if the most recently received response from the builder had SSZ Content-Type. - pub fn is_ssz_enabled(&self) -> bool { - self.ssz_enabled.load(Ordering::SeqCst) + /// Return `false` otherwise. + /// Also returns `false` if we have explicitly disabled ssz. + pub fn is_ssz_available(&self) -> bool { + !self.disable_ssz && self.ssz_available.load(Ordering::SeqCst) } async fn get_with_timeout( @@ -213,7 +227,7 @@ impl BuilderHttpClient { &self, url: U, ssz_body: Vec, - mut headers: HeaderMap, + headers: HeaderMap, timeout: Option, ) -> Result { let mut builder = self.client.post(url); @@ -221,11 +235,6 @@ impl BuilderHttpClient { builder = builder.timeout(timeout); } - headers.insert( - CONTENT_TYPE_HEADER, - HeaderValue::from_static(SSZ_CONTENT_TYPE_HEADER), - ); - let response = builder .headers(headers) .body(ssz_body) @@ -292,9 +301,21 @@ impl BuilderHttpClient { .push("blinded_blocks"); let mut headers = HeaderMap::new(); - if let Ok(value) = HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) { - headers.insert(CONSENSUS_VERSION_HEADER, value); - } + headers.insert( + CONSENSUS_VERSION_HEADER, + HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + CONTENT_TYPE_HEADER, + HeaderValue::from_str(SSZ_CONTENT_TYPE_HEADER) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + ACCEPT, + HeaderValue::from_str(PREFERENCE_ACCEPT_VALUE) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); let result = self .post_ssz_with_raw_response( @@ -326,9 +347,21 @@ impl BuilderHttpClient { .push("blinded_blocks"); let mut headers = HeaderMap::new(); - if let Ok(value) = HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) { - headers.insert(CONSENSUS_VERSION_HEADER, value); - } + headers.insert( + CONSENSUS_VERSION_HEADER, + HeaderValue::from_str(&blinded_block.fork_name_unchecked().to_string()) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + CONTENT_TYPE_HEADER, + HeaderValue::from_str(JSON_CONTENT_TYPE_HEADER) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + headers.insert( + ACCEPT, + HeaderValue::from_str(JSON_ACCEPT_VALUE) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); Ok(self .post_with_raw_response( @@ -362,12 +395,20 @@ impl BuilderHttpClient { .push(pubkey.as_hex_string().as_str()); let mut headers = HeaderMap::new(); - if let Ok(ssz_content_type_header) = HeaderValue::from_str(&format!( - "{}; q=1.0,{}; q=0.9", - SSZ_CONTENT_TYPE_HEADER, JSON_CONTENT_TYPE_HEADER - )) { - headers.insert(ACCEPT, ssz_content_type_header); - }; + if self.disable_ssz { + headers.insert( + ACCEPT, + HeaderValue::from_str(JSON_CONTENT_TYPE_HEADER) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + } else { + // Indicate preference for ssz response in the accept header + headers.insert( + ACCEPT, + HeaderValue::from_str(PREFERENCE_ACCEPT_VALUE) + .map_err(|e| Error::InvalidHeaders(format!("{}", e)))?, + ); + } let resp = self .get_with_header(path, self.timeouts.get_header, headers) @@ -395,3 +436,18 @@ impl BuilderHttpClient { .await } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_headers_no_panic() { + for fork in ForkName::list_all() { + assert!(HeaderValue::from_str(&fork.to_string()).is_ok()); + } + assert!(HeaderValue::from_str(PREFERENCE_ACCEPT_VALUE).is_ok()); + assert!(HeaderValue::from_str(JSON_ACCEPT_VALUE).is_ok()); + assert!(HeaderValue::from_str(JSON_CONTENT_TYPE_HEADER).is_ok()); + } +} diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 614115eb58..e11fc23072 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -27,6 +27,7 @@ http_api = { workspace = true } http_metrics = { path = "../http_metrics" } kzg = { workspace = true } lighthouse_network = { workspace = true } +logging = { workspace = true } metrics = { workspace = true } monitoring_api = { workspace = true } network = { workspace = true } @@ -35,11 +36,12 @@ serde = { workspace = true } serde_json = { workspace = true } slasher = { workspace = true } slasher_service = { path = "../../slasher/service" } -slog = { workspace = true } slot_clock = { workspace = true } store = { workspace = true } task_executor = { workspace = true } time = "0.3.5" timer = { path = "../timer" } tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index e3bfd60a48..c8ff6521c8 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -35,7 +35,6 @@ use monitoring_api::{MonitoringHttpClient, ProcessType}; use network::{NetworkConfig, NetworkSenders, NetworkService}; use slasher::Slasher; use slasher_service::SlasherService; -use slog::{debug, info, warn, Logger}; use std::net::TcpListener; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -44,6 +43,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use store::database::interface::BeaconNodeBackend; use timer::spawn_timer; use tokio::sync::oneshot; +use tracing::{debug, info, warn}; use types::{ test_utils::generate_deterministic_keypairs, BeaconState, BlobSidecarList, ChainSpec, EthSpec, ExecutionBlockHash, Hash256, SignedBeaconBlock, @@ -170,11 +170,9 @@ where let runtime_context = runtime_context.ok_or("beacon_chain_start_method requires a runtime context")?; let context = runtime_context.service_context("beacon".into()); - let log = context.log(); let spec = chain_spec.ok_or("beacon_chain_start_method requires a chain spec")?; let event_handler = if self.http_api_config.enabled { Some(ServerSentEventHandler::new( - context.log().clone(), self.http_api_config.sse_capacity_multiplier, )) } else { @@ -183,12 +181,8 @@ where let execution_layer = if let Some(config) = config.execution_layer.clone() { let context = runtime_context.service_context("exec".into()); - let execution_layer = ExecutionLayer::from_config( - config, - context.executor.clone(), - context.log().clone(), - ) - .map_err(|e| format!("unable to start execution layer endpoints: {:?}", e))?; + let execution_layer = ExecutionLayer::from_config(config, context.executor.clone()) + .map_err(|e| format!("unable to start execution layer endpoints: {:?}", e))?; Some(execution_layer) } else { None @@ -205,7 +199,6 @@ where }; let builder = BeaconChainBuilder::new(eth_spec_instance, Arc::new(kzg)) - .logger(context.log().clone()) .store(store) .task_executor(context.executor.clone()) .custom_spec(spec.clone()) @@ -245,7 +238,7 @@ where // using it. let client_genesis = if matches!(client_genesis, ClientGenesis::FromStore) && !chain_exists { - info!(context.log(), "Defaulting to deposit contract genesis"); + info!("Defaulting to deposit contract genesis"); ClientGenesis::DepositContract } else if chain_exists { @@ -253,9 +246,8 @@ where || matches!(client_genesis, ClientGenesis::CheckpointSyncUrl { .. }) { info!( - context.log(), - "Refusing to checkpoint sync"; - "msg" => "database already exists, use --purge-db to force checkpoint sync" + msg = "database already exists, use --purge-db to force checkpoint sync", + "Refusing to checkpoint sync" ); } @@ -295,12 +287,9 @@ where builder.genesis_state(genesis_state).map(|v| (v, None))? } ClientGenesis::GenesisState => { - info!( - context.log(), - "Starting from known genesis state"; - ); + info!("Starting from known genesis state"); - let genesis_state = genesis_state(&runtime_context, &config, log).await?; + let genesis_state = genesis_state(&runtime_context, &config).await?; // If the user has not explicitly allowed genesis sync, prevent // them from trying to sync from genesis if we're outside of the @@ -348,12 +337,9 @@ where anchor_block_bytes, anchor_blobs_bytes, } => { - info!(context.log(), "Starting checkpoint sync"); + info!("Starting checkpoint sync"); if config.chain.genesis_backfill { - info!( - context.log(), - "Blocks will downloaded all the way back to genesis" - ); + info!("Blocks will downloaded all the way back to genesis"); } let anchor_state = BeaconState::from_ssz_bytes(&anchor_state_bytes, &spec) @@ -371,7 +357,7 @@ where } else { None }; - let genesis_state = genesis_state(&runtime_context, &config, log).await?; + let genesis_state = genesis_state(&runtime_context, &config).await?; builder .weak_subjectivity_state( @@ -384,15 +370,11 @@ where } ClientGenesis::CheckpointSyncUrl { url } => { info!( - context.log(), - "Starting checkpoint sync"; - "remote_url" => %url, + remote_url = %url, + "Starting checkpoint sync" ); if config.chain.genesis_backfill { - info!( - context.log(), - "Blocks will be downloaded all the way back to genesis" - ); + info!("Blocks will be downloaded all the way back to genesis"); } let remote = BeaconNodeHttpClient::new( @@ -406,7 +388,7 @@ where // We want to fetch deposit snapshot before fetching the finalized beacon state to // ensure that the snapshot is not newer than the beacon state that satisfies the // deposit finalization conditions - debug!(context.log(), "Downloading deposit snapshot"); + debug!("Downloading deposit snapshot"); let deposit_snapshot_result = remote .get_deposit_snapshot() .await @@ -423,22 +405,18 @@ where if deposit_snapshot.is_valid() { Some(deposit_snapshot) } else { - warn!(context.log(), "Remote BN sent invalid deposit snapshot!"); + warn!("Remote BN sent invalid deposit snapshot!"); None } } Ok(None) => { - warn!( - context.log(), - "Remote BN does not support EIP-4881 fast deposit sync" - ); + warn!("Remote BN does not support EIP-4881 fast deposit sync"); None } Err(e) => { warn!( - context.log(), - "Remote BN does not support EIP-4881 fast deposit sync"; - "error" => e + error = e, + "Remote BN does not support EIP-4881 fast deposit sync" ); None } @@ -447,21 +425,18 @@ where None }; - debug!( - context.log(), - "Downloading finalized state"; - ); + debug!("Downloading finalized state"); let state = remote .get_debug_beacon_states_ssz::(StateId::Finalized, &spec) .await .map_err(|e| format!("Error loading checkpoint state from remote: {:?}", e))? .ok_or_else(|| "Checkpoint state missing from remote".to_string())?; - debug!(context.log(), "Downloaded finalized state"; "slot" => ?state.slot()); + debug!(slot = ?state.slot(), "Downloaded finalized state"); let finalized_block_slot = state.latest_block_header().slot; - debug!(context.log(), "Downloading finalized block"; "block_slot" => ?finalized_block_slot); + debug!(block_slot = ?finalized_block_slot,"Downloading finalized block"); let block = remote .get_beacon_blocks_ssz::(BlockId::Slot(finalized_block_slot), &spec) .await @@ -476,24 +451,23 @@ where .ok_or("Finalized block missing from remote, it returned 404")?; let block_root = block.canonical_root(); - debug!(context.log(), "Downloaded finalized block"); + debug!("Downloaded finalized block"); let blobs = if block.message().body().has_blobs() { - debug!(context.log(), "Downloading finalized blobs"); + debug!("Downloading finalized blobs"); if let Some(response) = remote .get_blobs::(BlockId::Root(block_root), None) .await .map_err(|e| format!("Error fetching finalized blobs from remote: {e:?}"))? { - debug!(context.log(), "Downloaded finalized blobs"); + debug!("Downloaded finalized blobs"); Some(response.data) } else { warn!( - context.log(), - "Checkpoint server is missing blobs"; - "block_root" => %block_root, - "hint" => "use a different URL or ask the provider to update", - "impact" => "db will be slightly corrupt until these blobs are pruned", + block_root = %block_root, + hint = "use a different URL or ask the provider to update", + impact = "db will be slightly corrupt until these blobs are pruned", + "Checkpoint server is missing blobs" ); None } @@ -501,35 +475,31 @@ where None }; - let genesis_state = genesis_state(&runtime_context, &config, log).await?; + let genesis_state = genesis_state(&runtime_context, &config).await?; info!( - context.log(), - "Loaded checkpoint block and state"; - "block_slot" => block.slot(), - "state_slot" => state.slot(), - "block_root" => ?block_root, + block_slot = %block.slot(), + state_slot = %state.slot(), + block_root = ?block_root, + "Loaded checkpoint block and state" ); let service = deposit_snapshot.and_then(|snapshot| match Eth1Service::from_deposit_snapshot( config.eth1, - context.log().clone(), spec.clone(), &snapshot, ) { Ok(service) => { info!( - context.log(), - "Loaded deposit tree snapshot"; - "deposits loaded" => snapshot.deposit_count, + deposits_loaded = snapshot.deposit_count, + "Loaded deposit tree snapshot" ); Some(service) } Err(e) => { - warn!(context.log(), - "Unable to load deposit snapshot"; - "error" => ?e + warn!(error = ?e, + "Unable to load deposit snapshot" ); None } @@ -541,18 +511,14 @@ where } ClientGenesis::DepositContract => { info!( - context.log(), - "Waiting for eth2 genesis from eth1"; - "eth1_endpoints" => format!("{:?}", &config.eth1.endpoint), - "contract_deploy_block" => config.eth1.deposit_contract_deploy_block, - "deposit_contract" => &config.eth1.deposit_contract_address + eth1_endpoints = ?config.eth1.endpoint, + contract_deploy_block = config.eth1.deposit_contract_deploy_block, + deposit_contract = &config.eth1.deposit_contract_address, + "Waiting for eth2 genesis from eth1" ); - let genesis_service = Eth1GenesisService::new( - config.eth1, - context.log().clone(), - context.eth2_config().spec.clone(), - )?; + let genesis_service = + Eth1GenesisService::new(config.eth1, context.eth2_config().spec.clone())?; // If the HTTP API server is enabled, start an instance of it where it only // contains a reference to the eth1 service (all non-eth1 endpoints will fail @@ -575,7 +541,6 @@ where beacon_processor_send: None, beacon_processor_reprocess_send: None, eth1_service: Some(genesis_service.eth1_service.clone()), - log: context.log().clone(), sse_logging_components: runtime_context.sse_logging_components.clone(), }); @@ -587,10 +552,9 @@ where let (listen_addr, server) = http_api::serve(ctx, exit_future) .map_err(|e| format!("Unable to start HTTP API server: {:?}", e))?; - let log_clone = context.log().clone(); let http_api_task = async move { server.await; - debug!(log_clone, "HTTP API server task ended"); + debug!("HTTP API server task ended"); }; context @@ -617,9 +581,8 @@ where // We will restart it again after we've finished setting up for genesis. while TcpListener::bind(http_listen).is_err() { warn!( - context.log(), - "Waiting for HTTP server port to open"; - "port" => http_listen + port = %http_listen, + "Waiting for HTTP server port to open" ); tokio::time::sleep(Duration::from_secs(1)).await; } @@ -738,7 +701,7 @@ where .as_ref() .ok_or("monitoring_client requires a runtime_context")? .service_context("monitoring_client".into()); - let monitoring_client = MonitoringHttpClient::new(config, context.log().clone())?; + let monitoring_client = MonitoringHttpClient::new(config)?; monitoring_client.auto_update( context.executor, vec![ProcessType::BeaconNode, ProcessType::System], @@ -798,7 +761,6 @@ where .beacon_processor_config .take() .ok_or("build requires a beacon_processor_config")?; - let log = runtime_context.log().clone(); let http_api_listen_addr = if self.http_api_config.enabled { let ctx = Arc::new(http_api::Context { @@ -812,7 +774,6 @@ where beacon_processor_channels.work_reprocessing_tx.clone(), ), sse_logging_components: runtime_context.sse_logging_components.clone(), - log: log.clone(), }); let exit = runtime_context.executor.exit(); @@ -820,10 +781,9 @@ where let (listen_addr, server) = http_api::serve(ctx, exit) .map_err(|e| format!("Unable to start HTTP API server: {:?}", e))?; - let http_log = runtime_context.log().clone(); let http_api_task = async move { server.await; - debug!(http_log, "HTTP API server task ended"); + debug!("HTTP API server task ended"); }; runtime_context @@ -833,7 +793,7 @@ where Some(listen_addr) } else { - info!(log, "HTTP server is disabled"); + info!("HTTP server is disabled"); None }; @@ -844,7 +804,6 @@ where db_path: self.db_path.clone(), freezer_db_path: self.freezer_db_path.clone(), gossipsub_registry: self.libp2p_registry.take().map(std::sync::Mutex::new), - log: log.clone(), }); let exit = runtime_context.executor.exit(); @@ -858,7 +817,7 @@ where Some(listen_addr) } else { - debug!(log, "Metrics server is disabled"); + debug!("Metrics server is disabled"); None }; @@ -874,7 +833,6 @@ where executor: beacon_processor_context.executor.clone(), current_workers: 0, config: beacon_processor_config, - log: beacon_processor_context.log().clone(), } .spawn_manager( beacon_processor_channels.beacon_processor_rx, @@ -895,12 +853,7 @@ where } let state_advance_context = runtime_context.service_context("state_advance".into()); - let state_advance_log = state_advance_context.log().clone(); - spawn_state_advance_timer( - state_advance_context.executor, - beacon_chain.clone(), - state_advance_log, - ); + spawn_state_advance_timer(state_advance_context.executor, beacon_chain.clone()); if let Some(execution_layer) = beacon_chain.execution_layer.as_ref() { // Only send a head update *after* genesis. @@ -929,9 +882,8 @@ where // node comes online. if let Err(e) = result { warn!( - log, - "Failed to update head on execution engines"; - "error" => ?e + error = ?e, + "Failed to update head on execution engines" ); } }, @@ -954,14 +906,12 @@ where let inner_chain = beacon_chain.clone(); let light_client_update_context = runtime_context.service_context("lc_update".to_string()); - let log = light_client_update_context.log().clone(); light_client_update_context.executor.spawn( async move { compute_light_client_updates( &inner_chain, light_client_server_rv, beacon_processor_channels.work_reprocessing_tx, - &log, ) .await }, @@ -1044,7 +994,6 @@ where cold_path: &Path, blobs_path: &Path, config: StoreConfig, - log: Logger, ) -> Result { let context = self .runtime_context @@ -1073,7 +1022,6 @@ where genesis_state_root, from, to, - log, ) }; @@ -1084,7 +1032,6 @@ where schema_upgrade, config, spec, - context.log().clone(), ) .map_err(|e| format!("Unable to open database: {:?}", e))?; self.store = Some(store); @@ -1132,22 +1079,15 @@ where CachingEth1Backend::from_service(eth1_service_from_genesis) } else if config.purge_cache { - CachingEth1Backend::new(config, context.log().clone(), spec)? + CachingEth1Backend::new(config, spec)? } else { beacon_chain_builder .get_persisted_eth1_backend()? .map(|persisted| { - Eth1Chain::from_ssz_container( - &persisted, - config.clone(), - &context.log().clone(), - spec.clone(), - ) - .map(|chain| chain.into_backend()) + Eth1Chain::from_ssz_container(&persisted, config.clone(), spec.clone()) + .map(|chain| chain.into_backend()) }) - .unwrap_or_else(|| { - CachingEth1Backend::new(config, context.log().clone(), spec.clone()) - })? + .unwrap_or_else(|| CachingEth1Backend::new(config, spec.clone()))? }; self.eth1_service = Some(backend.core.clone()); @@ -1230,7 +1170,6 @@ where async fn genesis_state( context: &RuntimeContext, config: &ClientConfig, - log: &Logger, ) -> Result, String> { let eth2_network_config = context .eth2_network_config @@ -1240,7 +1179,6 @@ async fn genesis_state( .genesis_state::( config.genesis_state_url.as_deref(), config.genesis_state_url_timeout, - log, ) .await? .ok_or_else(|| "Genesis state is unknown".to_string()) diff --git a/beacon_node/client/src/compute_light_client_updates.rs b/beacon_node/client/src/compute_light_client_updates.rs index 1eb977d421..fab284c428 100644 --- a/beacon_node/client/src/compute_light_client_updates.rs +++ b/beacon_node/client/src/compute_light_client_updates.rs @@ -2,8 +2,8 @@ use beacon_chain::{BeaconChain, BeaconChainTypes, LightClientProducerEvent}; use beacon_processor::work_reprocessing_queue::ReprocessQueueMessage; use futures::channel::mpsc::Receiver; use futures::StreamExt; -use slog::{error, Logger}; use tokio::sync::mpsc::Sender; +use tracing::error; // Each `LightClientProducerEvent` is ~200 bytes. With the light_client server producing only recent // updates it is okay to drop some events in case of overloading. In normal network conditions @@ -15,7 +15,6 @@ pub async fn compute_light_client_updates( chain: &BeaconChain, mut light_client_server_rv: Receiver>, reprocess_tx: Sender, - log: &Logger, ) { // Should only receive events for recent blocks, import_block filters by blocks close to clock. // @@ -28,12 +27,12 @@ pub async fn compute_light_client_updates( chain .recompute_and_cache_light_client_updates(event) .unwrap_or_else(|e| { - error!(log, "error computing light_client updates {:?}", e); + error!("error computing light_client updates {:?}", e); }); let msg = ReprocessQueueMessage::NewLightClientOptimisticUpdate { parent_root }; if reprocess_tx.try_send(msg).is_err() { - error!(log, "Failed to inform light client update"; "parent_root" => %parent_root) + error!(%parent_root,"Failed to inform light client update") }; } } diff --git a/beacon_node/client/src/notifier.rs b/beacon_node/client/src/notifier.rs index 0c3b1578d6..d103d48dfb 100644 --- a/beacon_node/client/src/notifier.rs +++ b/beacon_node/client/src/notifier.rs @@ -8,12 +8,13 @@ use beacon_chain::{ BeaconChain, BeaconChainTypes, ExecutionStatus, }; use lighthouse_network::{types::SyncState, NetworkGlobals}; -use slog::{crit, debug, error, info, warn, Logger}; +use logging::crit; use slot_clock::SlotClock; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::Mutex; use tokio::time::sleep; +use tracing::{debug, error, info, warn}; use types::*; /// Create a warning log whenever the peer count is at or below this value. @@ -39,7 +40,6 @@ pub fn spawn_notifier( let slot_duration = Duration::from_secs(seconds_per_slot); let speedo = Mutex::new(Speedo::default()); - let log = executor.log().clone(); // Keep track of sync state and reset the speedo on specific sync state changes. // Specifically, if we switch between a sync and a backfill sync, reset the speedo. @@ -56,15 +56,14 @@ pub fn spawn_notifier( // waiting for genesis. Some(next_slot) if next_slot > slot_duration => { info!( - log, - "Waiting for genesis"; - "peers" => peer_count_pretty(network.connected_peers()), - "wait_time" => estimated_time_pretty(Some(next_slot.as_secs() as f64)), + peers = peer_count_pretty(network.connected_peers()), + wait_time = estimated_time_pretty(Some(next_slot.as_secs() as f64)), + "Waiting for genesis" ); - eth1_logging(&beacon_chain, &log); - bellatrix_readiness_logging(Slot::new(0), &beacon_chain, &log).await; - capella_readiness_logging(Slot::new(0), &beacon_chain, &log).await; - genesis_execution_payload_logging(&beacon_chain, &log).await; + eth1_logging(&beacon_chain); + bellatrix_readiness_logging(Slot::new(0), &beacon_chain).await; + capella_readiness_logging(Slot::new(0), &beacon_chain).await; + genesis_execution_payload_logging(&beacon_chain).await; sleep(slot_duration).await; } _ => break, @@ -82,7 +81,7 @@ pub fn spawn_notifier( let wait = match beacon_chain.slot_clock.duration_to_next_slot() { Some(duration) => duration + slot_duration / 2, None => { - warn!(log, "Unable to read current slot"); + warn!("Unable to read current slot"); sleep(slot_duration).await; continue; } @@ -120,11 +119,7 @@ pub fn spawn_notifier( let current_slot = match beacon_chain.slot() { Ok(slot) => slot, Err(e) => { - error!( - log, - "Unable to read current slot"; - "error" => format!("{:?}", e) - ); + error!(error = ?e, "Unable to read current slot"); break; } }; @@ -168,26 +163,28 @@ pub fn spawn_notifier( ); if connected_peer_count <= WARN_PEER_COUNT { - warn!(log, "Low peer count"; "peer_count" => peer_count_pretty(connected_peer_count)); + warn!( + peer_count = peer_count_pretty(connected_peer_count), + "Low peer count" + ); } debug!( - log, - "Slot timer"; - "peers" => peer_count_pretty(connected_peer_count), - "finalized_root" => format!("{}", finalized_checkpoint.root), - "finalized_epoch" => finalized_checkpoint.epoch, - "head_block" => format!("{}", head_root), - "head_slot" => head_slot, - "current_slot" => current_slot, - "sync_state" =>format!("{}", current_sync_state) + peers = peer_count_pretty(connected_peer_count), + finalized_root = %finalized_checkpoint.root, + finalized_epoch = %finalized_checkpoint.epoch, + head_block = %head_root, + %head_slot, + %current_slot, + sync_state = %current_sync_state, + "Slot timer" ); // Log if we are backfilling. let is_backfilling = matches!(current_sync_state, SyncState::BackFillSyncing { .. }); if is_backfilling && last_backfill_log_slot - .map_or(true, |slot| slot + BACKFILL_LOG_INTERVAL <= current_slot) + .is_none_or(|slot| slot + BACKFILL_LOG_INTERVAL <= current_slot) { last_backfill_log_slot = Some(current_slot); @@ -202,26 +199,31 @@ pub fn spawn_notifier( if display_speed { info!( - log, - "Downloading historical blocks"; - "distance" => distance, - "speed" => sync_speed_pretty(speed), - "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(original_oldest_block_slot.saturating_sub(beacon_chain.genesis_backfill_slot))), + distance, + speed = sync_speed_pretty(speed), + est_time = estimated_time_pretty( + speedo.estimated_time_till_slot( + original_oldest_block_slot + .saturating_sub(beacon_chain.genesis_backfill_slot) + ) + ), + "Downloading historical blocks" ); } else { info!( - log, - "Downloading historical blocks"; - "distance" => distance, - "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(original_oldest_block_slot.saturating_sub(beacon_chain.genesis_backfill_slot))), + distance, + est_time = estimated_time_pretty( + speedo.estimated_time_till_slot( + original_oldest_block_slot + .saturating_sub(beacon_chain.genesis_backfill_slot) + ) + ), + "Downloading historical blocks" ); } } else if !is_backfilling && last_backfill_log_slot.is_some() { last_backfill_log_slot = None; - info!( - log, - "Historical block download complete"; - ); + info!("Historical block download complete"); } // Log if we are syncing @@ -238,20 +240,20 @@ pub fn spawn_notifier( if display_speed { info!( - log, - "Syncing"; - "peers" => peer_count_pretty(connected_peer_count), - "distance" => distance, - "speed" => sync_speed_pretty(speed), - "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)), + peers = peer_count_pretty(connected_peer_count), + distance, + speed = sync_speed_pretty(speed), + est_time = + estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)), + "Syncing" ); } else { info!( - log, - "Syncing"; - "peers" => peer_count_pretty(connected_peer_count), - "distance" => distance, - "est_time" => estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)), + peers = peer_count_pretty(connected_peer_count), + distance, + est_time = + estimated_time_pretty(speedo.estimated_time_till_slot(current_slot)), + "Syncing" ); } } else if current_sync_state.is_synced() { @@ -267,20 +269,18 @@ pub fn spawn_notifier( Ok(ExecutionStatus::Valid(hash)) => format!("{} (verified)", hash), Ok(ExecutionStatus::Optimistic(hash)) => { warn!( - log, - "Head is optimistic"; - "info" => "chain not fully verified, \ - block and attestation production disabled until execution engine syncs", - "execution_block_hash" => ?hash, + info = "chain not fully verified, \ + block and attestation production disabled until execution engine syncs", + execution_block_hash = ?hash, + "Head is optimistic" ); format!("{} (unverified)", hash) } Ok(ExecutionStatus::Invalid(hash)) => { crit!( - log, - "Head execution payload is invalid"; - "msg" => "this scenario may be unrecoverable", - "execution_block_hash" => ?hash, + msg = "this scenario may be unrecoverable", + execution_block_hash = ?hash, + "Head execution payload is invalid" ); format!("{} (invalid)", hash) } @@ -288,35 +288,33 @@ pub fn spawn_notifier( }; info!( - log, - "Synced"; - "peers" => peer_count_pretty(connected_peer_count), - "exec_hash" => block_hash, - "finalized_root" => format!("{}", finalized_checkpoint.root), - "finalized_epoch" => finalized_checkpoint.epoch, - "epoch" => current_epoch, - "block" => block_info, - "slot" => current_slot, + peers = peer_count_pretty(connected_peer_count), + exec_hash = block_hash, + finalized_root = %finalized_checkpoint.root, + finalized_epoch = %finalized_checkpoint.epoch, + epoch = %current_epoch, + block = block_info, + slot = %current_slot, + "Synced" ); } else { metrics::set_gauge(&metrics::IS_SYNCED, 0); info!( - log, - "Searching for peers"; - "peers" => peer_count_pretty(connected_peer_count), - "finalized_root" => format!("{}", finalized_checkpoint.root), - "finalized_epoch" => finalized_checkpoint.epoch, - "head_slot" => head_slot, - "current_slot" => current_slot, + peers = peer_count_pretty(connected_peer_count), + finalized_root = %finalized_checkpoint.root, + finalized_epoch = %finalized_checkpoint.epoch, + %head_slot, + %current_slot, + "Searching for peers" ); } - eth1_logging(&beacon_chain, &log); - bellatrix_readiness_logging(current_slot, &beacon_chain, &log).await; - capella_readiness_logging(current_slot, &beacon_chain, &log).await; - deneb_readiness_logging(current_slot, &beacon_chain, &log).await; - electra_readiness_logging(current_slot, &beacon_chain, &log).await; - fulu_readiness_logging(current_slot, &beacon_chain, &log).await; + eth1_logging(&beacon_chain); + bellatrix_readiness_logging(current_slot, &beacon_chain).await; + capella_readiness_logging(current_slot, &beacon_chain).await; + deneb_readiness_logging(current_slot, &beacon_chain).await; + electra_readiness_logging(current_slot, &beacon_chain).await; + fulu_readiness_logging(current_slot, &beacon_chain).await; } }; @@ -331,7 +329,6 @@ pub fn spawn_notifier( async fn bellatrix_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let merge_completed = beacon_chain .canonical_head @@ -355,10 +352,9 @@ async fn bellatrix_readiness_logging( // Logging of the EE being offline is handled in the other readiness logging functions. if !beacon_chain.is_time_to_prepare_for_capella(current_slot) { error!( - log, - "Execution endpoint required"; - "info" => "you need an execution engine to validate blocks, see: \ - https://lighthouse-book.sigmaprime.io/merge-migration.html" + info = "you need an execution engine to validate blocks, see: \ + https://lighthouse-book.sigmaprime.io/merge-migration.html", + "Execution endpoint required" ); } return; @@ -375,12 +371,11 @@ async fn bellatrix_readiness_logging( terminal_block_hash_epoch: None, } => { info!( - log, - "Ready for Bellatrix"; - "terminal_total_difficulty" => %ttd, - "current_difficulty" => current_difficulty + terminal_total_difficulty = %ttd, + current_difficulty = current_difficulty .map(|d| d.to_string()) .unwrap_or_else(|| "??".into()), + "Ready for Bellatrix" ) } MergeConfig { @@ -389,29 +384,25 @@ async fn bellatrix_readiness_logging( terminal_block_hash_epoch: Some(terminal_block_hash_epoch), } => { info!( - log, - "Ready for Bellatrix"; - "info" => "you are using override parameters, please ensure that you \ - understand these parameters and their implications.", - "terminal_block_hash" => ?terminal_block_hash, - "terminal_block_hash_epoch" => ?terminal_block_hash_epoch, + info = "you are using override parameters, please ensure that you \ + understand these parameters and their implications.", + ?terminal_block_hash, + ?terminal_block_hash_epoch, + "Ready for Bellatrix" ) } other => error!( - log, - "Inconsistent merge configuration"; - "config" => ?other + config = ?other, + "Inconsistent merge configuration" ), }, readiness @ BellatrixReadiness::NotSynced => warn!( - log, - "Not ready Bellatrix"; - "info" => %readiness, + info = %readiness, + "Not ready Bellatrix" ), readiness @ BellatrixReadiness::NoExecutionEndpoint => warn!( - log, - "Not ready for Bellatrix"; - "info" => %readiness, + info = %readiness, + "Not ready for Bellatrix" ), } } @@ -420,7 +411,6 @@ async fn bellatrix_readiness_logging( async fn capella_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let capella_completed = beacon_chain .canonical_head @@ -442,10 +432,9 @@ async fn capella_readiness_logging( // Logging of the EE being offline is handled in the other readiness logging functions. if !beacon_chain.is_time_to_prepare_for_deneb(current_slot) { error!( - log, - "Execution endpoint required"; - "info" => "you need a Capella enabled execution engine to validate blocks, see: \ - https://lighthouse-book.sigmaprime.io/merge-migration.html" + info = "you need a Capella enabled execution engine to validate blocks, see: \ + https://lighthouse-book.sigmaprime.io/merge-migration.html", + "Execution endpoint required" ); } return; @@ -454,24 +443,21 @@ async fn capella_readiness_logging( match beacon_chain.check_capella_readiness().await { CapellaReadiness::Ready => { info!( - log, - "Ready for Capella"; - "info" => "ensure the execution endpoint is updated to the latest Capella/Shanghai release" + info = "ensure the execution endpoint is updated to the latest Capella/Shanghai release", + "Ready for Capella" ) } readiness @ CapellaReadiness::ExchangeCapabilitiesFailed { error: _ } => { error!( - log, - "Not ready for Capella"; - "hint" => "the execution endpoint may be offline", - "info" => %readiness, + hint = "the execution endpoint may be offline", + info = %readiness, + "Not ready for Capella" ) } readiness => warn!( - log, - "Not ready for Capella"; - "hint" => "try updating the execution endpoint", - "info" => %readiness, + hint = "try updating the execution endpoint", + info = %readiness, + "Not ready for Capella" ), } } @@ -480,7 +466,6 @@ async fn capella_readiness_logging( async fn deneb_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let deneb_completed = beacon_chain .canonical_head @@ -500,9 +485,8 @@ async fn deneb_readiness_logging( if deneb_completed && !has_execution_layer { error!( - log, - "Execution endpoint required"; - "info" => "you need a Deneb enabled execution engine to validate blocks." + info = "you need a Deneb enabled execution engine to validate blocks.", + "Execution endpoint required" ); return; } @@ -510,24 +494,22 @@ async fn deneb_readiness_logging( match beacon_chain.check_deneb_readiness().await { DenebReadiness::Ready => { info!( - log, - "Ready for Deneb"; - "info" => "ensure the execution endpoint is updated to the latest Deneb/Cancun release" + info = + "ensure the execution endpoint is updated to the latest Deneb/Cancun release", + "Ready for Deneb" ) } readiness @ DenebReadiness::ExchangeCapabilitiesFailed { error: _ } => { error!( - log, - "Not ready for Deneb"; - "hint" => "the execution endpoint may be offline", - "info" => %readiness, + hint = "the execution endpoint may be offline", + info = %readiness, + "Not ready for Deneb" ) } readiness => warn!( - log, - "Not ready for Deneb"; - "hint" => "try updating the execution endpoint", - "info" => %readiness, + hint = "try updating the execution endpoint", + info = %readiness, + "Not ready for Deneb" ), } } @@ -535,7 +517,6 @@ async fn deneb_readiness_logging( async fn electra_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let electra_completed = beacon_chain .canonical_head @@ -556,9 +537,8 @@ async fn electra_readiness_logging( if electra_completed && !has_execution_layer { // When adding a new fork, add a check for the next fork readiness here. error!( - log, - "Execution endpoint required"; - "info" => "you need a Electra enabled execution engine to validate blocks." + info = "you need a Electra enabled execution engine to validate blocks.", + "Execution endpoint required" ); return; } @@ -566,24 +546,22 @@ async fn electra_readiness_logging( match beacon_chain.check_electra_readiness().await { ElectraReadiness::Ready => { info!( - log, - "Ready for Electra"; - "info" => "ensure the execution endpoint is updated to the latest Electra/Prague release" + info = + "ensure the execution endpoint is updated to the latest Electra/Prague release", + "Ready for Electra" ) } readiness @ ElectraReadiness::ExchangeCapabilitiesFailed { error: _ } => { error!( - log, - "Not ready for Electra"; - "hint" => "the execution endpoint may be offline", - "info" => %readiness, + hint = "the execution endpoint may be offline", + info = %readiness, + "Not ready for Electra" ) } readiness => warn!( - log, - "Not ready for Electra"; - "hint" => "try updating the execution endpoint", - "info" => %readiness, + hint = "try updating the execution endpoint", + info = %readiness, + "Not ready for Electra" ), } } @@ -592,7 +570,6 @@ async fn electra_readiness_logging( async fn fulu_readiness_logging( current_slot: Slot, beacon_chain: &BeaconChain, - log: &Logger, ) { let fulu_completed = beacon_chain .canonical_head @@ -612,9 +589,8 @@ async fn fulu_readiness_logging( if fulu_completed && !has_execution_layer { error!( - log, - "Execution endpoint required"; - "info" => "you need a Fulu enabled execution engine to validate blocks." + info = "you need a Fulu enabled execution engine to validate blocks.", + "Execution endpoint required" ); return; } @@ -622,102 +598,86 @@ async fn fulu_readiness_logging( match beacon_chain.check_fulu_readiness().await { FuluReadiness::Ready => { info!( - log, - "Ready for Fulu"; - "info" => "ensure the execution endpoint is updated to the latest Fulu release" + info = "ensure the execution endpoint is updated to the latest Fulu release", + "Ready for Fulu" ) } readiness @ FuluReadiness::ExchangeCapabilitiesFailed { error: _ } => { error!( - log, - "Not ready for Fulu"; - "hint" => "the execution endpoint may be offline", - "info" => %readiness, + hint = "the execution endpoint may be offline", + info = %readiness, + "Not ready for Fulu" ) } readiness => warn!( - log, - "Not ready for Fulu"; - "hint" => "try updating the execution endpoint", - "info" => %readiness, + hint = "try updating the execution endpoint", + info = %readiness, + "Not ready for Fulu" ), } } -async fn genesis_execution_payload_logging( - beacon_chain: &BeaconChain, - log: &Logger, -) { +async fn genesis_execution_payload_logging(beacon_chain: &BeaconChain) { match beacon_chain .check_genesis_execution_payload_is_correct() .await { Ok(GenesisExecutionPayloadStatus::Correct(block_hash)) => { info!( - log, - "Execution enabled from genesis"; - "genesis_payload_block_hash" => ?block_hash, + genesis_payload_block_hash = ?block_hash, + "Execution enabled from genesis" ); } Ok(GenesisExecutionPayloadStatus::BlockHashMismatch { got, expected }) => { error!( - log, - "Genesis payload block hash mismatch"; - "info" => "genesis is misconfigured and likely to fail", - "consensus_node_block_hash" => ?expected, - "execution_node_block_hash" => ?got, + info = "genesis is misconfigured and likely to fail", + consensus_node_block_hash = ?expected, + execution_node_block_hash = ?got, + "Genesis payload block hash mismatch" ); } Ok(GenesisExecutionPayloadStatus::TransactionsRootMismatch { got, expected }) => { error!( - log, - "Genesis payload transactions root mismatch"; - "info" => "genesis is misconfigured and likely to fail", - "consensus_node_transactions_root" => ?expected, - "execution_node_transactions_root" => ?got, + info = "genesis is misconfigured and likely to fail", + consensus_node_transactions_root = ?expected, + execution_node_transactions_root = ?got, + "Genesis payload transactions root mismatch" ); } Ok(GenesisExecutionPayloadStatus::WithdrawalsRootMismatch { got, expected }) => { error!( - log, - "Genesis payload withdrawals root mismatch"; - "info" => "genesis is misconfigured and likely to fail", - "consensus_node_withdrawals_root" => ?expected, - "execution_node_withdrawals_root" => ?got, + info = "genesis is misconfigured and likely to fail", + consensus_node_withdrawals_root = ?expected, + execution_node_withdrawals_root = ?got, + "Genesis payload withdrawals root mismatch" ); } Ok(GenesisExecutionPayloadStatus::OtherMismatch) => { error!( - log, - "Genesis payload header mismatch"; - "info" => "genesis is misconfigured and likely to fail", - "detail" => "see debug logs for payload headers" + info = "genesis is misconfigured and likely to fail", + detail = "see debug logs for payload headers", + "Genesis payload header mismatch" ); } Ok(GenesisExecutionPayloadStatus::Irrelevant) => { - info!( - log, - "Execution is not enabled from genesis"; - ); + info!("Execution is not enabled from genesis"); } Ok(GenesisExecutionPayloadStatus::AlreadyHappened) => { warn!( - log, - "Unable to check genesis which has already occurred"; - "info" => "this is probably a race condition or a bug" + info = "this is probably a race condition or a bug", + "Unable to check genesis which has already occurred" ); } Err(e) => { error!( - log, - "Unable to check genesis execution payload"; - "error" => ?e + error = ?e, + "Unable to check genesis execution payload" ); } } } -fn eth1_logging(beacon_chain: &BeaconChain, log: &Logger) { +fn eth1_logging(beacon_chain: &BeaconChain) { let current_slot_opt = beacon_chain.slot().ok(); // Perform some logging about the eth1 chain @@ -733,13 +693,12 @@ fn eth1_logging(beacon_chain: &BeaconChain, log: &Logger &beacon_chain.spec, ) { debug!( - log, - "Eth1 cache sync status"; - "eth1_head_block" => status.head_block_number, - "latest_cached_block_number" => status.latest_cached_block_number, - "latest_cached_timestamp" => status.latest_cached_block_timestamp, - "voting_target_timestamp" => status.voting_target_timestamp, - "ready" => status.lighthouse_is_cached_and_ready + eth1_head_block = status.head_block_number, + latest_cached_block_number = status.latest_cached_block_number, + latest_cached_timestamp = status.latest_cached_block_timestamp, + voting_target_timestamp = status.voting_target_timestamp, + ready = status.lighthouse_is_cached_and_ready, + "Eth1 cache sync status" ); if !status.lighthouse_is_cached_and_ready { @@ -755,16 +714,12 @@ fn eth1_logging(beacon_chain: &BeaconChain, log: &Logger .unwrap_or_else(|| "initializing deposits".to_string()); warn!( - log, - "Syncing deposit contract block cache"; - "est_blocks_remaining" => distance, + est_blocks_remaining = distance, + "Syncing deposit contract block cache" ); } } else { - error!( - log, - "Unable to determine deposit contract sync status"; - ); + error!("Unable to determine deposit contract sync status"); } } } diff --git a/beacon_node/eth1/Cargo.toml b/beacon_node/eth1/Cargo.toml index 8ccd50aad8..fa08364251 100644 --- a/beacon_node/eth1/Cargo.toml +++ b/beacon_node/eth1/Cargo.toml @@ -8,7 +8,6 @@ edition = { workspace = true } environment = { workspace = true } eth1_test_rig = { workspace = true } serde_yaml = { workspace = true } -sloggers = { workspace = true } [dependencies] eth2 = { workspace = true } @@ -22,10 +21,10 @@ metrics = { workspace = true } parking_lot = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } -slog = { workspace = true } state_processing = { workspace = true } superstruct = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } types = { workspace = true } diff --git a/beacon_node/eth1/src/service.rs b/beacon_node/eth1/src/service.rs index 71ab98a6a2..6b10bd2215 100644 --- a/beacon_node/eth1/src/service.rs +++ b/beacon_node/eth1/src/service.rs @@ -13,13 +13,13 @@ use futures::future::TryFutureExt; use parking_lot::{RwLock, RwLockReadGuard}; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{debug, error, info, trace, warn, Logger}; use std::fmt::Debug; use std::ops::{Range, RangeInclusive}; use std::path::PathBuf; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::time::{interval_at, Duration, Instant}; +use tracing::{debug, error, info, trace, warn}; use types::{ChainSpec, DepositTreeSnapshot, Eth1Data, EthSpec, Unsigned}; /// Indicates the default eth1 chain id we use for the deposit contract. @@ -58,22 +58,16 @@ type EndpointState = Result<(), EndpointError>; /// Returns `Ok` if the endpoint is usable, i.e. is reachable and has a correct network id and /// chain id. Otherwise it returns `Err`. -async fn endpoint_state( - endpoint: &HttpJsonRpc, - config_chain_id: &Eth1Id, - log: &Logger, -) -> EndpointState { +async fn endpoint_state(endpoint: &HttpJsonRpc, config_chain_id: &Eth1Id) -> EndpointState { let error_connecting = |e: String| { debug!( - log, - "eth1 endpoint error"; - "endpoint" => %endpoint, - "error" => &e, + %endpoint, + error = &e, + "eth1 endpoint error" ); warn!( - log, - "Error connecting to eth1 node endpoint"; - "endpoint" => %endpoint, + %endpoint, + "Error connecting to eth1 node endpoint" ); EndpointError::RequestFailed(e) }; @@ -86,19 +80,17 @@ async fn endpoint_state( // Handle the special case if chain_id == Eth1Id::Custom(0) { warn!( - log, - "Remote execution node is not synced"; - "endpoint" => %endpoint, + %endpoint, + "Remote execution node is not synced" ); return Err(EndpointError::FarBehind); } if &chain_id != config_chain_id { warn!( - log, - "Invalid execution chain ID. Please switch to correct chain ID on endpoint"; - "endpoint" => %endpoint, - "expected" => ?config_chain_id, - "received" => ?chain_id, + %endpoint, + expected = ?config_chain_id, + received = ?chain_id, + "Invalid execution chain ID. Please switch to correct chain ID on endpoint" ); Err(EndpointError::WrongChainId) } else { @@ -134,10 +126,9 @@ async fn get_remote_head_and_new_block_ranges( .unwrap_or(u64::MAX); if remote_head_block.timestamp + node_far_behind_seconds < now { warn!( - service.log, - "Execution endpoint is not synced"; - "endpoint" => %endpoint, - "last_seen_block_unix_timestamp" => remote_head_block.timestamp, + %endpoint, + last_seen_block_unix_timestamp = remote_head_block.timestamp, + "Execution endpoint is not synced" ); return Err(Error::EndpointError(EndpointError::FarBehind)); } @@ -145,9 +136,8 @@ async fn get_remote_head_and_new_block_ranges( let handle_remote_not_synced = |e| { if let Error::RemoteNotSynced { .. } = e { warn!( - service.log, - "Execution endpoint is not synced"; - "endpoint" => %endpoint, + %endpoint, + "Execution endpoint is not synced" ); } e @@ -392,12 +382,11 @@ pub fn endpoint_from_config(config: &Config) -> Result { #[derive(Clone)] pub struct Service { inner: Arc, - pub log: Logger, } impl Service { /// Creates a new service. Does not attempt to connect to the eth1 node. - pub fn new(config: Config, log: Logger, spec: Arc) -> Result { + pub fn new(config: Config, spec: Arc) -> Result { Ok(Self { inner: Arc::new(Inner { block_cache: <_>::default(), @@ -410,7 +399,6 @@ impl Service { config: RwLock::new(config), spec, }), - log, }) } @@ -425,7 +413,6 @@ impl Service { /// Creates a new service, initializing the deposit tree from a snapshot. pub fn from_deposit_snapshot( config: Config, - log: Logger, spec: Arc, deposit_snapshot: &DepositTreeSnapshot, ) -> Result { @@ -444,7 +431,6 @@ impl Service { config: RwLock::new(config), spec, }), - log, }) } @@ -464,16 +450,10 @@ impl Service { } /// Recover the deposit and block caches from encoded bytes. - pub fn from_bytes( - bytes: &[u8], - config: Config, - log: Logger, - spec: Arc, - ) -> Result { + pub fn from_bytes(bytes: &[u8], config: Config, spec: Arc) -> Result { let inner = Inner::from_bytes(bytes, config, spec)?; Ok(Self { inner: Arc::new(inner), - log, }) } @@ -621,11 +601,10 @@ impl Service { &self, ) -> Result<(DepositCacheUpdateOutcome, BlockCacheUpdateOutcome), String> { let client = self.client(); - let log = self.log.clone(); let chain_id = self.config().chain_id.clone(); let node_far_behind_seconds = self.inner.config.read().node_far_behind_seconds; - match endpoint_state(client, &chain_id, &log).await { + match endpoint_state(client, &chain_id).await { Ok(()) => crate::metrics::set_gauge(&metrics::ETH1_CONNECTED, 1), Err(e) => { crate::metrics::set_gauge(&metrics::ETH1_CONNECTED, 0); @@ -655,10 +634,9 @@ impl Service { { let mut deposit_cache = self.inner.deposit_cache.write(); debug!( - self.log, - "Resetting last processed block"; - "old_block_number" => deposit_cache.last_processed_block, - "new_block_number" => deposit_cache.cache.latest_block_number(), + old_block_number = deposit_cache.last_processed_block, + new_block_number = deposit_cache.cache.latest_block_number(), + "Resetting last processed block" ); deposit_cache.last_processed_block = Some(deposit_cache.cache.latest_block_number()); @@ -668,11 +646,11 @@ impl Service { outcome_result.map_err(|e| format!("Failed to update deposit cache: {:?}", e))?; trace!( - self.log, - "Updated deposit cache"; - "cached_deposits" => self.inner.deposit_cache.read().cache.len(), - "logs_imported" => outcome.logs_imported, - "last_processed_execution_block" => self.inner.deposit_cache.read().last_processed_block, + cached_deposits = self.inner.deposit_cache.read().cache.len(), + logs_imported = outcome.logs_imported, + last_processed_execution_block = + self.inner.deposit_cache.read().last_processed_block, + "Updated deposit cache" ); Ok::<_, String>(outcome) }; @@ -684,11 +662,10 @@ impl Service { .map_err(|e| format!("Failed to update deposit contract block cache: {:?}", e))?; trace!( - self.log, - "Updated deposit contract block cache"; - "cached_blocks" => self.inner.block_cache.read().len(), - "blocks_imported" => outcome.blocks_imported, - "head_block" => outcome.head_block_number, + cached_blocks = self.inner.block_cache.read().len(), + blocks_imported = outcome.blocks_imported, + head_block = outcome.head_block_number, + "Updated deposit contract block cache" ); Ok::<_, String>(outcome) }; @@ -727,17 +704,15 @@ impl Service { let update_result = self.update().await; match update_result { Err(e) => error!( - self.log, - "Error updating deposit contract cache"; - "retry_millis" => update_interval.as_millis(), - "error" => e, + retry_millis = update_interval.as_millis(), + error = e, + "Error updating deposit contract cache" ), Ok((deposit, block)) => debug!( - self.log, - "Updated deposit contract cache"; - "retry_millis" => update_interval.as_millis(), - "blocks" => format!("{:?}", block), - "deposits" => format!("{:?}", deposit), + retry_millis = update_interval.as_millis(), + ?block, + ?deposit, + "Updated deposit contract cache" ), }; let optional_eth1data = self.inner.to_finalize.write().take(); @@ -752,23 +727,20 @@ impl Service { if deposit_count_to_finalize > already_finalized { match self.finalize_deposits(eth1data_to_finalize) { Err(e) => warn!( - self.log, - "Failed to finalize deposit cache"; - "error" => ?e, - "info" => "this should resolve on its own" + error = ?e, + info = "this should resolve on its own", + "Failed to finalize deposit cache" ), Ok(()) => info!( - self.log, - "Successfully finalized deposit tree"; - "finalized deposit count" => deposit_count_to_finalize, + finalized_deposit_count = deposit_count_to_finalize, + "Successfully finalized deposit tree" ), } } else { debug!( - self.log, - "Deposits tree already finalized"; - "already_finalized" => already_finalized, - "deposit_count_to_finalize" => deposit_count_to_finalize, + %already_finalized, + %deposit_count_to_finalize, + "Deposits tree already finalized" ); } } @@ -889,10 +861,7 @@ impl Service { let deposit_contract_address_ref: &str = &deposit_contract_address; for block_range in block_number_chunks.into_iter() { if block_range.is_empty() { - debug!( - self.log, - "No new blocks to scan for logs"; - ); + debug!("No new blocks to scan for logs"); continue; } @@ -946,11 +915,7 @@ impl Service { Ok::<_, Error>(()) })?; - debug!( - self.log, - "Imported deposit logs chunk"; - "logs" => logs.len(), - ); + debug!(logs = logs.len(), "Imported deposit logs chunk"); cache.last_processed_block = Some(block_range.end.saturating_sub(1)); @@ -963,18 +928,16 @@ impl Service { if logs_imported > 0 { info!( - self.log, - "Imported deposit log(s)"; - "latest_block" => self.inner.deposit_cache.read().cache.latest_block_number(), - "total" => self.deposit_cache_len(), - "new" => logs_imported + latest_block = self.inner.deposit_cache.read().cache.latest_block_number(), + total = self.deposit_cache_len(), + new = logs_imported, + "Imported deposit log(s)" ); } else { debug!( - self.log, - "No new deposits found"; - "latest_block" => self.inner.deposit_cache.read().cache.latest_block_number(), - "total_deposits" => self.deposit_cache_len(), + latest_block = self.inner.deposit_cache.read().cache.latest_block_number(), + total_deposits = self.deposit_cache_len(), + "No new deposits found" ); } @@ -1058,10 +1021,9 @@ impl Service { .collect::>(); debug!( - self.log, - "Downloading execution blocks"; - "first" => ?required_block_numbers.first(), - "last" => ?required_block_numbers.last(), + first = ?required_block_numbers.first(), + last = ?required_block_numbers.last(), + "Downloading execution blocks" ); // Produce a stream from the list of required block numbers and return a future that @@ -1116,19 +1078,17 @@ impl Service { if blocks_imported > 0 { debug!( - self.log, - "Imported execution block(s)"; - "latest_block_age" => latest_block_mins, - "latest_block" => block_cache.highest_block_number(), - "total_cached_blocks" => block_cache.len(), - "new" => %blocks_imported + latest_block_age = latest_block_mins, + latest_block = block_cache.highest_block_number(), + total_cached_blocks = block_cache.len(), + new = %blocks_imported, + "Imported execution block(s)" ); } else { debug!( - self.log, - "No new execution blocks imported"; - "latest_block" => block_cache.highest_block_number(), - "cached_blocks" => block_cache.len(), + latest_block = block_cache.highest_block_number(), + cached_blocks = block_cache.len(), + "No new execution blocks imported" ); } diff --git a/beacon_node/eth1/tests/test.rs b/beacon_node/eth1/tests/test.rs index e442ce4863..48ed189259 100644 --- a/beacon_node/eth1/tests/test.rs +++ b/beacon_node/eth1/tests/test.rs @@ -4,7 +4,7 @@ use eth1::{Config, Eth1Endpoint, Service}; use eth1::{DepositCache, DEFAULT_CHAIN_ID}; use eth1_test_rig::{AnvilEth1Instance, Http, Middleware, Provider}; use execution_layer::http::{deposit_methods::*, HttpJsonRpc, Log}; -use logging::test_logger; +use logging::create_test_tracing_subscriber; use merkle_proof::verify_merkle_proof; use sensitive_url::SensitiveUrl; use std::ops::Range; @@ -19,11 +19,10 @@ use types::{ const DEPOSIT_CONTRACT_TREE_DEPTH: usize = 32; pub fn new_env() -> Environment { + create_test_tracing_subscriber(); EnvironmentBuilder::minimal() .multi_threaded_tokio_runtime() .expect("should start tokio runtime") - .test_logger() - .expect("should start null logger") .build() .expect("should build env") } @@ -100,9 +99,8 @@ mod eth1_cache { #[tokio::test] async fn simple_scenario() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - for follow_distance in 0..3 { let eth1 = new_anvil_instance() .await @@ -123,12 +121,8 @@ mod eth1_cache { }; let cache_follow_distance = config.cache_follow_distance(); - let service = Service::new( - config, - log.clone(), - Arc::new(MainnetEthSpec::default_spec()), - ) - .unwrap(); + let service = + Service::new(config, Arc::new(MainnetEthSpec::default_spec())).unwrap(); // Create some blocks and then consume them, performing the test `rounds` times. for round in 0..2 { @@ -186,9 +180,8 @@ mod eth1_cache { #[tokio::test] async fn big_skip() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); @@ -208,7 +201,6 @@ mod eth1_cache { block_cache_truncation: Some(cache_len), ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -241,9 +233,8 @@ mod eth1_cache { /// cache size. #[tokio::test] async fn pruning() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); @@ -263,7 +254,6 @@ mod eth1_cache { block_cache_truncation: Some(cache_len), ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -293,9 +283,8 @@ mod eth1_cache { #[tokio::test] async fn double_update() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let n = 16; let eth1 = new_anvil_instance() @@ -314,7 +303,6 @@ mod eth1_cache { follow_distance: 0, ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -346,9 +334,8 @@ mod deposit_tree { #[tokio::test] async fn updating() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let n = 4; let eth1 = new_anvil_instance() @@ -369,7 +356,6 @@ mod deposit_tree { follow_distance: 0, ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -427,9 +413,8 @@ mod deposit_tree { #[tokio::test] async fn double_update() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let n = 8; let eth1 = new_anvil_instance() @@ -451,7 +436,6 @@ mod deposit_tree { follow_distance: 0, ..Config::default() }, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); @@ -689,9 +673,8 @@ mod fast { // with the deposit count and root computed from the deposit cache. #[tokio::test] async fn deposit_cache_query() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); @@ -712,7 +695,6 @@ mod fast { block_cache_truncation: None, ..Config::default() }, - log, spec.clone(), ) .unwrap(); @@ -772,9 +754,8 @@ mod persist { use super::*; #[tokio::test] async fn test_persist_caches() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let eth1 = new_anvil_instance() .await .expect("should start eth1 environment"); @@ -793,12 +774,8 @@ mod persist { block_cache_truncation: None, ..Config::default() }; - let service = Service::new( - config.clone(), - log.clone(), - Arc::new(MainnetEthSpec::default_spec()), - ) - .unwrap(); + let service = + Service::new(config.clone(), Arc::new(MainnetEthSpec::default_spec())).unwrap(); let n = 10; let deposits: Vec<_> = (0..n).map(|_| random_deposit_data()).collect(); for deposit in &deposits { @@ -840,7 +817,6 @@ mod persist { let recovered_service = Service::from_bytes( ð1_bytes, config, - log, Arc::new(MainnetEthSpec::default_spec()), ) .unwrap(); diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index 236180c011..580eac3c88 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -12,6 +12,7 @@ arc-swap = "1.6.0" builder_client = { path = "../builder_client" } bytes = { workspace = true } eth2 = { workspace = true } +eth2_network_config = { workspace = true } ethereum_serde_utils = { workspace = true } ethereum_ssz = { workspace = true } ethers-core = { workspace = true } @@ -35,7 +36,6 @@ sensitive_url = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } sha2 = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } ssz_types = { workspace = true } state_processing = { workspace = true } @@ -45,6 +45,7 @@ task_executor = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } tree_hash_derive = { workspace = true } triehash = "0.8.4" diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index b9d878b1f8..aed6cdba67 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -142,11 +142,18 @@ pub struct ExecutionBlock { pub block_number: u64, pub parent_hash: ExecutionBlockHash, - pub total_difficulty: Uint256, + pub total_difficulty: Option, #[serde(with = "serde_utils::u64_hex_be")] pub timestamp: u64, } +impl ExecutionBlock { + pub fn terminal_total_difficulty_reached(&self, terminal_total_difficulty: Uint256) -> bool { + self.total_difficulty + .is_none_or(|td| td >= terminal_total_difficulty) + } +} + #[superstruct( variants(V1, V2, V3), variant_attributes(derive(Clone, Debug, Eq, Hash, PartialEq),), diff --git a/beacon_node/execution_layer/src/engines.rs b/beacon_node/execution_layer/src/engines.rs index 75d0b872ce..b9e030703d 100644 --- a/beacon_node/execution_layer/src/engines.rs +++ b/beacon_node/execution_layer/src/engines.rs @@ -6,7 +6,6 @@ use crate::engine_api::{ }; use crate::{ClientVersionV1, HttpJsonRpc}; use lru::LruCache; -use slog::{debug, error, info, warn, Logger}; use std::future::Future; use std::num::NonZeroUsize; use std::sync::Arc; @@ -14,6 +13,7 @@ use std::time::Duration; use task_executor::TaskExecutor; use tokio::sync::{watch, Mutex, RwLock}; use tokio_stream::wrappers::WatchStream; +use tracing::{debug, error, info, warn}; use types::non_zero_usize::new_non_zero_usize; use types::ExecutionBlockHash; @@ -128,19 +128,17 @@ pub struct Engine { state: RwLock, latest_forkchoice_state: RwLock>, executor: TaskExecutor, - log: Logger, } impl Engine { /// Creates a new, offline engine. - pub fn new(api: HttpJsonRpc, executor: TaskExecutor, log: &Logger) -> Self { + pub fn new(api: HttpJsonRpc, executor: TaskExecutor) -> Self { Self { api, payload_id_cache: Mutex::new(LruCache::new(PAYLOAD_ID_LRU_CACHE_SIZE)), state: Default::default(), latest_forkchoice_state: Default::default(), executor, - log: log.clone(), } } @@ -167,7 +165,6 @@ impl Engine { &self, forkchoice_state: ForkchoiceState, payload_attributes: Option, - log: &Logger, ) -> Result { let response = self .api @@ -180,11 +177,7 @@ impl Engine { { self.payload_id_cache.lock().await.put(key, payload_id); } else { - debug!( - log, - "Engine returned unexpected payload_id"; - "payload_id" => ?payload_id - ); + debug!(?payload_id, "Engine returned unexpected payload_id"); } } @@ -205,33 +198,24 @@ impl Engine { if let Some(forkchoice_state) = latest_forkchoice_state { if forkchoice_state.head_block_hash == ExecutionBlockHash::zero() { debug!( - self.log, - "No need to call forkchoiceUpdated"; - "msg" => "head does not have execution enabled", + msg = "head does not have execution enabled", + "No need to call forkchoiceUpdated" ); return; } - info!( - self.log, - "Issuing forkchoiceUpdated"; - "forkchoice_state" => ?forkchoice_state, - ); + info!(?forkchoice_state, "Issuing forkchoiceUpdated"); // For simplicity, payload attributes are never included in this call. It may be // reasonable to include them in the future. if let Err(e) = self.api.forkchoice_updated(forkchoice_state, None).await { debug!( - self.log, - "Failed to issue latest head to engine"; - "error" => ?e, + error = ?e, + "Failed to issue latest head to engine" ); } } else { - debug!( - self.log, - "No head, not sending to engine"; - ); + debug!("No head, not sending to engine"); } } @@ -252,18 +236,12 @@ impl Engine { Ok(()) => { let mut state = self.state.write().await; if **state != EngineStateInternal::Synced { - info!( - self.log, - "Execution engine online"; - ); + info!("Execution engine online"); // Send the node our latest forkchoice_state. self.send_latest_forkchoice_state().await; } else { - debug!( - self.log, - "Execution engine online"; - ); + debug!("Execution engine online"); } state.update(EngineStateInternal::Synced); (**state, ResponseCacheAction::Update) @@ -275,9 +253,8 @@ impl Engine { } Err(EngineApiError::Auth(err)) => { error!( - self.log, - "Failed jwt authorization"; - "error" => ?err, + error = ?err, + "Failed jwt authorization" ); let mut state = self.state.write().await; @@ -286,9 +263,8 @@ impl Engine { } Err(e) => { error!( - self.log, - "Error during execution engine upcheck"; - "error" => ?e, + error = ?e, + "Error during execution engine upcheck" ); let mut state = self.state.write().await; @@ -308,9 +284,9 @@ impl Engine { .get_engine_capabilities(Some(CACHED_RESPONSE_AGE_LIMIT)) .await { - warn!(self.log, - "Error during exchange capabilities"; - "error" => ?e, + warn!( + error = ?e, + "Error during exchange capabilities" ) } else { // no point in running this if there was an error fetching the capabilities @@ -326,11 +302,7 @@ impl Engine { } } - debug!( - self.log, - "Execution engine upcheck complete"; - "state" => ?state, - ); + debug!(?state, "Execution engine upcheck complete"); } /// Returns the execution engine capabilities resulting from a call to @@ -395,11 +367,7 @@ impl Engine { Ok(result) } Err(error) => { - warn!( - self.log, - "Execution engine call failed"; - "error" => ?error, - ); + warn!(?error, "Execution engine call failed"); // The node just returned an error, run an upcheck so we can update the endpoint // state. diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 6e5e4fca01..6644e46a0d 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -21,12 +21,12 @@ use eth2::types::{builder_bid::SignedBuilderBid, BlobsBundle, ForkVersionedRespo use ethers_core::types::Transaction as EthersTransaction; use fixed_bytes::UintExtended; use fork_choice::ForkchoiceUpdateParameters; +use logging::crit; use lru::LruCache; use payload_status::process_payload_status; pub use payload_status::PayloadStatus; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use std::collections::{hash_map::Entry, HashMap}; use std::fmt; @@ -43,6 +43,7 @@ use tokio::{ time::sleep, }; use tokio_stream::wrappers::WatchStream; +use tracing::{debug, error, info, warn}; use tree_hash::TreeHash; use types::beacon_block_body::KzgCommitments; use types::builder_bid::BuilderBid; @@ -422,7 +423,6 @@ struct Inner { proposers: RwLock>, executor: TaskExecutor, payload_cache: PayloadCache, - log: Logger, /// Track whether the last `newPayload` call errored. /// /// This is used *only* in the informational sync status endpoint, so that a VC using this @@ -441,6 +441,8 @@ pub struct Config { pub builder_header_timeout: Option, /// User agent to send with requests to the builder API. pub builder_user_agent: Option, + /// Disable ssz requests on builder. Only use json. + pub disable_builder_ssz_requests: bool, /// JWT secret for the above endpoint running the engine api. pub secret_file: Option, /// The default fee recipient to use on the beacon node if none if provided from @@ -464,12 +466,13 @@ pub struct ExecutionLayer { impl ExecutionLayer { /// Instantiate `Self` with an Execution engine specified in `Config`, using JSON-RPC via HTTP. - pub fn from_config(config: Config, executor: TaskExecutor, log: Logger) -> Result { + pub fn from_config(config: Config, executor: TaskExecutor) -> Result { let Config { execution_endpoint: url, builder_url, builder_user_agent, builder_header_timeout, + disable_builder_ssz_requests, secret_file, suggested_fee_recipient, jwt_id, @@ -497,7 +500,7 @@ impl ExecutionLayer { .map_err(Error::InvalidJWTSecret) } else { // Create a new file and write a randomly generated secret to it if file does not exist - warn!(log, "No JWT found on disk. Generating"; "path" => %secret_file.display()); + warn!(path = %secret_file.display(),"No JWT found on disk. Generating"); std::fs::File::options() .write(true) .create_new(true) @@ -514,10 +517,10 @@ impl ExecutionLayer { let engine: Engine = { let auth = Auth::new(jwt_key, jwt_id, jwt_version); - debug!(log, "Loaded execution endpoint"; "endpoint" => %execution_url, "jwt_path" => ?secret_file.as_path()); + debug!(endpoint = %execution_url, jwt_path = ?secret_file.as_path(),"Loaded execution endpoint"); let api = HttpJsonRpc::new_with_auth(execution_url, auth, execution_timeout_multiplier) .map_err(Error::ApiError)?; - Engine::new(api, executor.clone(), &log) + Engine::new(api, executor.clone()) }; let inner = Inner { @@ -530,7 +533,6 @@ impl ExecutionLayer { execution_blocks: Mutex::new(LruCache::new(EXECUTION_BLOCKS_LRU_CACHE_SIZE)), executor, payload_cache: PayloadCache::default(), - log, last_new_payload_errored: RwLock::new(false), }; @@ -539,7 +541,12 @@ impl ExecutionLayer { }; if let Some(builder_url) = builder_url { - el.set_builder_url(builder_url, builder_user_agent, builder_header_timeout)?; + el.set_builder_url( + builder_url, + builder_user_agent, + builder_header_timeout, + disable_builder_ssz_requests, + )?; } Ok(el) @@ -562,18 +569,20 @@ impl ExecutionLayer { builder_url: SensitiveUrl, builder_user_agent: Option, builder_header_timeout: Option, + disable_ssz: bool, ) -> Result<(), Error> { let builder_client = BuilderHttpClient::new( builder_url.clone(), builder_user_agent, builder_header_timeout, + disable_ssz, ) .map_err(Error::Builder)?; info!( - self.log(), - "Using external block builder"; - "builder_url" => ?builder_url, - "local_user_agent" => builder_client.get_user_agent(), + ?builder_url, + local_user_agent = builder_client.get_user_agent(), + ssz_disabled = disable_ssz, + "Using external block builder" ); self.inner.builder.swap(Some(Arc::new(builder_client))); Ok(()) @@ -610,7 +619,7 @@ impl ExecutionLayer { } /// Get the current difficulty of the PoW chain. - pub async fn get_current_difficulty(&self) -> Result { + pub async fn get_current_difficulty(&self) -> Result, ApiError> { let block = self .engine() .api @@ -644,10 +653,6 @@ impl ExecutionLayer { &self.inner.proposers } - fn log(&self) -> &Logger { - &self.inner.log - } - pub async fn execution_engine_forkchoice_lock(&self) -> MutexGuard<'_, ()> { self.inner.execution_engine_forkchoice_lock.lock().await } @@ -705,16 +710,15 @@ impl ExecutionLayer { .await .map_err(|e| { error!( - el.log(), - "Failed to clean proposer preparation cache"; - "error" => format!("{:?}", e) + error = ?e, + "Failed to clean proposer preparation cache" ) }) .unwrap_or(()), - None => error!(el.log(), "Failed to get current epoch from slot clock"), + None => error!("Failed to get current epoch from slot clock"), } } else { - error!(el.log(), "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot and retry. sleep(slot_clock.slot_duration()).await; } @@ -854,12 +858,11 @@ impl ExecutionLayer { } else { // If there is no user-provided fee recipient, use a junk value and complain loudly. crit!( - self.log(), - "Fee recipient unknown"; - "msg" => "the suggested_fee_recipient was unknown during block production. \ + msg = "the suggested_fee_recipient was unknown during block production. \ a junk address was used, rewards were lost! \ check the --suggested-fee-recipient flag and VC configuration.", - "proposer_index" => ?proposer_index + ?proposer_index, + "Fee recipient unknown" ); Address::from_slice(&DEFAULT_SUGGESTED_FEE_RECIPIENT) @@ -976,11 +979,10 @@ impl ExecutionLayer { let parent_hash = payload_parameters.parent_hash; info!( - self.log(), - "Requesting blinded header from connected builder"; - "slot" => ?slot, - "pubkey" => ?pubkey, - "parent_hash" => ?parent_hash, + ?slot, + ?pubkey, + ?parent_hash, + "Requesting blinded header from connected builder" ); // Wait for the builder *and* local EL to produce a payload (or return an error). @@ -1001,20 +1003,19 @@ impl ExecutionLayer { ); info!( - self.log(), - "Requested blinded execution payload"; - "relay_fee_recipient" => match &relay_result { + relay_fee_recipient = match &relay_result { Ok(Some(r)) => format!("{:?}", r.data.message.header().fee_recipient()), Ok(None) => "empty response".to_string(), Err(_) => "request failed".to_string(), }, - "relay_response_ms" => relay_duration.as_millis(), - "local_fee_recipient" => match &local_result { + relay_response_ms = relay_duration.as_millis(), + local_fee_recipient = match &local_result { Ok(get_payload_response) => format!("{:?}", get_payload_response.fee_recipient()), - Err(_) => "request failed".to_string() + Err(_) => "request failed".to_string(), }, - "local_response_ms" => local_duration.as_millis(), - "parent_hash" => ?parent_hash, + local_response_ms = local_duration.as_millis(), + ?parent_hash, + "Requested blinded execution payload" ); (relay_result, local_result) @@ -1041,24 +1042,21 @@ impl ExecutionLayer { // chain is unhealthy, gotta use local payload match builder_params.chain_health { ChainHealth::Unhealthy(condition) => info!( - self.log(), - "Chain is unhealthy, using local payload"; - "info" => "this helps protect the network. the --builder-fallback flags \ - can adjust the expected health conditions.", - "failed_condition" => ?condition + info = "this helps protect the network. the --builder-fallback flags \ + can adjust the expected health conditions.", + failed_condition = ?condition, + "Chain is unhealthy, using local payload" ), // Intentional no-op, so we never attempt builder API proposals pre-merge. ChainHealth::PreMerge => (), ChainHealth::Optimistic => info!( - self.log(), - "Chain is optimistic; can't build payload"; - "info" => "the local execution engine is syncing and the builder network \ - cannot safely be used - unable to propose block" - ), - ChainHealth::Healthy => crit!( - self.log(), - "got healthy but also not healthy.. this shouldn't happen!" + info = "the local execution engine is syncing and the builder network \ + cannot safely be used - unable to propose block", + "Chain is optimistic; can't build payload" ), + ChainHealth::Healthy => { + crit!("got healthy but also not healthy.. this shouldn't happen!") + } } return self .get_full_payload_caching(payload_parameters) @@ -1075,12 +1073,11 @@ impl ExecutionLayer { match (relay_result, local_result) { (Err(e), Ok(local)) => { warn!( - self.log(), - "Builder error when requesting payload"; - "info" => "falling back to local execution client", - "relay_error" => ?e, - "local_block_hash" => ?local.block_hash(), - "parent_hash" => ?parent_hash, + info = "falling back to local execution client", + relay_error = ?e, + local_block_hash = ?local.block_hash(), + ?parent_hash, + "Builder error when requesting payload" ); Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1088,11 +1085,10 @@ impl ExecutionLayer { } (Ok(None), Ok(local)) => { info!( - self.log(), - "Builder did not return a payload"; - "info" => "falling back to local execution client", - "local_block_hash" => ?local.block_hash(), - "parent_hash" => ?parent_hash, + info = "falling back to local execution client", + local_block_hash=?local.block_hash(), + ?parent_hash, + "Builder did not return a payload" ); Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1100,24 +1096,22 @@ impl ExecutionLayer { } (Err(relay_error), Err(local_error)) => { crit!( - self.log(), - "Unable to produce execution payload"; - "info" => "the local EL and builder both failed - unable to propose block", - "relay_error" => ?relay_error, - "local_error" => ?local_error, - "parent_hash" => ?parent_hash, + info = "the local EL and builder both failed - unable to propose block", + ?relay_error, + ?local_error, + ?parent_hash, + "Unable to produce execution payload" ); Err(Error::CannotProduceHeader) } (Ok(None), Err(local_error)) => { crit!( - self.log(), - "Unable to produce execution payload"; - "info" => "the local EL failed and the builder returned nothing - \ - the block proposal will be missed", - "local_error" => ?local_error, - "parent_hash" => ?parent_hash, + info = "the local EL failed and the builder returned nothing - \ + the block proposal will be missed", + ?local_error, + ?parent_hash, + "Unable to produce execution payload" ); Err(Error::CannotProduceHeader) @@ -1126,11 +1120,10 @@ impl ExecutionLayer { let header = &relay.data.message.header(); info!( - self.log(), - "Received local and builder payloads"; - "relay_block_hash" => ?header.block_hash(), - "local_block_hash" => ?local.block_hash(), - "parent_hash" => ?parent_hash, + relay_block_hash = ?header.block_hash(), + local_block_hash=?local.block_hash(), + ?parent_hash, + "Received local and builder payloads" ); // check relay payload validity @@ -1143,12 +1136,11 @@ impl ExecutionLayer { &[reason.as_ref().as_ref()], ); warn!( - self.log(), - "Builder returned invalid payload"; - "info" => "using local payload", - "reason" => %reason, - "relay_block_hash" => ?header.block_hash(), - "parent_hash" => ?parent_hash, + info = "using local payload", + %reason, + relay_block_hash = ?header.block_hash(), + ?parent_hash, + "Builder returned invalid payload" ); return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1167,12 +1159,11 @@ impl ExecutionLayer { if local_value >= boosted_relay_value { info!( - self.log(), - "Local block is more profitable than relay block"; - "local_block_value" => %local_value, - "relay_value" => %relay_value, - "boosted_relay_value" => %boosted_relay_value, - "builder_boost_factor" => ?builder_boost_factor, + %local_value, + %relay_value, + %boosted_relay_value, + ?builder_boost_factor, + "Local block is more profitable than relay block" ); return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1181,10 +1172,9 @@ impl ExecutionLayer { if local.should_override_builder().unwrap_or(false) { info!( - self.log(), - "Using local payload because execution engine suggested we ignore builder payload"; - "local_block_value" => %local_value, - "relay_value" => %relay_value + %local_value, + %relay_value, + "Using local payload because execution engine suggested we ignore builder payload" ); return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( local.try_into()?, @@ -1192,12 +1182,11 @@ impl ExecutionLayer { } info!( - self.log(), - "Relay block is more profitable than local block"; - "local_block_value" => %local_value, - "relay_value" => %relay_value, - "boosted_relay_value" => %boosted_relay_value, - "builder_boost_factor" => ?builder_boost_factor + %local_value, + %relay_value, + %boosted_relay_value, + ?builder_boost_factor, + "Relay block is more profitable than local block" ); Ok(ProvenancedPayload::try_from(relay.data.message)?) @@ -1206,11 +1195,10 @@ impl ExecutionLayer { let header = &relay.data.message.header(); info!( - self.log(), - "Received builder payload with local error"; - "relay_block_hash" => ?header.block_hash(), - "local_error" => ?local_error, - "parent_hash" => ?parent_hash, + relay_block_hash = ?header.block_hash(), + ?local_error, + ?parent_hash, + "Received builder payload with local error" ); match verify_builder_bid(&relay, payload_parameters, None, spec) { @@ -1221,12 +1209,11 @@ impl ExecutionLayer { &[reason.as_ref().as_ref()], ); crit!( - self.log(), - "Builder returned invalid payload"; - "info" => "no local payload either - unable to propose block", - "reason" => %reason, - "relay_block_hash" => ?header.block_hash(), - "parent_hash" => ?parent_hash, + info = "no local payload either - unable to propose block", + %reason, + relay_block_hash = ?header.block_hash(), + ?parent_hash, + "Builder returned invalid payload" ); Err(Error::CannotProduceHeader) } @@ -1293,7 +1280,6 @@ impl ExecutionLayer { .notify_forkchoice_updated( fork_choice_state, Some(payload_attributes.clone()), - self.log(), ) .await?; @@ -1301,12 +1287,11 @@ impl ExecutionLayer { Some(payload_id) => payload_id, None => { error!( - self.log(), - "Exec engine unable to produce payload"; - "msg" => "No payload ID, the engine is likely syncing. \ - This has the potential to cause a missed block proposal.", - "status" => ?response.payload_status - ); + msg = "No payload ID, the engine is likely syncing. \ + This has the potential to cause a missed block proposal.", + status = ?response.payload_status, + "Exec engine unable to produce payload" + ); return Err(ApiError::PayloadIdUnavailable); } } @@ -1314,36 +1299,44 @@ impl ExecutionLayer { let payload_response = async { debug!( - self.log(), - "Issuing engine_getPayload"; - "suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(), - "prev_randao" => ?payload_attributes.prev_randao(), - "timestamp" => payload_attributes.timestamp(), - "parent_hash" => ?parent_hash, + suggested_fee_recipient = ?payload_attributes.suggested_fee_recipient(), + prev_randao = ?payload_attributes.prev_randao(), + timestamp = payload_attributes.timestamp(), + ?parent_hash, + "Issuing engine_getPayload" ); let _timer = metrics::start_timer_vec( &metrics::EXECUTION_LAYER_REQUEST_TIMES, &[metrics::GET_PAYLOAD], ); engine.api.get_payload::(current_fork, payload_id).await - }.await?; + } + .await?; - if payload_response.execution_payload_ref().fee_recipient() != payload_attributes.suggested_fee_recipient() { + if payload_response.execution_payload_ref().fee_recipient() + != payload_attributes.suggested_fee_recipient() + { error!( - self.log(), - "Inconsistent fee recipient"; - "msg" => "The fee recipient returned from the Execution Engine differs \ + msg = "The fee recipient returned from the Execution Engine differs \ from the suggested_fee_recipient set on the beacon node. This could \ indicate that fees are being diverted to another address. Please \ ensure that the value of suggested_fee_recipient is set correctly and \ that the Execution Engine is trusted.", - "fee_recipient" => ?payload_response.execution_payload_ref().fee_recipient(), - "suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(), + fee_recipient = ?payload_response.execution_payload_ref().fee_recipient(), + suggested_fee_recipient = ?payload_attributes.suggested_fee_recipient(), + "Inconsistent fee recipient" ); } - if cache_fn(self, (payload_response.execution_payload_ref(), payload_response.blobs_bundle().ok())).is_some() { + if cache_fn( + self, + ( + payload_response.execution_payload_ref(), + payload_response.blobs_bundle().ok(), + ), + ) + .is_some() + { warn!( - self.log(), "Duplicate payload cached, this might indicate redundant proposal \ attempts." ); @@ -1383,18 +1376,17 @@ impl ExecutionLayer { &["new_payload", status_str], ); debug!( - self.log(), - "Processed engine_newPayload"; - "status" => status_str, - "parent_hash" => ?parent_hash, - "block_hash" => ?block_hash, - "block_number" => block_number, - "response_time_ms" => timer.elapsed().as_millis() + status = status_str, + ?parent_hash, + ?block_hash, + block_number, + response_time_ms = timer.elapsed().as_millis(), + "Processed engine_newPayload" ); } *self.inner.last_new_payload_errored.write().await = result.is_err(); - process_payload_status(block_hash, result, self.log()) + process_payload_status(block_hash, result) .map_err(Box::new) .map_err(Error::EngineError) } @@ -1451,12 +1443,11 @@ impl ExecutionLayer { let proposer = self.proposers().read().await.get(&proposers_key).cloned()?; debug!( - self.log(), - "Beacon proposer found"; - "payload_attributes" => ?proposer.payload_attributes, - "head_block_root" => ?head_block_root, - "slot" => current_slot, - "validator_index" => proposer.validator_index, + payload_attributes = ?proposer.payload_attributes, + ?head_block_root, + slot = %current_slot, + validator_index = proposer.validator_index, + "Beacon proposer found" ); Some(proposer.payload_attributes) @@ -1477,13 +1468,12 @@ impl ExecutionLayer { ); debug!( - self.log(), - "Issuing engine_forkchoiceUpdated"; - "finalized_block_hash" => ?finalized_block_hash, - "justified_block_hash" => ?justified_block_hash, - "head_block_hash" => ?head_block_hash, - "head_block_root" => ?head_block_root, - "current_slot" => current_slot, + ?finalized_block_hash, + ?justified_block_hash, + ?head_block_hash, + ?head_block_root, + ?current_slot, + "Issuing engine_forkchoiceUpdated" ); let next_slot = current_slot + 1; @@ -1499,12 +1489,7 @@ impl ExecutionLayer { lookahead, ); } else { - debug!( - self.log(), - "Late payload attributes"; - "timestamp" => ?timestamp, - "now" => ?now, - ) + debug!(?timestamp, ?now, "Late payload attributes") } } } @@ -1523,7 +1508,7 @@ impl ExecutionLayer { .engine() .request(|engine| async move { engine - .notify_forkchoice_updated(forkchoice_state, payload_attributes, self.log()) + .notify_forkchoice_updated(forkchoice_state, payload_attributes) .await }) .await; @@ -1538,7 +1523,6 @@ impl ExecutionLayer { process_payload_status( head_block_hash, result.map(|response| response.payload_status), - self.log(), ) .map_err(Box::new) .map_err(Error::EngineError) @@ -1635,11 +1619,10 @@ impl ExecutionLayer { if let Some(hash) = &hash_opt { info!( - self.log(), - "Found terminal block hash"; - "terminal_block_hash_override" => ?spec.terminal_block_hash, - "terminal_total_difficulty" => ?spec.terminal_total_difficulty, - "block_hash" => ?hash, + terminal_block_hash_override = ?spec.terminal_block_hash, + terminal_total_difficulty = ?spec.terminal_total_difficulty, + block_hash = ?hash, + "Found terminal block hash" ); } @@ -1669,7 +1652,8 @@ impl ExecutionLayer { self.execution_blocks().await.put(block.block_hash, block); loop { - let block_reached_ttd = block.total_difficulty >= spec.terminal_total_difficulty; + let block_reached_ttd = + block.terminal_total_difficulty_reached(spec.terminal_total_difficulty); if block_reached_ttd { if block.parent_hash == ExecutionBlockHash::zero() { return Ok(Some(block)); @@ -1678,7 +1662,8 @@ impl ExecutionLayer { .get_pow_block(engine, block.parent_hash) .await? .ok_or(ApiError::ExecutionBlockNotFound(block.parent_hash))?; - let parent_reached_ttd = parent.total_difficulty >= spec.terminal_total_difficulty; + let parent_reached_ttd = + parent.terminal_total_difficulty_reached(spec.terminal_total_difficulty); if block_reached_ttd && !parent_reached_ttd { return Ok(Some(block)); @@ -1754,9 +1739,11 @@ impl ExecutionLayer { parent: ExecutionBlock, spec: &ChainSpec, ) -> bool { - let is_total_difficulty_reached = block.total_difficulty >= spec.terminal_total_difficulty; - let is_parent_total_difficulty_valid = - parent.total_difficulty < spec.terminal_total_difficulty; + let is_total_difficulty_reached = + block.terminal_total_difficulty_reached(spec.terminal_total_difficulty); + let is_parent_total_difficulty_valid = parent + .total_difficulty + .is_some_and(|td| td < spec.terminal_total_difficulty); is_total_difficulty_reached && is_parent_total_difficulty_valid } @@ -1892,16 +1879,18 @@ impl ExecutionLayer { block_root: Hash256, block: &SignedBlindedBeaconBlock, ) -> Result, Error> { - debug!( - self.log(), - "Sending block to builder"; - "root" => ?block_root, - ); + debug!(?block_root, "Sending block to builder"); if let Some(builder) = self.builder() { let (payload_result, duration) = timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async { - if builder.is_ssz_enabled() { + let ssz_enabled = builder.is_ssz_available(); + debug!( + ?block_root, + ssz = ssz_enabled, + "Calling submit_blinded_block on builder" + ); + if ssz_enabled { builder .post_builder_blinded_blocks_ssz(block) .await @@ -1924,13 +1913,12 @@ impl ExecutionLayer { ); let payload = unblinded_response.payload_ref(); info!( - self.log(), - "Builder successfully revealed payload"; - "relay_response_ms" => duration.as_millis(), - "block_root" => ?block_root, - "fee_recipient" => ?payload.fee_recipient(), - "block_hash" => ?payload.block_hash(), - "parent_hash" => ?payload.parent_hash() + relay_response_ms = duration.as_millis(), + ?block_root, + fee_recipient = ?payload.fee_recipient(), + block_hash = ?payload.block_hash(), + parent_hash = ?payload.parent_hash(), + "Builder successfully revealed payload" ) } Err(e) => { @@ -1939,17 +1927,16 @@ impl ExecutionLayer { &[metrics::FAILURE], ); warn!( - self.log(), - "Builder failed to reveal payload"; - "info" => "this is common behaviour for some builders and may not indicate an issue", - "error" => ?e, - "relay_response_ms" => duration.as_millis(), - "block_root" => ?block_root, - "parent_hash" => ?block + info = "this is common behaviour for some builders and may not indicate an issue", + error = ?e, + relay_response_ms = duration.as_millis(), + ?block_root, + parent_hash = ?block .message() .execution_payload() .map(|payload| format!("{}", payload.parent_hash())) - .unwrap_or_else(|_| "unknown".to_string()) + .unwrap_or_else(|_| "unknown".to_string()), + "Builder failed to reveal payload" ) } } diff --git a/beacon_node/execution_layer/src/payload_status.rs b/beacon_node/execution_layer/src/payload_status.rs index cf0be8ed0d..bbfd30239d 100644 --- a/beacon_node/execution_layer/src/payload_status.rs +++ b/beacon_node/execution_layer/src/payload_status.rs @@ -1,6 +1,6 @@ use crate::engine_api::{Error as ApiError, PayloadStatusV1, PayloadStatusV1Status}; use crate::engines::EngineError; -use slog::{warn, Logger}; +use tracing::warn; use types::ExecutionBlockHash; /// Provides a simpler, easier to parse version of `PayloadStatusV1` for upstream users. @@ -26,15 +26,10 @@ pub enum PayloadStatus { pub fn process_payload_status( head_block_hash: ExecutionBlockHash, status: Result, - log: &Logger, ) -> Result { match status { Err(error) => { - warn!( - log, - "Error whilst processing payload status"; - "error" => ?error, - ); + warn!(?error, "Error whilst processing payload status"); Err(error) } Ok(response) => match &response.status { @@ -66,10 +61,9 @@ pub fn process_payload_status( // warning here. if response.latest_valid_hash.is_some() { warn!( - log, - "Malformed response from execution engine"; - "msg" => "expected a null latest_valid_hash", - "status" => ?response.status + msg = "expected a null latest_valid_hash", + status = ?response.status, + "Malformed response from execution engine" ) } @@ -82,10 +76,9 @@ pub fn process_payload_status( // warning here. if response.latest_valid_hash.is_some() { warn!( - log, - "Malformed response from execution engine"; - "msg" => "expected a null latest_valid_hash", - "status" => ?response.status + msg = "expected a null latest_valid_hash", + status = ?response.status, + "Malformed response from execution engine" ) } @@ -96,10 +89,9 @@ pub fn process_payload_status( // warning here. if response.latest_valid_hash.is_some() { warn!( - log, - "Malformed response from execution engine"; - "msg" => "expected a null latest_valid_hash", - "status" => ?response.status + msg = "expected a null latest_valid_hash", + status = ?response.status, + "Malformed response from execution engine" ) } diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 9fa375b375..81fb9bd7b8 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -84,14 +84,14 @@ impl Block { block_hash: block.block_hash, block_number: block.block_number, parent_hash: block.parent_hash, - total_difficulty: block.total_difficulty, + total_difficulty: Some(block.total_difficulty), timestamp: block.timestamp, }, Block::PoS(payload) => ExecutionBlock { block_hash: payload.block_hash(), block_number: payload.block_number(), parent_hash: payload.parent_hash(), - total_difficulty, + total_difficulty: Some(total_difficulty), timestamp: payload.timestamp(), }, } @@ -448,7 +448,7 @@ impl ExecutionBlockGenerator { if self .head_block .as_ref() - .map_or(true, |head| head.block_hash() == last_block_hash) + .is_none_or(|head| head.block_hash() == last_block_hash) { self.head_block = Some(block.clone()); } diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index f07ee7ac6f..fba34121a7 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -13,7 +13,6 @@ use eth2::{ use fork_choice::ForkchoiceUpdateParameters; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; -use slog::{debug, error, info, warn, Logger}; use ssz::Encode; use std::collections::HashMap; use std::fmt::Debug; @@ -24,6 +23,7 @@ use std::time::Duration; use task_executor::TaskExecutor; use tempfile::NamedTempFile; use tokio_stream::StreamExt; +use tracing::{debug, error, info, warn}; use tree_hash::TreeHash; use types::builder_bid::{ BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra, @@ -309,7 +309,6 @@ pub struct MockBuilder { max_bid: bool, /// A cache that stores the proposers index for a given epoch proposers_cache: Arc>>>, - log: Logger, } impl MockBuilder { @@ -331,8 +330,7 @@ impl MockBuilder { ..Default::default() }; - let el = - ExecutionLayer::from_config(config, executor.clone(), executor.log().clone()).unwrap(); + let el = ExecutionLayer::from_config(config, executor.clone()).unwrap(); let builder = MockBuilder::new( el, @@ -342,7 +340,6 @@ impl MockBuilder { false, spec, None, - executor.log().clone(), ); let host: Ipv4Addr = Ipv4Addr::LOCALHOST; let port = 0; @@ -359,16 +356,12 @@ impl MockBuilder { max_bid: bool, spec: Arc, sk: Option<&[u8]>, - log: Logger, ) -> Self { let builder_sk = if let Some(sk_bytes) = sk { match SecretKey::deserialize(sk_bytes) { Ok(sk) => sk, Err(_) => { - error!( - log, - "Invalid sk_bytes provided, generating random secret key" - ); + error!("Invalid sk_bytes provided, generating random secret key"); SecretKey::random() } } @@ -390,7 +383,6 @@ impl MockBuilder { apply_operations, max_bid, genesis_time: None, - log, } } @@ -425,18 +417,13 @@ impl MockBuilder { &self, registrations: Vec, ) -> Result<(), String> { - info!( - self.log, - "Registering validators"; - "count" => registrations.len(), - ); + info!(count = registrations.len(), "Registering validators"); for registration in registrations { if !registration.verify_signature(&self.spec) { error!( - self.log, - "Failed to register validator"; - "error" => "invalid signature", - "validator" => %registration.message.pubkey + error = "invalid signature", + validator = %registration.message.pubkey, + "Failed to register validator" ); return Err("invalid signature".to_string()); } @@ -472,9 +459,8 @@ impl MockBuilder { } }; info!( - self.log, - "Submitting blinded beacon block to builder"; - "block_hash" => %root + block_hash = %root, + "Submitting blinded beacon block to builder" ); let payload = self .el @@ -486,10 +472,9 @@ impl MockBuilder { .try_into_full_block(Some(payload.clone())) .ok_or("Internal error, just provided a payload")?; debug!( - self.log, - "Got full payload, sending to local beacon node for propagation"; - "txs_count" => payload.transactions().len(), - "blob_count" => blobs.as_ref().map(|b| b.commitments.len()) + txs_count = payload.transactions().len(), + blob_count = blobs.as_ref().map(|b| b.commitments.len()), + "Got full payload, sending to local beacon node for propagation" ); let publish_block_request = PublishBlockRequest::new( Arc::new(full_block), @@ -508,7 +493,7 @@ impl MockBuilder { parent_hash: ExecutionBlockHash, pubkey: PublicKeyBytes, ) -> Result, String> { - info!(self.log, "In get_header"); + info!("In get_header"); // Check if the pubkey has registered with the builder if required if self.validate_pubkey && !self.val_registration_cache.read().contains_key(&pubkey) { return Err("validator not registered with builder".to_string()); @@ -521,15 +506,12 @@ impl MockBuilder { let payload_parameters = match payload_parameters { Some(params) => params, None => { - warn!( - self.log, - "Payload params not cached for parent_hash {}", parent_hash - ); + warn!("Payload params not cached for parent_hash {}", parent_hash); self.get_payload_params(slot, None, pubkey, None).await? } }; - info!(self.log, "Got payload params"); + info!("Got payload params"); let fork = self.fork_name_at_slot(slot); let payload_response_type = self @@ -545,7 +527,7 @@ impl MockBuilder { .await .map_err(|e| format!("couldn't get payload {:?}", e))?; - info!(self.log, "Got payload message, fork {}", fork); + info!("Got payload message, fork {}", fork); let mut message = match payload_response_type { crate::GetPayloadResponseType::Full(payload_response) => { @@ -616,10 +598,10 @@ impl MockBuilder { }; if self.apply_operations { - info!(self.log, "Applying operations"); + info!("Applying operations"); self.apply_operations(&mut message); } - info!(self.log, "Signing builder message"); + info!("Signing builder message"); let mut signature = message.sign_builder_message(&self.builder_sk, &self.spec); @@ -627,7 +609,7 @@ impl MockBuilder { signature = Signature::empty(); }; let signed_bid = SignedBuilderBid { message, signature }; - info!(self.log, "Builder bid {:?}", &signed_bid.message.value()); + info!("Builder bid {:?}", &signed_bid.message.value()); Ok(signed_bid) } @@ -648,10 +630,7 @@ impl MockBuilder { /// Prepare the execution layer for payload creation every slot for the correct /// proposer index pub async fn prepare_execution_layer(&self) -> Result<(), String> { - info!( - self.log, - "Starting a task to prepare the execution layer"; - ); + info!("Starting a task to prepare the execution layer"); let mut head_event_stream = self .beacon_client .get_events::(&[EventTopic::Head]) @@ -662,9 +641,8 @@ impl MockBuilder { match event { EventKind::Head(head) => { debug!( - self.log, - "Got a new head event"; - "block_hash" => %head.block + block_hash = %head.block, + "Got a new head event" ); let next_slot = head.slot + 1; // Find the next proposer index from the cached data or through a beacon api call @@ -712,9 +690,8 @@ impl MockBuilder { } e => { warn!( - self.log, - "Got an unexpected event"; - "event" => %e.topic_name() + event = %e.topic_name(), + "Got an unexpected event" ); } } @@ -812,7 +789,6 @@ impl MockBuilder { ), None => { warn!( - self.log, "Validator not registered {}, using default fee recipient and gas limits", pubkey ); diff --git a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs index f45bfda9ff..cbe5e3ae98 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs @@ -76,8 +76,7 @@ impl MockExecutionLayer { suggested_fee_recipient: Some(Address::repeat_byte(42)), ..Default::default() }; - let el = - ExecutionLayer::from_config(config, executor.clone(), executor.log().clone()).unwrap(); + let el = ExecutionLayer::from_config(config, executor.clone()).unwrap(); Self { server, diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 75ff435886..17441a15fb 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -9,11 +9,11 @@ use bytes::Bytes; use execution_block_generator::PoWBlock; use handle_rpc::handle_rpc; use kzg::Kzg; -use logging::test_logger; + +use logging::create_test_tracing_subscriber; use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; use serde::{Deserialize, Serialize}; use serde_json::json; -use slog::{info, Logger}; use std::collections::HashMap; use std::convert::Infallible; use std::future::Future; @@ -21,6 +21,7 @@ use std::marker::PhantomData; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::sync::{Arc, LazyLock}; use tokio::{runtime, sync::oneshot}; +use tracing::info; use types::{ChainSpec, EthSpec, ExecutionBlockHash, Uint256}; use warp::{http::StatusCode, Filter, Rejection}; @@ -133,6 +134,7 @@ impl MockServer { spec: Arc, kzg: Option>, ) -> Self { + create_test_tracing_subscriber(); let MockExecutionConfig { jwt_key, terminal_difficulty, @@ -161,7 +163,6 @@ impl MockServer { let ctx: Arc> = Arc::new(Context { config: server_config, jwt_key, - log: test_logger(), last_echo_request: last_echo_request.clone(), execution_block_generator: RwLock::new(execution_block_generator), previous_request: <_>::default(), @@ -533,7 +534,7 @@ impl warp::reject::Reject for AuthError {} pub struct Context { pub config: Config, pub jwt_key: JwtKey, - pub log: Logger, + pub last_echo_request: Arc>>, pub execution_block_generator: RwLock>, pub preloaded_responses: Arc>>, @@ -671,7 +672,6 @@ pub fn serve( shutdown: impl Future + Send + Sync + 'static, ) -> Result<(SocketAddr, impl Future), Error> { let config = &ctx.config; - let log = ctx.log.clone(); let inner_ctx = ctx.clone(); let ctx_filter = warp::any().map(move || inner_ctx.clone()); @@ -751,9 +751,8 @@ pub fn serve( )?; info!( - log, - "Metrics HTTP server started"; - "listen_address" => listening_socket.to_string(), + listen_address = listening_socket.to_string(), + "Metrics HTTP server started" ); Ok((listening_socket, server)) diff --git a/beacon_node/genesis/Cargo.toml b/beacon_node/genesis/Cargo.toml index eeca393947..6ba8998a01 100644 --- a/beacon_node/genesis/Cargo.toml +++ b/beacon_node/genesis/Cargo.toml @@ -6,6 +6,7 @@ edition = { workspace = true } [dev-dependencies] eth1_test_rig = { workspace = true } +logging = { workspace = true } sensitive_url = { workspace = true } [dependencies] @@ -17,8 +18,8 @@ futures = { workspace = true } int_to_bytes = { workspace = true } merkle_proof = { workspace = true } rayon = { workspace = true } -slog = { workspace = true } state_processing = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } types = { workspace = true } diff --git a/beacon_node/genesis/src/eth1_genesis_service.rs b/beacon_node/genesis/src/eth1_genesis_service.rs index b5f4bd50ee..dede96512c 100644 --- a/beacon_node/genesis/src/eth1_genesis_service.rs +++ b/beacon_node/genesis/src/eth1_genesis_service.rs @@ -2,7 +2,6 @@ pub use crate::common::genesis_deposits; pub use eth1::Config as Eth1Config; use eth1::{DepositLog, Eth1Block, Service as Eth1Service}; -use slog::{debug, error, info, trace, Logger}; use state_processing::{ eth2_genesis_time, initialize_beacon_state_from_eth1, is_valid_genesis_state, per_block_processing::process_operations::apply_deposit, process_activations, @@ -13,6 +12,7 @@ use std::sync::{ }; use std::time::Duration; use tokio::time::sleep; +use tracing::{debug, error, info, trace}; use types::{BeaconState, ChainSpec, Deposit, Eth1Data, EthSpec, FixedBytesExtended, Hash256}; /// The number of blocks that are pulled per request whilst waiting for genesis. @@ -43,7 +43,7 @@ impl Eth1GenesisService { /// Creates a new service. Does not attempt to connect to the Eth1 node. /// /// Modifies the given `config` to make it more suitable to the task of listening to genesis. - pub fn new(config: Eth1Config, log: Logger, spec: Arc) -> Result { + pub fn new(config: Eth1Config, spec: Arc) -> Result { let config = Eth1Config { // Truncating the block cache makes searching for genesis more // complicated. @@ -65,7 +65,7 @@ impl Eth1GenesisService { }; Ok(Self { - eth1_service: Eth1Service::new(config, log, spec) + eth1_service: Eth1Service::new(config, spec) .map_err(|e| format!("Failed to create eth1 service: {:?}", e))?, stats: Arc::new(Statistics { highest_processed_block: AtomicU64::new(0), @@ -103,15 +103,11 @@ impl Eth1GenesisService { ) -> Result, String> { let eth1_service = &self.eth1_service; let spec = eth1_service.chain_spec(); - let log = ð1_service.log; let mut sync_blocks = false; let mut highest_processed_block = None; - info!( - log, - "Importing eth1 deposit logs"; - ); + info!("Importing eth1 deposit logs"); loop { let update_result = eth1_service @@ -120,11 +116,7 @@ impl Eth1GenesisService { .map_err(|e| format!("{:?}", e)); if let Err(e) = update_result { - error!( - log, - "Failed to update eth1 deposit cache"; - "error" => e - ) + error!(error = e, "Failed to update eth1 deposit cache") } self.stats @@ -135,19 +127,15 @@ impl Eth1GenesisService { if let Some(viable_eth1_block) = self .first_candidate_eth1_block(spec.min_genesis_active_validator_count as usize) { - info!( - log, - "Importing eth1 blocks"; - ); + info!("Importing eth1 blocks"); self.eth1_service.set_lowest_cached_block(viable_eth1_block); sync_blocks = true } else { info!( - log, - "Waiting for more deposits"; - "min_genesis_active_validators" => spec.min_genesis_active_validator_count, - "total_deposits" => eth1_service.deposit_cache_len(), - "valid_deposits" => eth1_service.get_raw_valid_signature_count(), + min_genesis_active_validators = spec.min_genesis_active_validator_count, + total_deposits = eth1_service.deposit_cache_len(), + valid_deposits = eth1_service.get_raw_valid_signature_count(), + "Waiting for more deposits" ); sleep(update_interval).await; @@ -160,19 +148,17 @@ impl Eth1GenesisService { let blocks_imported = match eth1_service.update_block_cache(None).await { Ok(outcome) => { debug!( - log, - "Imported eth1 blocks"; - "latest_block_timestamp" => eth1_service.latest_block_timestamp(), - "cache_head" => eth1_service.highest_safe_block(), - "count" => outcome.blocks_imported, + latest_block_timestamp = eth1_service.latest_block_timestamp(), + cache_head = eth1_service.highest_safe_block(), + count = outcome.blocks_imported, + "Imported eth1 blocks" ); outcome.blocks_imported } Err(e) => { error!( - log, - "Failed to update eth1 block cache"; - "error" => format!("{:?}", e) + error = ?e, + "Failed to update eth1 block cache" ); 0 } @@ -183,13 +169,12 @@ impl Eth1GenesisService { self.scan_new_blocks::(&mut highest_processed_block, spec)? { info!( - log, - "Genesis ceremony complete"; - "genesis_validators" => genesis_state + genesis_validators = genesis_state .get_active_validator_indices(E::genesis_epoch(), spec) .map_err(|e| format!("Genesis validators error: {:?}", e))? .len(), - "genesis_time" => genesis_state.genesis_time(), + genesis_time = genesis_state.genesis_time(), + "Genesis ceremony complete" ); break Ok(genesis_state); } @@ -207,21 +192,19 @@ impl Eth1GenesisService { // Indicate that we are awaiting adequate active validators. if (active_validator_count as u64) < spec.min_genesis_active_validator_count { info!( - log, - "Waiting for more validators"; - "min_genesis_active_validators" => spec.min_genesis_active_validator_count, - "active_validators" => active_validator_count, - "total_deposits" => total_deposit_count, - "valid_deposits" => eth1_service.get_valid_signature_count().unwrap_or(0), + min_genesis_active_validators = spec.min_genesis_active_validator_count, + active_validators = active_validator_count, + total_deposits = total_deposit_count, + valid_deposits = eth1_service.get_valid_signature_count().unwrap_or(0), + "Waiting for more validators" ); } } else { info!( - log, - "Waiting for adequate eth1 timestamp"; - "genesis_delay" => spec.genesis_delay, - "genesis_time" => spec.min_genesis_time, - "latest_eth1_timestamp" => latest_timestamp, + genesis_delay = spec.genesis_delay, + genesis_time = spec.min_genesis_time, + latest_eth1_timestamp = latest_timestamp, + "Waiting for adequate eth1 timestamp" ); } @@ -253,7 +236,6 @@ impl Eth1GenesisService { spec: &ChainSpec, ) -> Result>, String> { let eth1_service = &self.eth1_service; - let log = ð1_service.log; for block in eth1_service.blocks().read().iter() { // It's possible that the block and deposit caches aren't synced. Ignore any blocks @@ -263,7 +245,7 @@ impl Eth1GenesisService { // again later. if eth1_service .highest_safe_block() - .map_or(true, |n| block.number > n) + .is_none_or(|n| block.number > n) { continue; } @@ -286,12 +268,11 @@ impl Eth1GenesisService { // Ignore any block with an insufficient timestamp. if !timestamp_can_trigger_genesis(block.timestamp, spec)? { trace!( - log, - "Insufficient block timestamp"; - "genesis_delay" => spec.genesis_delay, - "min_genesis_time" => spec.min_genesis_time, - "eth1_block_timestamp" => block.timestamp, - "eth1_block_number" => block.number, + genesis_delay = spec.genesis_delay, + min_genesis_time = spec.min_genesis_time, + eth1_block_timestamp = block.timestamp, + eth1_block_number = block.number, + "Insufficient block timestamp" ); continue; } @@ -301,12 +282,11 @@ impl Eth1GenesisService { .unwrap_or(0); if (valid_signature_count as u64) < spec.min_genesis_active_validator_count { trace!( - log, - "Insufficient valid signatures"; - "genesis_delay" => spec.genesis_delay, - "valid_signature_count" => valid_signature_count, - "min_validator_count" => spec.min_genesis_active_validator_count, - "eth1_block_number" => block.number, + genesis_delay = spec.genesis_delay, + valid_signature_count = valid_signature_count, + min_validator_count = spec.min_genesis_active_validator_count, + eth1_block_number = block.number, + "Insufficient valid signatures" ); continue; } @@ -333,11 +313,11 @@ impl Eth1GenesisService { return Ok(Some(genesis_state)); } else { trace!( - log, - "Insufficient active validators"; - "min_genesis_active_validator_count" => format!("{}", spec.min_genesis_active_validator_count), - "active_validators" => active_validator_count, - "eth1_block_number" => block.number, + min_genesis_active_validator_count = + format!("{}", spec.min_genesis_active_validator_count), + active_validators = active_validator_count, + eth1_block_number = block.number, + "Insufficient active validators" ); } } diff --git a/beacon_node/genesis/tests/tests.rs b/beacon_node/genesis/tests/tests.rs index 6cc7517aa4..b5710e50fd 100644 --- a/beacon_node/genesis/tests/tests.rs +++ b/beacon_node/genesis/tests/tests.rs @@ -3,6 +3,7 @@ use environment::{Environment, EnvironmentBuilder}; use eth1::{Eth1Endpoint, DEFAULT_CHAIN_ID}; use eth1_test_rig::{AnvilEth1Instance, DelayThenDeposit, Middleware}; use genesis::{Eth1Config, Eth1GenesisService}; +use logging::create_test_tracing_subscriber; use sensitive_url::SensitiveUrl; use state_processing::is_valid_genesis_state; use std::sync::Arc; @@ -12,11 +13,10 @@ use types::{ }; pub fn new_env() -> Environment { + create_test_tracing_subscriber(); EnvironmentBuilder::minimal() .multi_threaded_tokio_runtime() .expect("should start tokio runtime") - .test_logger() - .expect("should start null logger") .build() .expect("should build env") } @@ -24,7 +24,6 @@ pub fn new_env() -> Environment { #[test] fn basic() { let env = new_env(); - let log = env.core_context().log().clone(); let mut spec = (*env.eth2_config().spec).clone(); spec.min_genesis_time = 0; spec.min_genesis_active_validator_count = 8; @@ -55,7 +54,6 @@ fn basic() { block_cache_truncation: None, ..Eth1Config::default() }, - log, spec.clone(), ) .unwrap(); diff --git a/beacon_node/http_api/Cargo.toml b/beacon_node/http_api/Cargo.toml index 10fd473337..a4352f1c3d 100644 --- a/beacon_node/http_api/Cargo.toml +++ b/beacon_node/http_api/Cargo.toml @@ -33,7 +33,6 @@ safe_arith = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } state_processing = { workspace = true } store = { workspace = true } @@ -42,6 +41,7 @@ system_health = { path = "../../common/system_health" } task_executor = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } types = { workspace = true } warp = { workspace = true } @@ -49,8 +49,8 @@ warp_utils = { workspace = true } [dev-dependencies] genesis = { workspace = true } -logging = { workspace = true } proto_array = { workspace = true } +serde_json = { workspace = true } [[test]] name = "bn_http_api_tests" diff --git a/beacon_node/http_api/src/aggregate_attestation.rs b/beacon_node/http_api/src/aggregate_attestation.rs index 94b6acd2e6..23af5b0cb5 100644 --- a/beacon_node/http_api/src/aggregate_attestation.rs +++ b/beacon_node/http_api/src/aggregate_attestation.rs @@ -18,13 +18,14 @@ pub fn get_aggregate_attestation( endpoint_version: EndpointVersion, chain: Arc>, ) -> Result, warp::reject::Rejection> { - if endpoint_version == V2 { + let fork_name = chain.spec.fork_name_at_slot::(slot); + let aggregate_attestation = if fork_name.electra_enabled() { let Some(committee_index) = committee_index else { return Err(warp_utils::reject::custom_bad_request( "missing committee index".to_string(), )); }; - let aggregate_attestation = chain + chain .get_aggregated_attestation_electra(slot, attestation_data_root, committee_index) .map_err(|e| { warp_utils::reject::custom_bad_request(format!( @@ -34,8 +35,22 @@ pub fn get_aggregate_attestation( })? .ok_or_else(|| { warp_utils::reject::custom_not_found("no matching aggregate found".to_string()) - })?; - let fork_name = chain.spec.fork_name_at_slot::(slot); + })? + } else { + chain + .get_pre_electra_aggregated_attestation_by_slot_and_root(slot, attestation_data_root) + .map_err(|e| { + warp_utils::reject::custom_bad_request(format!( + "unable to fetch aggregate: {:?}", + e + )) + })? + .ok_or_else(|| { + warp_utils::reject::custom_not_found("no matching aggregate found".to_string()) + })? + }; + + if endpoint_version == V2 { let fork_versioned_response = ForkVersionedResponse { version: Some(fork_name), metadata: EmptyMetadata {}, @@ -46,19 +61,7 @@ pub fn get_aggregate_attestation( fork_name, )) } else if endpoint_version == V1 { - let aggregate_attestation = chain - .get_pre_electra_aggregated_attestation_by_slot_and_root(slot, attestation_data_root) - .map_err(|e| { - warp_utils::reject::custom_bad_request(format!( - "unable to fetch aggregate: {:?}", - e - )) - })? - .map(GenericResponse::from) - .ok_or_else(|| { - warp_utils::reject::custom_not_found("no matching aggregate found".to_string()) - })?; - Ok(warp::reply::json(&aggregate_attestation).into_response()) + Ok(warp::reply::json(&GenericResponse::from(aggregate_attestation)).into_response()) } else { return Err(unsupported_version_rejection(endpoint_version)); } diff --git a/beacon_node/http_api/src/block_rewards.rs b/beacon_node/http_api/src/block_rewards.rs index 0cc878bb48..fbb16e9540 100644 --- a/beacon_node/http_api/src/block_rewards.rs +++ b/beacon_node/http_api/src/block_rewards.rs @@ -1,10 +1,10 @@ use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped}; use eth2::lighthouse::{BlockReward, BlockRewardsQuery}; use lru::LruCache; -use slog::{debug, warn, Logger}; use state_processing::BlockReplayer; use std::num::NonZeroUsize; use std::sync::Arc; +use tracing::{debug, warn}; use types::beacon_block::BlindedBeaconBlock; use types::non_zero_usize::new_non_zero_usize; use warp_utils::reject::{beacon_state_error, custom_bad_request, unhandled_error}; @@ -15,7 +15,6 @@ const STATE_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(2); pub fn get_block_rewards( query: BlockRewardsQuery, chain: Arc>, - log: Logger, ) -> Result, warp::Rejection> { let start_slot = query.start_slot; let end_slot = query.end_slot; @@ -81,12 +80,7 @@ pub fn get_block_rewards( .map_err(unhandled_error)?; if block_replayer.state_root_miss() { - warn!( - log, - "Block reward state root miss"; - "start_slot" => start_slot, - "end_slot" => end_slot, - ); + warn!(%start_slot, %end_slot, "Block reward state root miss"); } drop(block_replayer); @@ -98,7 +92,6 @@ pub fn get_block_rewards( pub fn compute_block_rewards( blocks: Vec>, chain: Arc>, - log: Logger, ) -> Result, warp::Rejection> { let mut block_rewards = Vec::with_capacity(blocks.len()); let mut state_cache = LruCache::new(STATE_CACHE_SIZE); @@ -110,18 +103,16 @@ pub fn compute_block_rewards( // Check LRU cache for a constructed state from a previous iteration. let state = if let Some(state) = state_cache.get(&(parent_root, block.slot())) { debug!( - log, - "Re-using cached state for block rewards"; - "parent_root" => ?parent_root, - "slot" => block.slot(), + ?parent_root, + slot = %block.slot(), + "Re-using cached state for block rewards" ); state } else { debug!( - log, - "Fetching state for block rewards"; - "parent_root" => ?parent_root, - "slot" => block.slot() + ?parent_root, + slot = %block.slot(), + "Fetching state for block rewards" ); let parent_block = chain .get_blinded_block(&parent_root) @@ -152,10 +143,9 @@ pub fn compute_block_rewards( if block_replayer.state_root_miss() { warn!( - log, - "Block reward state root miss"; - "parent_slot" => parent_block.slot(), - "slot" => block.slot(), + parent_slot = %parent_block.slot(), + slot = %block.slot(), + "Block reward state root miss" ); } diff --git a/beacon_node/http_api/src/database.rs b/beacon_node/http_api/src/database.rs index 8a50ec45b0..aa8b0e8ffc 100644 --- a/beacon_node/http_api/src/database.rs +++ b/beacon_node/http_api/src/database.rs @@ -1,17 +1,7 @@ use beacon_chain::store::metadata::CURRENT_SCHEMA_VERSION; use beacon_chain::{BeaconChain, BeaconChainTypes}; -use serde::Serialize; +use eth2::lighthouse::DatabaseInfo; use std::sync::Arc; -use store::{AnchorInfo, BlobInfo, Split, StoreConfig}; - -#[derive(Debug, Serialize)] -pub struct DatabaseInfo { - pub schema_version: u64, - pub config: StoreConfig, - pub split: Split, - pub anchor: AnchorInfo, - pub blob_info: BlobInfo, -} pub fn info( chain: Arc>, diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 7f9000b2bd..b6ad8da128 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -55,7 +55,7 @@ use health_metrics::observe::Observe; use lighthouse_network::rpc::methods::MetaData; use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage}; use lighthouse_version::version_with_platform; -use logging::SSELoggingComponents; +use logging::{crit, SSELoggingComponents}; use network::{NetworkMessage, NetworkSenders, ValidatorSubscriptionMessage}; use operation_pool::ReceivedPreCapella; use parking_lot::RwLock; @@ -64,7 +64,6 @@ pub use publish_blocks::{ }; use serde::{Deserialize, Serialize}; use serde_json::Value; -use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; use ssz::Encode; pub use state_id::StateId; @@ -84,6 +83,7 @@ use tokio_stream::{ wrappers::{errors::BroadcastStreamRecvError, BroadcastStream}, StreamExt, }; +use tracing::{debug, error, info, warn}; use types::{ fork_versioned_response::EmptyMetadata, Attestation, AttestationData, AttestationShufflingId, AttesterSlashing, BeaconStateError, ChainSpec, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, @@ -107,13 +107,6 @@ use warp_utils::{query::multi_key_query, reject::convert_rejection, uor::Unifyin const API_PREFIX: &str = "eth"; -/// If the node is within this many epochs from the head, we declare it to be synced regardless of -/// the network sync state. -/// -/// This helps prevent attacks where nodes can convince us that we're syncing some non-existent -/// finalized head. -const SYNC_TOLERANCE_EPOCHS: u64 = 8; - /// A custom type which allows for both unsecured and TLS-enabled HTTP servers. type HttpServer = (SocketAddr, Pin + Send>>); @@ -139,7 +132,6 @@ pub struct Context { pub beacon_processor_reprocess_send: Option>, pub eth1_service: Option, pub sse_logging_components: Option, - pub log: Logger, } /// Configuration for the HTTP server. @@ -155,7 +147,6 @@ pub struct Config { pub enable_beacon_processor: bool, #[serde(with = "eth2::types::serde_status_code")] pub duplicate_block_status_code: StatusCode, - pub enable_light_client_server: bool, pub target_peers: usize, } @@ -171,7 +162,6 @@ impl Default for Config { sse_capacity_multiplier: 1, enable_beacon_processor: true, duplicate_block_status_code: StatusCode::ACCEPTED, - enable_light_client_server: true, target_peers: 100, } } @@ -195,40 +185,6 @@ impl From for Error { } } -/// Creates a `warp` logging wrapper which we use to create `slog` logs. -pub fn slog_logging( - log: Logger, -) -> warp::filters::log::Log { - warp::log::custom(move |info| { - match info.status() { - status - if status == StatusCode::OK - || status == StatusCode::NOT_FOUND - || status == StatusCode::PARTIAL_CONTENT => - { - debug!( - log, - "Processed HTTP API request"; - "elapsed" => format!("{:?}", info.elapsed()), - "status" => status.to_string(), - "path" => info.path(), - "method" => info.method().to_string(), - ); - } - status => { - warn!( - log, - "Error processing HTTP API request"; - "elapsed" => format!("{:?}", info.elapsed()), - "status" => status.to_string(), - "path" => info.path(), - "method" => info.method().to_string(), - ); - } - }; - }) -} - /// Creates a `warp` logging wrapper which we use for Prometheus metrics (not necessarily logging, /// per say). pub fn prometheus_metrics() -> warp::filters::log::Log { @@ -296,18 +252,6 @@ pub fn prometheus_metrics() -> warp::filters::log::Log impl Filter + Clone { - warp::any() - .and_then(move || async move { - if is_enabled { - Ok(()) - } else { - Err(warp::reject::not_found()) - } - }) - .untuple_one() -} - /// Creates a server that will serve requests using information from `ctx`. /// /// The server will shut down gracefully when the `shutdown` future resolves. @@ -328,7 +272,6 @@ pub fn serve( shutdown: impl Future + Send + Sync + 'static, ) -> Result { let config = ctx.config.clone(); - let log = ctx.log.clone(); // Configure CORS. let cors_builder = { @@ -345,7 +288,7 @@ pub fn serve( // Sanity check. if !config.enabled { - crit!(log, "Cannot start disabled HTTP server"); + crit!("Cannot start disabled HTTP server"); return Err(Error::Other( "A disabled server should not be started".to_string(), )); @@ -473,7 +416,8 @@ pub fn serve( ) })?; - let tolerance = SYNC_TOLERANCE_EPOCHS * T::EthSpec::slots_per_epoch(); + let tolerance = + chain.config.sync_tolerance_epochs * T::EthSpec::slots_per_epoch(); if head_slot + tolerance >= current_slot { Ok(()) @@ -493,9 +437,17 @@ pub fn serve( }, ); - // Create a `warp` filter that provides access to the logger. - let inner_ctx = ctx.clone(); - let log_filter = warp::any().map(move || inner_ctx.log.clone()); + // Create a `warp` filter that returns 404s if the light client server is disabled. + let light_client_server_filter = + warp::any() + .and(chain_filter.clone()) + .then(|chain: Arc>| async move { + if chain.config.enable_light_client_server { + Ok(()) + } else { + Err(warp::reject::not_found()) + } + }); let inner_components = ctx.sse_logging_components.clone(); let sse_component_filter = warp::any().map(move || inner_components.clone()); @@ -1292,21 +1244,18 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |block_contents: PublishBlockRequest, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_block( None, ProvenancedBlock::local_from_publish_request(block_contents), chain, &network_tx, - log, BroadcastValidation::default(), duplicate_block_status_code, network_globals, @@ -1326,15 +1275,13 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |block_bytes: Bytes, consensus_version: ForkName, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block_contents = PublishBlockRequest::::from_ssz_bytes( &block_bytes, @@ -1348,7 +1295,6 @@ pub fn serve( ProvenancedBlock::local_from_publish_request(block_contents), chain, &network_tx, - log, BroadcastValidation::default(), duplicate_block_status_code, network_globals, @@ -1368,22 +1314,19 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |validation_level: api_types::BroadcastValidationQuery, block_contents: PublishBlockRequest, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_block( None, ProvenancedBlock::local_from_publish_request(block_contents), chain, &network_tx, - log, validation_level.broadcast_validation, duplicate_block_status_code, network_globals, @@ -1404,7 +1347,6 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |validation_level: api_types::BroadcastValidationQuery, block_bytes: Bytes, @@ -1412,8 +1354,7 @@ pub fn serve( task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block_contents = PublishBlockRequest::::from_ssz_bytes( &block_bytes, @@ -1427,7 +1368,6 @@ pub fn serve( ProvenancedBlock::local_from_publish_request(block_contents), chain, &network_tx, - log, validation_level.broadcast_validation, duplicate_block_status_code, network_globals, @@ -1451,20 +1391,17 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |block_contents: Arc>, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_blinded_block( block_contents, chain, &network_tx, - log, BroadcastValidation::default(), duplicate_block_status_code, network_globals, @@ -1484,14 +1421,12 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |block_bytes: Bytes, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block = SignedBlindedBeaconBlock::::from_ssz_bytes( &block_bytes, @@ -1505,7 +1440,6 @@ pub fn serve( block, chain, &network_tx, - log, BroadcastValidation::default(), duplicate_block_status_code, network_globals, @@ -1525,21 +1459,18 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |validation_level: api_types::BroadcastValidationQuery, blinded_block: Arc>, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_blinded_block( blinded_block, chain, &network_tx, - log, validation_level.broadcast_validation, duplicate_block_status_code, network_globals, @@ -1559,15 +1490,13 @@ pub fn serve( .and(chain_filter.clone()) .and(network_tx_filter.clone()) .and(network_globals.clone()) - .and(log_filter.clone()) .then( move |validation_level: api_types::BroadcastValidationQuery, block_bytes: Bytes, task_spawner: TaskSpawner, chain: Arc>, network_tx: UnboundedSender>, - network_globals: Arc>, - log: Logger| { + network_globals: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block = SignedBlindedBeaconBlock::::from_ssz_bytes( &block_bytes, @@ -1581,7 +1510,6 @@ pub fn serve( block, chain, &network_tx, - log, validation_level.broadcast_validation, duplicate_block_status_code, network_globals, @@ -1851,14 +1779,12 @@ pub fn serve( .and(warp_utils::json::json()) .and(network_tx_filter.clone()) .and(reprocess_send_filter.clone()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, attestations: Vec>, network_tx: UnboundedSender>, - reprocess_tx: Option>, - log: Logger| async move { + reprocess_tx: Option>| async move { let attestations = attestations.into_iter().map(Either::Left).collect(); let result = crate::publish_attestations::publish_attestations( task_spawner, @@ -1866,7 +1792,6 @@ pub fn serve( attestations, network_tx, reprocess_tx, - log, ) .await .map(|()| warp::reply::json(&())); @@ -1882,25 +1807,22 @@ pub fn serve( .and(optional_consensus_version_header_filter) .and(network_tx_filter.clone()) .and(reprocess_send_filter.clone()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, payload: Value, fork_name: Option, network_tx: UnboundedSender>, - reprocess_tx: Option>, - log: Logger| async move { + reprocess_tx: Option>| async move { let attestations = match crate::publish_attestations::deserialize_attestation_payload::( - payload, fork_name, &log, + payload, fork_name, ) { Ok(attestations) => attestations, Err(err) => { warn!( - log, - "Unable to deserialize attestation POST request"; - "error" => ?err + error = ?err, + "Unable to deserialize attestation POST request" ); return warp::reply::with_status( warp::reply::json( @@ -1918,7 +1840,6 @@ pub fn serve( attestations, network_tx, reprocess_tx, - log, ) .await .map(|()| warp::reply::json(&())); @@ -1939,10 +1860,10 @@ pub fn serve( query: api_types::AttestationPoolQuery| { task_spawner.blocking_response_task(Priority::P1, move || { let query_filter = |data: &AttestationData| { - query.slot.map_or(true, |slot| slot == data.slot) + query.slot.is_none_or(|slot| slot == data.slot) && query .committee_index - .map_or(true, |index| index == data.index) + .is_none_or(|index| index == data.index) }; let mut attestations = chain.op_pool.get_filtered_attestations(query_filter); @@ -2193,16 +2114,14 @@ pub fn serve( .and(warp::path::end()) .and(warp_utils::json::json()) .and(network_tx_filter.clone()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, signatures: Vec, - network_tx: UnboundedSender>, - log: Logger| { + network_tx: UnboundedSender>| { task_spawner.blocking_json_task(Priority::P0, move || { sync_committees::process_sync_committee_signatures( - signatures, network_tx, &chain, log, + signatures, network_tx, &chain, )?; Ok(api_types::GenericResponse::from(())) }) @@ -2230,13 +2149,11 @@ pub fn serve( .and(warp::path::end()) .and(warp_utils::json::json()) .and(network_tx_filter.clone()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, address_changes: Vec, - network_tx: UnboundedSender>, - log: Logger| { + network_tx: UnboundedSender>| { task_spawner.blocking_json_task(Priority::P0, move || { let mut failures = vec![]; @@ -2253,11 +2170,12 @@ pub fn serve( .to_execution_address; // New to P2P *and* op pool, gossip immediately if post-Capella. - let received_pre_capella = if chain.current_slot_is_post_capella().unwrap_or(false) { - ReceivedPreCapella::No - } else { - ReceivedPreCapella::Yes - }; + let received_pre_capella = + if chain.current_slot_is_post_capella().unwrap_or(false) { + ReceivedPreCapella::No + } else { + ReceivedPreCapella::Yes + }; if matches!(received_pre_capella, ReceivedPreCapella::No) { publish_pubsub_message( &network_tx, @@ -2268,32 +2186,29 @@ pub fn serve( } // Import to op pool (may return `false` if there's a race). - let imported = - chain.import_bls_to_execution_change(verified_address_change, received_pre_capella); + let imported = chain.import_bls_to_execution_change( + verified_address_change, + received_pre_capella, + ); info!( - log, - "Processed BLS to execution change"; - "validator_index" => validator_index, - "address" => ?address, - "published" => matches!(received_pre_capella, ReceivedPreCapella::No), - "imported" => imported, + %validator_index, + ?address, + published = + matches!(received_pre_capella, ReceivedPreCapella::No), + imported, + "Processed BLS to execution change" ); } Ok(ObservationOutcome::AlreadyKnown) => { - debug!( - log, - "BLS to execution change already known"; - "validator_index" => validator_index, - ); + debug!(%validator_index, "BLS to execution change already known"); } Err(e) => { warn!( - log, - "Invalid BLS to execution change"; - "validator_index" => validator_index, - "reason" => ?e, - "source" => "HTTP", + validator_index, + reason = ?e, + source = "HTTP", + "Invalid BLS to execution change" ); failures.push(api_types::Failure::new( index, @@ -2452,6 +2367,7 @@ pub fn serve( let beacon_light_client_path = eth_v1 .and(warp::path("beacon")) .and(warp::path("light_client")) + .and(light_client_server_filter) .and(chain_filter.clone()); // GET beacon/light_client/bootstrap/{block_root} @@ -2467,11 +2383,13 @@ pub fn serve( .and(warp::path::end()) .and(warp::header::optional::("accept")) .then( - |chain: Arc>, + |light_client_server_enabled: Result<(), Rejection>, + chain: Arc>, task_spawner: TaskSpawner, block_root: Hash256, accept_header: Option| { task_spawner.blocking_response_task(Priority::P1, move || { + light_client_server_enabled?; get_light_client_bootstrap::(chain, &block_root, accept_header) }) }, @@ -2485,10 +2403,12 @@ pub fn serve( .and(warp::path::end()) .and(warp::header::optional::("accept")) .then( - |chain: Arc>, + |light_client_server_enabled: Result<(), Rejection>, + chain: Arc>, task_spawner: TaskSpawner, accept_header: Option| { task_spawner.blocking_response_task(Priority::P1, move || { + light_client_server_enabled?; let update = chain .light_client_server_cache .get_latest_optimistic_update() @@ -2532,10 +2452,12 @@ pub fn serve( .and(warp::path::end()) .and(warp::header::optional::("accept")) .then( - |chain: Arc>, + |light_client_server_enabled: Result<(), Rejection>, + chain: Arc>, task_spawner: TaskSpawner, accept_header: Option| { task_spawner.blocking_response_task(Priority::P1, move || { + light_client_server_enabled?; let update = chain .light_client_server_cache .get_latest_finality_update() @@ -2580,11 +2502,13 @@ pub fn serve( .and(warp::query::()) .and(warp::header::optional::("accept")) .then( - |chain: Arc>, + |light_client_server_enabled: Result<(), Rejection>, + chain: Arc>, task_spawner: TaskSpawner, query: LightClientUpdatesQuery, accept_header: Option| { task_spawner.blocking_response_task(Priority::P1, move || { + light_client_server_enabled?; get_light_client_updates::(chain, query, accept_header) }) }, @@ -2657,17 +2581,15 @@ pub fn serve( .and(block_id_or_err) .and(warp::path::end()) .and(warp_utils::json::json()) - .and(log_filter.clone()) .then( |task_spawner: TaskSpawner, chain: Arc>, block_id: BlockId, - validators: Vec, - log: Logger| { + validators: Vec| { task_spawner.blocking_json_task(Priority::P1, move || { let (rewards, execution_optimistic, finalized) = sync_committee_rewards::compute_sync_committee_rewards( - chain, block_id, validators, log, + chain, block_id, validators, )?; Ok(api_types::GenericResponse::from(rewards)).map(|resp| { @@ -2754,14 +2676,12 @@ pub fn serve( .and(warp::header::optional::("accept")) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |endpoint_version: EndpointVersion, state_id: StateId, accept_header: Option, task_spawner: TaskSpawner, - chain: Arc>, - log: Logger| { + chain: Arc>| { task_spawner.blocking_response_task(Priority::P1, move || match accept_header { Some(api_types::Accept::Ssz) => { // We can ignore the optimistic status for the "fork" since it's a @@ -2776,10 +2696,9 @@ pub fn serve( let response_bytes = state.as_ssz_bytes(); drop(timer); debug!( - log, - "HTTP state load"; - "total_time_ms" => t.elapsed().as_millis(), - "target_slot" => state.slot() + total_time_ms = t.elapsed().as_millis(), + target_slot = %state.slot(), + "HTTP state load" ); Response::builder() @@ -3108,9 +3027,9 @@ pub fn serve( peer_id: peer_id.to_string(), enr: peer_info.enr().map(|enr| enr.to_base64()), last_seen_p2p_address: address, - direction: api_types::PeerDirection::from((*dir).clone()), - state: api_types::PeerState::from( - peer_info.connection_status().clone(), + direction: api_types::PeerDirection::from_connection_direction(dir), + state: api_types::PeerState::from_peer_connection_status( + peer_info.connection_status(), ), })); } @@ -3153,16 +3072,17 @@ pub fn serve( // the eth2 API spec implies only peers we have been connected to at some point should be included. if let Some(dir) = peer_info.connection_direction() { - let direction = api_types::PeerDirection::from((*dir).clone()); - let state = api_types::PeerState::from( - peer_info.connection_status().clone(), + let direction = + api_types::PeerDirection::from_connection_direction(dir); + let state = api_types::PeerState::from_peer_connection_status( + peer_info.connection_status(), ); - let state_matches = query.state.as_ref().map_or(true, |states| { + let state_matches = query.state.as_ref().is_none_or(|states| { states.iter().any(|state_param| *state_param == state) }); let direction_matches = - query.direction.as_ref().map_or(true, |directions| { + query.direction.as_ref().is_none_or(|directions| { directions.iter().any(|dir_param| *dir_param == direction) }); @@ -3208,8 +3128,9 @@ pub fn serve( .read() .peers() .for_each(|(_, peer_info)| { - let state = - api_types::PeerState::from(peer_info.connection_status().clone()); + let state = api_types::PeerState::from_peer_connection_status( + peer_info.connection_status(), + ); match state { api_types::PeerState::Connected => connected += 1, api_types::PeerState::Connecting => connecting += 1, @@ -3245,16 +3166,14 @@ pub fn serve( .and(not_while_syncing_filter.clone()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |epoch: Epoch, not_synced_filter: Result<(), Rejection>, task_spawner: TaskSpawner, - chain: Arc>, - log: Logger| { + chain: Arc>| { task_spawner.blocking_json_task(Priority::P0, move || { not_synced_filter?; - proposer_duties::proposer_duties(epoch, &chain, &log) + proposer_duties::proposer_duties(epoch, &chain) }) }, ); @@ -3274,7 +3193,6 @@ pub fn serve( .and(warp::query::()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |endpoint_version: EndpointVersion, slot: Slot, @@ -3282,14 +3200,9 @@ pub fn serve( not_synced_filter: Result<(), Rejection>, query: api_types::ValidatorBlocksQuery, task_spawner: TaskSpawner, - chain: Arc>, - log: Logger| { + chain: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { - debug!( - log, - "Block production request from HTTP API"; - "slot" => slot - ); + debug!(?slot, "Block production request from HTTP API"); not_synced_filter?; @@ -3496,7 +3409,6 @@ pub fn serve( .and(chain_filter.clone()) .and(warp_utils::json::json()) .and(network_tx_filter.clone()) - .and(log_filter.clone()) .then( // V1 and V2 are identical except V2 has a consensus version header in the request. // We only require this header for SSZ deserialization, which isn't supported for @@ -3506,7 +3418,7 @@ pub fn serve( task_spawner: TaskSpawner, chain: Arc>, aggregates: Vec>, - network_tx: UnboundedSender>, log: Logger| { + network_tx: UnboundedSender>| { task_spawner.blocking_json_task(Priority::P0, move || { not_synced_filter?; let seen_timestamp = timestamp_now(); @@ -3553,13 +3465,13 @@ pub fn serve( // aggregate has been successfully published by some other node. Err(AttnError::AggregatorAlreadyKnown(_)) => continue, Err(e) => { - error!(log, - "Failure verifying aggregate and proofs"; - "error" => format!("{:?}", e), - "request_index" => index, - "aggregator_index" => aggregate.message().aggregator_index(), - "attestation_index" => aggregate.message().aggregate().committee_index(), - "attestation_slot" => aggregate.message().aggregate().data().slot, + error!( + error = ?e, + request_index = index, + aggregator_index = aggregate.message().aggregator_index(), + attestation_index = aggregate.message().aggregate().committee_index(), + attestation_slot = %aggregate.message().aggregate().data().slot, + "Failure verifying aggregate and proofs" ); failures.push(api_types::Failure::new(index, format!("Verification: {:?}", e))); } @@ -3574,22 +3486,21 @@ pub fn serve( // Import aggregate attestations for (index, verified_aggregate) in verified_aggregates { if let Err(e) = chain.apply_attestation_to_fork_choice(&verified_aggregate) { - error!(log, - "Failure applying verified aggregate attestation to fork choice"; - "error" => format!("{:?}", e), - "request_index" => index, - "aggregator_index" => verified_aggregate.aggregate().message().aggregator_index(), - "attestation_index" => verified_aggregate.attestation().committee_index(), - "attestation_slot" => verified_aggregate.attestation().data().slot, + error!( + error = ?e, + request_index = index, + aggregator_index = verified_aggregate.aggregate().message().aggregator_index(), + attestation_index = verified_aggregate.attestation().committee_index(), + attestation_slot = %verified_aggregate.attestation().data().slot, + "Failure applying verified aggregate attestation to fork choice" ); failures.push(api_types::Failure::new(index, format!("Fork choice: {:?}", e))); } if let Err(e) = chain.add_to_block_inclusion_pool(verified_aggregate) { warn!( - log, - "Could not add verified aggregate attestation to the inclusion pool"; - "error" => ?e, - "request_index" => index, + error = ?e, + request_index = index, + "Could not add verified aggregate attestation to the inclusion pool" ); failures.push(api_types::Failure::new(index, format!("Op pool: {:?}", e))); } @@ -3615,21 +3526,18 @@ pub fn serve( .and(chain_filter.clone()) .and(warp_utils::json::json()) .and(network_tx_filter) - .and(log_filter.clone()) .then( |not_synced_filter: Result<(), Rejection>, task_spawner: TaskSpawner, chain: Arc>, contributions: Vec>, - network_tx: UnboundedSender>, - log: Logger| { + network_tx: UnboundedSender>| { task_spawner.blocking_json_task(Priority::P0, move || { not_synced_filter?; sync_committees::process_signed_contribution_and_proofs( contributions, network_tx, &chain, - log, )?; Ok(api_types::GenericResponse::from(())) }) @@ -3645,13 +3553,11 @@ pub fn serve( .and(validator_subscription_tx_filter.clone()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |subscriptions: Vec, validator_subscription_tx: Sender, task_spawner: TaskSpawner, - chain: Arc>, - log: Logger| { + chain: Arc>| { task_spawner.blocking_json_task(Priority::P0, move || { let subscriptions: std::collections::BTreeSet<_> = subscriptions .iter() @@ -3672,10 +3578,9 @@ pub fn serve( ValidatorSubscriptionMessage::AttestationSubscribe { subscriptions }; if let Err(e) = validator_subscription_tx.try_send(message) { warn!( - log, - "Unable to process committee subscriptions"; - "info" => "the host may be overloaded or resource-constrained", - "error" => ?e, + info = "the host may be overloaded or resource-constrained", + error = ?e, + "Unable to process committee subscriptions" ); return Err(warp_utils::reject::custom_server_error( "unable to queue subscription, host may be overloaded or shutting down" @@ -3696,13 +3601,11 @@ pub fn serve( .and(not_while_syncing_filter.clone()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .and(warp_utils::json::json()) .then( |not_synced_filter: Result<(), Rejection>, task_spawner: TaskSpawner, chain: Arc>, - log: Logger, preparation_data: Vec| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { not_synced_filter?; @@ -3716,9 +3619,8 @@ pub fn serve( let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); debug!( - log, - "Received proposer preparation data"; - "count" => preparation_data.len(), + count = preparation_data.len(), + "Received proposer preparation data" ); execution_layer @@ -3750,12 +3652,10 @@ pub fn serve( .and(warp::path::end()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .and(warp_utils::json::json()) .then( |task_spawner: TaskSpawner, chain: Arc>, - log: Logger, register_val_data: Vec| async { let (tx, rx) = oneshot::channel(); @@ -3774,9 +3674,8 @@ pub fn serve( let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch()); debug!( - log, - "Received register validator request"; - "count" => register_val_data.len(), + count = register_val_data.len(), + "Received register validator request" ); let head_snapshot = chain.head_snapshot(); @@ -3851,9 +3750,8 @@ pub fn serve( })?; info!( - log, - "Forwarding register validator request to connected builder"; - "count" => filtered_registration_data.len(), + count = filtered_registration_data.len(), + "Forwarding register validator request to connected builder" ); // It's a waste of a `BeaconProcessor` worker to just @@ -3878,10 +3776,9 @@ pub fn serve( .map(|resp| warp::reply::json(&resp).into_response()) .map_err(|e| { warn!( - log, - "Relay error when registering validator(s)"; - "num_registrations" => filtered_registration_data.len(), - "error" => ?e + num_registrations = filtered_registration_data.len(), + error = ?e, + "Relay error when registering validator(s)" ); // Forward the HTTP status code if we are able to, otherwise fall back // to a server error. @@ -3935,13 +3832,11 @@ pub fn serve( .and(validator_subscription_tx_filter) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) .then( |subscriptions: Vec, validator_subscription_tx: Sender, task_spawner: TaskSpawner, chain: Arc>, - log: Logger | { task_spawner.blocking_json_task(Priority::P0, move || { for subscription in subscriptions { @@ -3955,10 +3850,9 @@ pub fn serve( }; if let Err(e) = validator_subscription_tx.try_send(message) { warn!( - log, - "Unable to process sync subscriptions"; - "info" => "the host may be overloaded or resource-constrained", - "error" => ?e + info = "the host may be overloaded or resource-constrained", + error = ?e, + "Unable to process sync subscriptions" ); return Err(warp_utils::reject::custom_server_error( "unable to queue subscription, host may be overloaded or shutting down".to_string(), @@ -4191,18 +4085,15 @@ pub fn serve( |task_spawner: TaskSpawner, network_globals: Arc>| { task_spawner.blocking_json_task(Priority::P1, move || { - let mut peers = vec![]; - for (peer_id, peer_info) in network_globals.peers.read().peers() { - peers.push(eth2::lighthouse::Peer { + Ok(network_globals + .peers + .read() + .peers() + .map(|(peer_id, peer_info)| eth2::lighthouse::Peer { peer_id: peer_id.to_string(), - peer_info: serde_json::to_value(peer_info).map_err(|e| { - warp_utils::reject::custom_not_found(format!( - "unable to serialize peer_info: {e:?}", - )) - })?, - }); - } - Ok(peers) + peer_info: peer_info.clone(), + }) + .collect::>()) }) }, ); @@ -4218,18 +4109,15 @@ pub fn serve( |task_spawner: TaskSpawner, network_globals: Arc>| { task_spawner.blocking_json_task(Priority::P1, move || { - let mut peers = vec![]; - for (peer_id, peer_info) in network_globals.peers.read().connected_peers() { - peers.push(eth2::lighthouse::Peer { + Ok(network_globals + .peers + .read() + .connected_peers() + .map(|(peer_id, peer_info)| eth2::lighthouse::Peer { peer_id: peer_id.to_string(), - peer_info: serde_json::to_value(peer_info).map_err(|e| { - warp_utils::reject::custom_not_found(format!( - "unable to serialize peer_info: {e:?}", - )) - })?, - }); - } - Ok(peers) + peer_info: peer_info.clone(), + }) + .collect::>()) }) }, ); @@ -4434,10 +4322,9 @@ pub fn serve( .and(warp::path::end()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) - .then(|query, task_spawner: TaskSpawner, chain, log| { + .then(|query, task_spawner: TaskSpawner, chain| { task_spawner.blocking_json_task(Priority::P1, move || { - block_rewards::get_block_rewards(query, chain, log) + block_rewards::get_block_rewards(query, chain) }) }); @@ -4449,14 +4336,11 @@ pub fn serve( .and(warp::path::end()) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) - .and(log_filter.clone()) - .then( - |blocks, task_spawner: TaskSpawner, chain, log| { - task_spawner.blocking_json_task(Priority::P1, move || { - block_rewards::compute_block_rewards(blocks, chain, log) - }) - }, - ); + .then(|blocks, task_spawner: TaskSpawner, chain| { + task_spawner.blocking_json_task(Priority::P1, move || { + block_rewards::compute_block_rewards(blocks, chain) + }) + }); // GET lighthouse/analysis/attestation_performance/{index} let get_lighthouse_attestation_performance = warp::path("lighthouse") @@ -4634,7 +4518,9 @@ pub fn serve( match msg { Ok(data) => { // Serialize to json - match data.to_json_string() { + match serde_json::to_string(&data) + .map_err(|e| format!("{:?}", e)) + { // Send the json as a Server Side Event Ok(json) => Ok(Event::default().data(json)), Err(e) => { @@ -4727,22 +4613,10 @@ pub fn serve( .uor(get_lighthouse_database_info) .uor(get_lighthouse_block_rewards) .uor(get_lighthouse_attestation_performance) - .uor( - enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_optimistic_update), - ) - .uor( - enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_finality_update), - ) - .uor( - enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_bootstrap), - ) - .uor( - enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_updates), - ) + .uor(get_beacon_light_client_optimistic_update) + .uor(get_beacon_light_client_finality_update) + .uor(get_beacon_light_client_bootstrap) + .uor(get_beacon_light_client_updates) .uor(get_lighthouse_block_packing_efficiency) .uor(get_lighthouse_merge_readiness) .uor(get_events) @@ -4794,7 +4668,6 @@ pub fn serve( ), ) .recover(warp_utils::reject::handle_rejection) - .with(slog_logging(log.clone())) .with(prometheus_metrics()) // Add a `Server` header. .map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform())) @@ -4812,7 +4685,7 @@ pub fn serve( shutdown.await; })?; - info!(log, "HTTP API is being served over TLS";); + info!("HTTP API is being served over TLS"); (socket, Box::pin(server)) } @@ -4826,9 +4699,8 @@ pub fn serve( }; info!( - log, - "HTTP API started"; - "listen_address" => %http_server.0, + listen_address = %http_server.0, + "HTTP API started" ); Ok(http_server) diff --git a/beacon_node/http_api/src/produce_block.rs b/beacon_node/http_api/src/produce_block.rs index 0e24e8f175..22d6f0e7ae 100644 --- a/beacon_node/http_api/src/produce_block.rs +++ b/beacon_node/http_api/src/produce_block.rs @@ -147,7 +147,7 @@ pub async fn produce_blinded_block_v2( .produce_block_with_verification( randao_reveal, slot, - query.graffiti.map(Into::into), + query.graffiti, randao_verification, None, BlockProductionVersion::BlindedV2, @@ -178,7 +178,7 @@ pub async fn produce_block_v2( .produce_block_with_verification( randao_reveal, slot, - query.graffiti.map(Into::into), + query.graffiti, randao_verification, None, BlockProductionVersion::FullV2, diff --git a/beacon_node/http_api/src/proposer_duties.rs b/beacon_node/http_api/src/proposer_duties.rs index c4945df9d7..971571f487 100644 --- a/beacon_node/http_api/src/proposer_duties.rs +++ b/beacon_node/http_api/src/proposer_duties.rs @@ -7,9 +7,9 @@ use beacon_chain::{ }; use eth2::types::{self as api_types}; use safe_arith::SafeArith; -use slog::{debug, Logger}; use slot_clock::SlotClock; use std::cmp::Ordering; +use tracing::debug; use types::{Epoch, EthSpec, Hash256, Slot}; /// The struct that is returned to the requesting HTTP client. @@ -19,7 +19,6 @@ type ApiDuties = api_types::DutiesResponse>; pub fn proposer_duties( request_epoch: Epoch, chain: &BeaconChain, - log: &Logger, ) -> Result { let current_epoch = chain .slot_clock @@ -52,11 +51,7 @@ pub fn proposer_duties( if let Some(duties) = try_proposer_duties_from_cache(request_epoch, chain)? { Ok(duties) } else { - debug!( - log, - "Proposer cache miss"; - "request_epoch" => request_epoch, - ); + debug!(%request_epoch, "Proposer cache miss"); compute_and_cache_proposer_duties(request_epoch, chain) } } else if request_epoch diff --git a/beacon_node/http_api/src/publish_attestations.rs b/beacon_node/http_api/src/publish_attestations.rs index 10d13e09a5..cd5e912bdf 100644 --- a/beacon_node/http_api/src/publish_attestations.rs +++ b/beacon_node/http_api/src/publish_attestations.rs @@ -45,7 +45,6 @@ use eth2::types::Failure; use lighthouse_network::PubsubMessage; use network::NetworkMessage; use serde_json::Value; -use slog::{debug, error, warn, Logger}; use std::borrow::Cow; use std::sync::Arc; use std::time::Duration; @@ -53,6 +52,7 @@ use tokio::sync::{ mpsc::{Sender, UnboundedSender}, oneshot, }; +use tracing::{debug, error, warn}; use types::{Attestation, EthSpec, ForkName, SingleAttestation}; // Error variants are only used in `Debug` and considered `dead_code` by the compiler. @@ -80,14 +80,10 @@ enum PublishAttestationResult { pub fn deserialize_attestation_payload( payload: Value, fork_name: Option, - log: &Logger, ) -> Result, SingleAttestation>>, Error> { if fork_name.is_some_and(|fork_name| fork_name.electra_enabled()) || fork_name.is_none() { if fork_name.is_none() { - warn!( - log, - "No Consensus Version header specified."; - ); + warn!("No Consensus Version header specified."); } Ok(serde_json::from_value::>(payload) @@ -111,7 +107,6 @@ fn verify_and_publish_attestation( either_attestation: &Either, SingleAttestation>, seen_timestamp: Duration, network_tx: &UnboundedSender>, - log: &Logger, ) -> Result<(), Error> { let attestation = convert_to_attestation(chain, either_attestation)?; let verified_attestation = chain @@ -157,16 +152,14 @@ fn verify_and_publish_attestation( if let Err(e) = &fc_result { warn!( - log, - "Attestation invalid for fork choice"; - "err" => ?e, + err = ?e, + "Attestation invalid for fork choice" ); } if let Err(e) = &naive_aggregation_result { warn!( - log, - "Attestation invalid for aggregation"; - "err" => ?e + err = ?e, + "Attestation invalid for aggregation" ); } @@ -232,7 +225,6 @@ pub async fn publish_attestations( attestations: Vec, SingleAttestation>>, network_tx: UnboundedSender>, reprocess_send: Option>, - log: Logger, ) -> Result<(), warp::Rejection> { // Collect metadata about attestations which we'll use to report failures. We need to // move the `attestations` vec into the blocking task, so this small overhead is unavoidable. @@ -246,7 +238,6 @@ pub async fn publish_attestations( // Gossip validate and publish attestations that can be immediately processed. let seen_timestamp = timestamp_now(); - let inner_log = log.clone(); let mut prelim_results = task_spawner .blocking_task(Priority::P0, move || { Ok(attestations @@ -257,7 +248,6 @@ pub async fn publish_attestations( &attestation, seen_timestamp, &network_tx, - &inner_log, ) { Ok(()) => PublishAttestationResult::Success, Err(Error::Validation(AttestationError::UnknownHeadBlock { @@ -270,14 +260,12 @@ pub async fn publish_attestations( let (tx, rx) = oneshot::channel(); let reprocess_chain = chain.clone(); let reprocess_network_tx = network_tx.clone(); - let reprocess_log = inner_log.clone(); let reprocess_fn = move || { let result = verify_and_publish_attestation( &reprocess_chain, &attestation, seen_timestamp, &reprocess_network_tx, - &reprocess_log, ); // Ignore failure on the oneshot that reports the result. This // shouldn't happen unless some catastrophe befalls the waiting @@ -330,10 +318,9 @@ pub async fn publish_attestations( for (i, reprocess_result) in reprocess_indices.into_iter().zip(reprocess_results) { let Some(result_entry) = prelim_results.get_mut(i) else { error!( - log, - "Unreachable case in attestation publishing"; - "case" => "prelim out of bounds", - "request_index" => i, + case = "prelim out of bounds", + request_index = i, + "Unreachable case in attestation publishing" ); continue; }; @@ -361,39 +348,35 @@ pub async fn publish_attestations( Some(PublishAttestationResult::Failure(e)) => { if let Some((slot, committee_index)) = attestation_metadata.get(index) { error!( - log, - "Failure verifying attestation for gossip"; - "error" => ?e, - "request_index" => index, - "committee_index" => committee_index, - "attestation_slot" => slot, + error = ?e, + request_index = index, + committee_index, + attestation_slot = %slot, + "Failure verifying attestation for gossip" ); failures.push(Failure::new(index, format!("{e:?}"))); } else { error!( - log, - "Unreachable case in attestation publishing"; - "case" => "out of bounds", - "request_index" => index + case = "out of bounds", + request_index = index, + "Unreachable case in attestation publishing" ); failures.push(Failure::new(index, "metadata logic error".into())); } } Some(PublishAttestationResult::Reprocessing(_)) => { error!( - log, - "Unreachable case in attestation publishing"; - "case" => "reprocessing", - "request_index" => index + case = "reprocessing", + request_index = index, + "Unreachable case in attestation publishing" ); failures.push(Failure::new(index, "reprocess logic error".into())); } None => { error!( - log, - "Unreachable case in attestation publishing"; - "case" => "result is None", - "request_index" => index + case = "result is None", + request_index = index, + "Unreachable case in attestation publishing" ); failures.push(Failure::new(index, "result logic error".into())); } @@ -402,9 +385,8 @@ pub async fn publish_attestations( if num_already_known > 0 { debug!( - log, - "Some unagg attestations already known"; - "count" => num_already_known + count = num_already_known, + "Some unagg attestations already known" ); } diff --git a/beacon_node/http_api/src/publish_blocks.rs b/beacon_node/http_api/src/publish_blocks.rs index 60d4b2f16e..24af16680e 100644 --- a/beacon_node/http_api/src/publish_blocks.rs +++ b/beacon_node/http_api/src/publish_blocks.rs @@ -18,13 +18,13 @@ use futures::TryFutureExt; use lighthouse_network::{NetworkGlobals, PubsubMessage}; use network::NetworkMessage; use rand::prelude::SliceRandom; -use slog::{debug, error, info, warn, Logger}; use slot_clock::SlotClock; use std::marker::PhantomData; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc::UnboundedSender; +use tracing::{debug, error, info, warn}; use tree_hash::TreeHash; use types::{ AbstractExecPayload, BeaconBlockRef, BlobSidecar, BlobsList, BlockImportSource, @@ -80,12 +80,13 @@ pub async fn publish_block>( provenanced_block: ProvenancedBlock, chain: Arc>, network_tx: &UnboundedSender>, - log: Logger, validation_level: BroadcastValidation, duplicate_status_code: StatusCode, network_globals: Arc>, ) -> Result { let seen_timestamp = timestamp_now(); + let block_publishing_delay_for_testing = chain.config.block_publishing_delay; + let data_column_publishing_delay_for_testing = chain.config.data_column_publishing_delay; let (unverified_block, unverified_blobs, is_locally_built_block) = match provenanced_block { ProvenancedBlock::Local(block, blobs, _) => (block, blobs, true), @@ -97,12 +98,12 @@ pub async fn publish_block>( "builder" }; let block = unverified_block.inner_block(); - debug!(log, "Signed block received in HTTP API"; "slot" => block.slot()); + + debug!(slot = %block.slot(), "Signed block received in HTTP API"); /* actually publish a block */ let publish_block_p2p = move |block: Arc>, sender, - log, seen_timestamp| -> Result<(), BlockError> { let publish_timestamp = timestamp_now(); @@ -117,10 +118,9 @@ pub async fn publish_block>( ); info!( - log, - "Signed block published to network via HTTP API"; - "slot" => block.slot(), - "publish_delay_ms" => publish_delay.as_millis(), + slot = %block.slot(), + publish_delay_ms = publish_delay.as_millis(), + "Signed block published to network via HTTP API" ); crate::publish_pubsub_message(&sender, PubsubMessage::BeaconBlock(block.clone())) @@ -134,7 +134,7 @@ pub async fn publish_block>( let sender_clone = network_tx.clone(); let build_sidecar_task_handle = - spawn_build_data_sidecar_task(chain.clone(), block.clone(), unverified_blobs, log.clone())?; + spawn_build_data_sidecar_task(chain.clone(), block.clone(), unverified_blobs)?; // Gossip verify the block and blobs/data columns separately. let gossip_verified_block_result = unverified_block.into_gossip_verified_block(&chain); @@ -147,13 +147,15 @@ pub async fn publish_block>( let should_publish_block = gossip_verified_block_result.is_ok(); if BroadcastValidation::Gossip == validation_level && should_publish_block { - publish_block_p2p( - block.clone(), - sender_clone.clone(), - log.clone(), - seen_timestamp, - ) - .map_err(|_| warp_utils::reject::custom_server_error("unable to publish".into()))?; + if let Some(block_publishing_delay) = block_publishing_delay_for_testing { + debug!( + ?block_publishing_delay, + "Publishing block with artificial delay" + ); + tokio::time::sleep(block_publishing_delay).await; + } + publish_block_p2p(block.clone(), sender_clone.clone(), seen_timestamp) + .map_err(|_| warp_utils::reject::custom_server_error("unable to publish".into()))?; } let publish_fn_completed = Arc::new(AtomicBool::new(false)); @@ -165,15 +167,13 @@ pub async fn publish_block>( BroadcastValidation::Consensus => publish_block_p2p( block_to_publish.clone(), sender_clone.clone(), - log.clone(), seen_timestamp, )?, BroadcastValidation::ConsensusAndEquivocation => { - check_slashable(&chain, block_root, &block_to_publish, &log)?; + check_slashable(&chain, block_root, &block_to_publish)?; publish_block_p2p( block_to_publish.clone(), sender_clone.clone(), - log.clone(), seen_timestamp, )?; } @@ -196,17 +196,29 @@ pub async fn publish_block>( return if let BroadcastValidation::Gossip = validation_level { Err(warp_utils::reject::broadcast_without_import(msg)) } else { - error!( - log, - "Invalid blob provided to HTTP API"; - "reason" => &msg - ); + error!(reason = &msg, "Invalid blob provided to HTTP API"); Err(warp_utils::reject::custom_bad_request(msg)) }; } } if gossip_verified_columns.iter().map(Option::is_some).count() > 0 { + if let Some(data_column_publishing_delay) = data_column_publishing_delay_for_testing { + // Subtract block publishing delay if it is also used. + // Note: if `data_column_publishing_delay` is less than `block_publishing_delay`, it + // will still be delayed by `block_publishing_delay`. This could be solved with spawning + // async tasks but the limitation is minor and I believe it's probably not worth + // affecting the mainnet code path. + let block_publishing_delay = block_publishing_delay_for_testing.unwrap_or_default(); + let delay = data_column_publishing_delay.saturating_sub(block_publishing_delay); + if !delay.is_zero() { + debug!( + ?data_column_publishing_delay, + "Publishing data columns with artificial delay" + ); + tokio::time::sleep(delay).await; + } + } publish_column_sidecars(network_tx, &gossip_verified_columns, &chain).map_err(|_| { warp_utils::reject::custom_server_error("unable to publish data column sidecars".into()) })?; @@ -227,9 +239,8 @@ pub async fn publish_block>( Err(warp_utils::reject::broadcast_without_import(msg)) } else { error!( - log, - "Invalid data column during block publication"; - "reason" => &msg + reason = &msg, + "Invalid data column during block publication" ); Err(warp_utils::reject::custom_bad_request(msg)) }; @@ -253,7 +264,6 @@ pub async fn publish_block>( is_locally_built_block, seen_timestamp, &chain, - &log, ) .await } @@ -266,7 +276,6 @@ pub async fn publish_block>( is_locally_built_block, seen_timestamp, &chain, - &log, ) .await } else { @@ -286,10 +295,9 @@ pub async fn publish_block>( } Err(BlockError::DuplicateImportStatusUnknown(root)) => { debug!( - log, - "Block previously seen"; - "block_root" => ?root, - "slot" => block.slot(), + block_root = ?root, + slot = %block.slot(), + "Block previously seen" ); let import_result = Box::pin(chain.process_block( block_root, @@ -306,16 +314,14 @@ pub async fn publish_block>( is_locally_built_block, seen_timestamp, &chain, - &log, ) .await } Err(e) => { warn!( - log, - "Not publishing block - not gossip verified"; - "slot" => slot, - "error" => %e + %slot, + error = %e, + "Not publishing block - not gossip verified" ); Err(warp_utils::reject::custom_bad_request(e.to_string())) } @@ -338,7 +344,6 @@ fn spawn_build_data_sidecar_task( chain: Arc>, block: Arc>>, proofs_and_blobs: UnverifiedBlobs, - log: Logger, ) -> Result>, Rejection> { chain .clone() @@ -353,12 +358,12 @@ fn spawn_build_data_sidecar_task( if !peer_das_enabled { // Pre-PeerDAS: construct blob sidecars for the network. let gossip_verified_blobs = - build_gossip_verified_blobs(&chain, &block, blobs, kzg_proofs, &log)?; + build_gossip_verified_blobs(&chain, &block, blobs, kzg_proofs)?; Ok((gossip_verified_blobs, vec![])) } else { // Post PeerDAS: construct data columns. let gossip_verified_data_columns = - build_gossip_verified_data_columns(&chain, &block, blobs, &log)?; + build_gossip_verified_data_columns(&chain, &block, blobs)?; Ok((vec![], gossip_verified_data_columns)) } }, @@ -377,16 +382,14 @@ fn build_gossip_verified_data_columns( chain: &BeaconChain, block: &SignedBeaconBlock>, blobs: BlobsList, - log: &Logger, ) -> Result>>, Rejection> { let slot = block.slot(); let data_column_sidecars = build_blob_data_column_sidecars(chain, block, blobs).map_err(|e| { error!( - log, - "Invalid data column - not publishing block"; - "error" => ?e, - "slot" => slot + error = ?e, + %slot, + "Invalid data column - not publishing block" ); warp_utils::reject::custom_bad_request(format!("{e:?}")) })?; @@ -407,21 +410,19 @@ fn build_gossip_verified_data_columns( // or some of the other data columns if the block & data columns are only // partially published by the other publisher. debug!( - log, - "Data column for publication already known"; - "column_index" => column_index, - "slot" => slot, - "proposer" => proposer, + column_index, + %slot, + proposer, + "Data column for publication already known" ); Ok(None) } Err(e) => { error!( - log, - "Data column for publication is gossip-invalid"; - "column_index" => column_index, - "slot" => slot, - "error" => ?e, + column_index, + %slot, + error = ?e, + "Data column for publication is gossip-invalid" ); Err(warp_utils::reject::custom_bad_request(format!("{e:?}"))) } @@ -437,7 +438,6 @@ fn build_gossip_verified_blobs( block: &SignedBeaconBlock>, blobs: BlobsList, kzg_proofs: KzgProofs, - log: &Logger, ) -> Result>>, Rejection> { let slot = block.slot(); let gossip_verified_blobs = kzg_proofs @@ -452,11 +452,10 @@ fn build_gossip_verified_blobs( .map(Arc::new) .map_err(|e| { error!( - log, - "Invalid blob - not publishing block"; - "error" => ?e, - "blob_index" => i, - "slot" => slot, + error = ?e, + blob_index = i, + %slot, + "Invalid blob - not publishing block" ); warp_utils::reject::custom_bad_request(format!("{e:?}")) })?; @@ -472,21 +471,19 @@ fn build_gossip_verified_blobs( // or some of the other blobs if the block & blobs are only partially published // by the other publisher. debug!( - log, - "Blob for publication already known"; - "blob_index" => blob_sidecar.index, - "slot" => slot, - "proposer" => proposer, + blob_index = blob_sidecar.index, + %slot, + proposer, + "Blob for publication already known" ); Ok(None) } Err(e) => { error!( - log, - "Blob for publication is gossip-invalid"; - "blob_index" => blob_sidecar.index, - "slot" => slot, - "error" => ?e, + blob_index = blob_sidecar.index, + %slot, + error = ?e, + "Blob for publication is gossip-invalid" ); Err(warp_utils::reject::custom_bad_request(e.to_string())) } @@ -497,6 +494,15 @@ fn build_gossip_verified_blobs( Ok(gossip_verified_blobs) } +fn publish_blob_sidecars( + sender_clone: &UnboundedSender>, + blob: &GossipVerifiedBlob, +) -> Result<(), BlockError> { + let pubsub_message = PubsubMessage::BlobSidecar(Box::new((blob.index(), blob.clone_blob()))); + crate::publish_pubsub_message(sender_clone, pubsub_message) + .map_err(|_| BlockError::BeaconChainError(BeaconChainError::UnableToPublish)) +} + fn publish_column_sidecars( sender_clone: &UnboundedSender>, data_column_sidecars: &[Option>], @@ -527,15 +533,6 @@ fn publish_column_sidecars( .map_err(|_| BlockError::BeaconChainError(BeaconChainError::UnableToPublish)) } -fn publish_blob_sidecars( - sender_clone: &UnboundedSender>, - blob: &GossipVerifiedBlob, -) -> Result<(), BlockError> { - let pubsub_message = PubsubMessage::BlobSidecar(Box::new((blob.index(), blob.clone_blob()))); - crate::publish_pubsub_message(sender_clone, pubsub_message) - .map_err(|_| BlockError::BeaconChainError(BeaconChainError::UnableToPublish)) -} - async fn post_block_import_logging_and_response( result: Result, validation_level: BroadcastValidation, @@ -543,7 +540,6 @@ async fn post_block_import_logging_and_response( is_locally_built_block: bool, seen_timestamp: Duration, chain: &Arc>, - log: &Logger, ) -> Result { match result { // The `DuplicateFullyImported` case here captures the case where the block finishes @@ -555,12 +551,11 @@ async fn post_block_import_logging_and_response( | Err(BlockError::DuplicateFullyImported(root)) => { let delay = get_block_delay_ms(seen_timestamp, block.message(), &chain.slot_clock); info!( - log, - "Valid block from HTTP API"; - "block_delay" => ?delay, - "root" => %root, - "proposer_index" => block.message().proposer_index(), - "slot" => block.slot(), + block_delay = ?delay, + root = %root, + proposer_index = block.message().proposer_index(), + slot = %block.slot(), + "Valid block from HTTP API" ); // Notify the validator monitor. @@ -579,7 +574,7 @@ async fn post_block_import_logging_and_response( // blocks built with builders we consider the broadcast time to be // when the blinded block is published to the builder. if is_locally_built_block { - late_block_logging(chain, seen_timestamp, block.message(), root, "local", log) + late_block_logging(chain, seen_timestamp, block.message(), root, "local") } Ok(warp::reply().into_response()) } @@ -588,11 +583,7 @@ async fn post_block_import_logging_and_response( if let BroadcastValidation::Gossip = validation_level { Err(warp_utils::reject::broadcast_without_import(msg)) } else { - error!( - log, - "Invalid block provided to HTTP API"; - "reason" => &msg - ); + error!(reason = &msg, "Invalid block provided to HTTP API"); Err(warp_utils::reject::custom_bad_request(msg)) } } @@ -609,9 +600,8 @@ async fn post_block_import_logging_and_response( Err(warp_utils::reject::broadcast_without_import(format!("{e}"))) } else { error!( - log, - "Invalid block provided to HTTP API"; - "reason" => ?e, + reason = ?e, + "Invalid block provided to HTTP API" ); Err(warp_utils::reject::custom_bad_request(format!( "Invalid block: {e}" @@ -627,20 +617,17 @@ pub async fn publish_blinded_block( blinded_block: Arc>, chain: Arc>, network_tx: &UnboundedSender>, - log: Logger, validation_level: BroadcastValidation, duplicate_status_code: StatusCode, network_globals: Arc>, ) -> Result { let block_root = blinded_block.canonical_root(); - let full_block = - reconstruct_block(chain.clone(), block_root, blinded_block, log.clone()).await?; + let full_block = reconstruct_block(chain.clone(), block_root, blinded_block).await?; publish_block::( Some(block_root), full_block, chain, network_tx, - log, validation_level, duplicate_status_code, network_globals, @@ -655,7 +642,6 @@ pub async fn reconstruct_block( chain: Arc>, block_root: Hash256, block: Arc>, - log: Logger, ) -> Result>>, Rejection> { let full_payload_opt = if let Ok(payload_header) = block.message().body().execution_payload() { let el = chain.execution_layer.as_ref().ok_or_else(|| { @@ -679,7 +665,7 @@ pub async fn reconstruct_block( } else if let Some(cached_payload) = el.get_payload_by_root(&payload_header.tree_hash_root()) { - info!(log, "Reconstructing a full block using a local payload"; "block_hash" => ?cached_payload.block_hash()); + info!(block_hash = ?cached_payload.block_hash(), "Reconstructing a full block using a local payload"); ProvenancedPayload::Local(cached_payload) // Otherwise, this means we are attempting a blind block proposal. } else { @@ -694,7 +680,6 @@ pub async fn reconstruct_block( block.message(), block_root, "builder", - &log, ); let full_payload = el @@ -706,7 +691,7 @@ pub async fn reconstruct_block( e )) })?; - info!(log, "Successfully published a block to the builder network"; "block_hash" => ?full_payload.block_hash()); + info!(block_hash = ?full_payload.block_hash(), "Successfully published a block to the builder network"); ProvenancedPayload::Builder(full_payload) }; @@ -748,7 +733,6 @@ fn late_block_logging>( block: BeaconBlockRef, root: Hash256, provenance: &str, - log: &Logger, ) { let delay = get_block_delay_ms(seen_timestamp, block, &chain.slot_clock); @@ -767,23 +751,21 @@ fn late_block_logging>( let delayed_threshold = too_late_threshold / 2; if delay >= too_late_threshold { error!( - log, - "Block was broadcast too late"; - "msg" => "system may be overloaded, block likely to be orphaned", - "provenance" => provenance, - "delay_ms" => delay.as_millis(), - "slot" => block.slot(), - "root" => ?root, + msg = "system may be overloaded, block likely to be orphaned", + provenance, + delay_ms = delay.as_millis(), + slot = %block.slot(), + ?root, + "Block was broadcast too late" ) } else if delay >= delayed_threshold { error!( - log, - "Block broadcast was delayed"; - "msg" => "system may be overloaded, block may be orphaned", - "provenance" => provenance, - "delay_ms" => delay.as_millis(), - "slot" => block.slot(), - "root" => ?root, + msg = "system may be overloaded, block may be orphaned", + provenance, + delay_ms = delay.as_millis(), + slot = %block.slot(), + ?root, + "Block broadcast was delayed" ) } } @@ -793,7 +775,6 @@ fn check_slashable( chain_clone: &BeaconChain, block_root: Hash256, block_clone: &SignedBeaconBlock>, - log_clone: &Logger, ) -> Result<(), BlockError> { let slashable_cache = chain_clone.observed_slashable.read(); if slashable_cache @@ -805,9 +786,8 @@ fn check_slashable( .map_err(|e| BlockError::BeaconChainError(e.into()))? { warn!( - log_clone, - "Not publishing equivocating block"; - "slot" => block_clone.slot() + slot = %block_clone.slot(), + "Not publishing equivocating block" ); return Err(BlockError::Slashable); } diff --git a/beacon_node/http_api/src/sync_committee_rewards.rs b/beacon_node/http_api/src/sync_committee_rewards.rs index ec63372406..e5a9d9daea 100644 --- a/beacon_node/http_api/src/sync_committee_rewards.rs +++ b/beacon_node/http_api/src/sync_committee_rewards.rs @@ -2,9 +2,9 @@ use crate::{BlockId, ExecutionOptimistic}; use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes}; use eth2::lighthouse::SyncCommitteeReward; use eth2::types::ValidatorId; -use slog::{debug, Logger}; use state_processing::BlockReplayer; use std::sync::Arc; +use tracing::debug; use types::{BeaconState, SignedBlindedBeaconBlock}; use warp_utils::reject::{custom_not_found, unhandled_error}; @@ -12,7 +12,6 @@ pub fn compute_sync_committee_rewards( chain: Arc>, block_id: BlockId, validators: Vec, - log: Logger, ) -> Result<(Option>, ExecutionOptimistic, bool), warp::Rejection> { let (block, execution_optimistic, finalized) = block_id.blinded_block(&chain)?; @@ -23,7 +22,7 @@ pub fn compute_sync_committee_rewards( .map_err(unhandled_error)?; let data = if reward_payload.is_empty() { - debug!(log, "compute_sync_committee_rewards returned empty"); + debug!("compute_sync_committee_rewards returned empty"); None } else if validators.is_empty() { Some(reward_payload) diff --git a/beacon_node/http_api/src/sync_committees.rs b/beacon_node/http_api/src/sync_committees.rs index da9f9b7a06..9ca1a2401a 100644 --- a/beacon_node/http_api/src/sync_committees.rs +++ b/beacon_node/http_api/src/sync_committees.rs @@ -11,11 +11,11 @@ use beacon_chain::{ use eth2::types::{self as api_types}; use lighthouse_network::PubsubMessage; use network::NetworkMessage; -use slog::{debug, error, warn, Logger}; use slot_clock::SlotClock; use std::cmp::max; use std::collections::HashMap; use tokio::sync::mpsc::UnboundedSender; +use tracing::{debug, error, warn}; use types::{ slot_data::SlotData, BeaconStateError, Epoch, EthSpec, SignedContributionAndProof, SyncCommitteeMessage, SyncDuty, SyncSubnetId, @@ -178,7 +178,6 @@ pub fn process_sync_committee_signatures( sync_committee_signatures: Vec, network_tx: UnboundedSender>, chain: &BeaconChain, - log: Logger, ) -> Result<(), warp::reject::Rejection> { let mut failures = vec![]; @@ -192,10 +191,9 @@ pub fn process_sync_committee_signatures( Ok(positions) => positions, Err(e) => { error!( - log, - "Unable to compute subnet positions for sync message"; - "error" => ?e, - "slot" => sync_committee_signature.slot, + error = ?e, + slot = %sync_committee_signature.slot, + "Unable to compute subnet positions for sync message" ); failures.push(api_types::Failure::new(i, format!("Verification: {:?}", e))); continue; @@ -248,22 +246,20 @@ pub fn process_sync_committee_signatures( new_root, }) => { debug!( - log, - "Ignoring already-known sync message"; - "new_root" => ?new_root, - "prev_root" => ?prev_root, - "slot" => slot, - "validator_index" => validator_index, + ?new_root, + ?prev_root, + %slot, + validator_index, + "Ignoring already-known sync message" ); } Err(e) => { error!( - log, - "Failure verifying sync committee signature for gossip"; - "error" => ?e, - "request_index" => i, - "slot" => sync_committee_signature.slot, - "validator_index" => sync_committee_signature.validator_index, + error = ?e, + request_index = i, + slot = %sync_committee_signature.slot, + validator_index = sync_committee_signature.validator_index, + "Failure verifying sync committee signature for gossip" ); failures.push(api_types::Failure::new(i, format!("Verification: {:?}", e))); } @@ -273,11 +269,10 @@ pub fn process_sync_committee_signatures( if let Some(verified) = verified_for_pool { if let Err(e) = chain.add_to_naive_sync_aggregation_pool(verified) { error!( - log, - "Unable to add sync committee signature to pool"; - "error" => ?e, - "slot" => sync_committee_signature.slot, - "validator_index" => sync_committee_signature.validator_index, + error = ?e, + slot = %sync_committee_signature.slot, + validator_index = sync_committee_signature.validator_index, + "Unable to add sync committee signature to pool" ); } } @@ -312,7 +307,6 @@ pub fn process_signed_contribution_and_proofs( signed_contribution_and_proofs: Vec>, network_tx: UnboundedSender>, chain: &BeaconChain, - log: Logger, ) -> Result<(), warp::reject::Rejection> { let mut verified_contributions = Vec::with_capacity(signed_contribution_and_proofs.len()); let mut failures = vec![]; @@ -362,13 +356,12 @@ pub fn process_signed_contribution_and_proofs( Err(SyncVerificationError::AggregatorAlreadyKnown(_)) => continue, Err(e) => { error!( - log, - "Failure verifying signed contribution and proof"; - "error" => ?e, - "request_index" => index, - "aggregator_index" => aggregator_index, - "subcommittee_index" => subcommittee_index, - "contribution_slot" => contribution_slot, + error = ?e, + request_index = index, + aggregator_index = aggregator_index, + subcommittee_index = subcommittee_index, + contribution_slot = %contribution_slot, + "Failure verifying signed contribution and proof" ); failures.push(api_types::Failure::new( index, @@ -382,10 +375,9 @@ pub fn process_signed_contribution_and_proofs( for (index, verified_contribution) in verified_contributions { if let Err(e) = chain.add_contribution_to_block_inclusion_pool(verified_contribution) { warn!( - log, - "Could not add verified sync contribution to the inclusion pool"; - "error" => ?e, - "request_index" => index, + error = ?e, + request_index = index, + "Could not add verified sync contribution to the inclusion pool" ); failures.push(api_types::Failure::new(index, format!("Op pool: {:?}", e))); } diff --git a/beacon_node/http_api/src/test_utils.rs b/beacon_node/http_api/src/test_utils.rs index fbc92a45cc..f78a361dad 100644 --- a/beacon_node/http_api/src/test_utils.rs +++ b/beacon_node/http_api/src/test_utils.rs @@ -19,10 +19,8 @@ use lighthouse_network::{ types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield, SyncState}, ConnectedPoint, Enr, NetworkConfig, NetworkGlobals, PeerId, PeerManager, }; -use logging::test_logger; use network::{NetworkReceivers, NetworkSenders}; use sensitive_url::SensitiveUrl; -use slog::Logger; use std::future::Future; use std::net::SocketAddr; use std::sync::Arc; @@ -75,7 +73,6 @@ impl InteractiveTester { ) -> Self { let mut harness_builder = BeaconChainHarness::builder(E::default()) .spec_or_default(spec.map(Arc::new)) - .logger(test_logger()) .mock_execution_layer(); harness_builder = if let Some(initializer) = initializer { @@ -102,13 +99,7 @@ impl InteractiveTester { listening_socket, network_rx, .. - } = create_api_server_with_config( - harness.chain.clone(), - config, - &harness.runtime, - harness.logger().clone(), - ) - .await; + } = create_api_server_with_config(harness.chain.clone(), config, &harness.runtime).await; tokio::spawn(server); @@ -134,16 +125,14 @@ impl InteractiveTester { pub async fn create_api_server( chain: Arc>, test_runtime: &TestRuntime, - log: Logger, ) -> ApiServer> { - create_api_server_with_config(chain, Config::default(), test_runtime, log).await + create_api_server_with_config(chain, Config::default(), test_runtime).await } pub async fn create_api_server_with_config( chain: Arc>, http_config: Config, test_runtime: &TestRuntime, - log: Logger, ) -> ApiServer> { // Use port 0 to allocate a new unused port. let port = 0; @@ -174,14 +163,13 @@ pub async fn create_api_server_with_config( meta_data, vec![], false, - &log, network_config, chain.spec.clone(), )); // Only a peer manager can add peers, so we create a dummy manager. let config = lighthouse_network::peer_manager::config::Config::default(); - let mut pm = PeerManager::new(config, network_globals.clone(), &log).unwrap(); + let mut pm = PeerManager::new(config, network_globals.clone()).unwrap(); // add a peer let peer_id = PeerId::random(); @@ -200,8 +188,7 @@ pub async fn create_api_server_with_config( })); *network_globals.sync_state.write() = SyncState::Synced; - let eth1_service = - eth1::Service::new(eth1::Config::default(), log.clone(), chain.spec.clone()).unwrap(); + let eth1_service = eth1::Service::new(eth1::Config::default(), chain.spec.clone()).unwrap(); let beacon_processor_config = BeaconProcessorConfig { // The number of workers must be greater than one. Tests which use the @@ -225,7 +212,6 @@ pub async fn create_api_server_with_config( executor: test_runtime.task_executor.clone(), current_workers: 0, config: beacon_processor_config, - log: log.clone(), } .spawn_manager( beacon_processor_rx, @@ -249,7 +235,6 @@ pub async fn create_api_server_with_config( enabled: true, listen_port: port, data_dir: std::path::PathBuf::from(DEFAULT_ROOT_DIR), - enable_light_client_server: true, ..http_config }, chain: Some(chain), @@ -259,7 +244,6 @@ pub async fn create_api_server_with_config( beacon_processor_reprocess_send: Some(reprocess_send), eth1_service: Some(eth1_service), sse_logging_components: None, - log, }); let (listening_socket, server) = diff --git a/beacon_node/http_api/src/validators.rs b/beacon_node/http_api/src/validators.rs index 93e63953ef..f3d78e6fcd 100644 --- a/beacon_node/http_api/src/validators.rs +++ b/beacon_node/http_api/src/validators.rs @@ -29,7 +29,7 @@ pub fn get_beacon_state_validators( .enumerate() // filter by validator id(s) if provided .filter(|(index, (validator, _))| { - ids_filter_set.as_ref().map_or(true, |ids_set| { + ids_filter_set.as_ref().is_none_or(|ids_set| { ids_set.contains(&ValidatorId::PublicKey(validator.pubkey)) || ids_set.contains(&ValidatorId::Index(*index as u64)) }) @@ -42,7 +42,7 @@ pub fn get_beacon_state_validators( far_future_epoch, ); - let status_matches = query_statuses.as_ref().map_or(true, |statuses| { + let status_matches = query_statuses.as_ref().is_none_or(|statuses| { statuses.contains(&status) || statuses.contains(&status.superstatus()) }); @@ -92,7 +92,7 @@ pub fn get_beacon_state_validator_balances( .enumerate() // filter by validator id(s) if provided .filter(|(index, (validator, _))| { - ids_filter_set.as_ref().map_or(true, |ids_set| { + ids_filter_set.as_ref().is_none_or(|ids_set| { ids_set.contains(&ValidatorId::PublicKey(validator.pubkey)) || ids_set.contains(&ValidatorId::Index(*index as u64)) }) diff --git a/beacon_node/http_api/tests/broadcast_validation_tests.rs b/beacon_node/http_api/tests/broadcast_validation_tests.rs index 1baa71699c..b888439238 100644 --- a/beacon_node/http_api/tests/broadcast_validation_tests.rs +++ b/beacon_node/http_api/tests/broadcast_validation_tests.rs @@ -331,7 +331,6 @@ pub async fn consensus_partial_pass_only_consensus() { let validator_count = 64; let num_initial: u64 = 31; let tester = InteractiveTester::::new(None, validator_count).await; - let test_logger = tester.harness.logger().clone(); // Create some chain depth. tester.harness.advance_slot(); @@ -379,7 +378,6 @@ pub async fn consensus_partial_pass_only_consensus() { ProvenancedBlock::local(gossip_block_b.unwrap(), blobs_b), tester.harness.chain.clone(), &channel.0, - test_logger, validation_level, StatusCode::ACCEPTED, network_globals, @@ -624,7 +622,6 @@ pub async fn equivocation_consensus_late_equivocation() { let validator_count = 64; let num_initial: u64 = 31; let tester = InteractiveTester::::new(None, validator_count).await; - let test_logger = tester.harness.logger().clone(); // Create some chain depth. tester.harness.advance_slot(); @@ -671,7 +668,6 @@ pub async fn equivocation_consensus_late_equivocation() { ProvenancedBlock::local(gossip_block_b.unwrap(), blobs_b), tester.harness.chain, &channel.0, - test_logger, validation_level, StatusCode::ACCEPTED, network_globals, @@ -1236,7 +1232,6 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { let validator_count = 64; let num_initial: u64 = 31; let tester = InteractiveTester::::new(None, validator_count).await; - let test_logger = tester.harness.logger().clone(); // Create some chain depth. tester.harness.advance_slot(); @@ -1276,7 +1271,6 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { tester.harness.chain.clone(), block_a.canonical_root(), Arc::new(block_a), - test_logger.clone(), ) .await .unwrap(); @@ -1284,7 +1278,6 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { tester.harness.chain.clone(), block_b.canonical_root(), block_b.clone(), - test_logger.clone(), ) .await .unwrap(); @@ -1310,7 +1303,6 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { block_b, tester.harness.chain, &channel.0, - test_logger, validation_level, StatusCode::ACCEPTED, network_globals, diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 36410a2581..2020130d18 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -26,7 +26,6 @@ use http_api::{ BlockId, StateId, }; use lighthouse_network::{types::SyncState, Enr, EnrExt, PeerId}; -use logging::test_logger; use network::NetworkReceivers; use proto_array::ExecutionStatus; use sensitive_url::SensitiveUrl; @@ -36,7 +35,6 @@ use state_processing::per_slot_processing; use state_processing::state_advance::partial_state_advance; use std::convert::TryInto; use std::sync::Arc; -use store::{AnchorInfo, Split}; use tokio::time::Duration; use tree_hash::TreeHash; use types::application_domain::ApplicationDomain; @@ -136,7 +134,6 @@ impl ApiTester { reconstruct_historic_states: config.retain_historic_states, ..ChainConfig::default() }) - .logger(logging::test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .deterministic_withdrawal_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() @@ -278,8 +275,6 @@ impl ApiTester { "precondition: justification" ); - let log = test_logger(); - let ApiServer { ctx, server, @@ -287,7 +282,7 @@ impl ApiTester { network_rx, local_enr, external_peer_id, - } = create_api_server(chain.clone(), &harness.runtime, log).await; + } = create_api_server(chain.clone(), &harness.runtime).await; harness.runtime.task_executor.spawn(server, "api_server"); @@ -376,7 +371,6 @@ impl ApiTester { let bls_to_execution_change = harness.make_bls_to_execution_change(4, Address::zero()); let chain = harness.chain.clone(); - let log = test_logger(); let ApiServer { ctx, @@ -385,7 +379,7 @@ impl ApiTester { network_rx, local_enr, external_peer_id, - } = create_api_server(chain.clone(), &harness.runtime, log).await; + } = create_api_server(chain.clone(), &harness.runtime).await; harness.runtime.task_executor.spawn(server, "api_server"); @@ -2451,8 +2445,8 @@ impl ApiTester { }; let state_match = - states.map_or(true, |states| states.contains(&PeerState::Connected)); - let dir_match = dirs.map_or(true, |dirs| dirs.contains(&PeerDirection::Inbound)); + states.is_none_or(|states| states.contains(&PeerState::Connected)); + let dir_match = dirs.is_none_or(|dirs| dirs.contains(&PeerDirection::Inbound)); let mut expected_peers = Vec::new(); if state_match && dir_match { @@ -3556,44 +3550,48 @@ impl ApiTester { } #[allow(clippy::await_holding_lock)] // This is a test, so it should be fine. - pub async fn test_get_validator_aggregate_attestation(self) -> Self { - if self + pub async fn test_get_validator_aggregate_attestation_v1(self) -> Self { + let attestation = self .chain - .spec - .fork_name_at_slot::(self.chain.slot().unwrap()) - .electra_enabled() - { - for attestation in self.chain.naive_aggregation_pool.read().iter() { - let result = self - .client - .get_validator_aggregate_attestation_v2( - attestation.data().slot, - attestation.data().tree_hash_root(), - attestation.committee_index().expect("committee index"), - ) - .await - .unwrap() - .unwrap() - .data; - let expected = attestation; + .head_beacon_block() + .message() + .body() + .attestations() + .next() + .unwrap() + .clone_as_attestation(); + let result = self + .client + .get_validator_aggregate_attestation_v1( + attestation.data().slot, + attestation.data().tree_hash_root(), + ) + .await + .unwrap() + .unwrap() + .data; + let expected = attestation; - assert_eq!(&result, expected); - } - } else { - let attestation = self - .chain - .head_beacon_block() - .message() - .body() - .attestations() - .next() - .unwrap() - .clone_as_attestation(); + assert_eq!(result, expected); + + self + } + + pub async fn test_get_validator_aggregate_attestation_v2(self) -> Self { + let attestations = self + .chain + .naive_aggregation_pool + .read() + .iter() + .cloned() + .collect::>(); + for attestation in attestations { let result = self .client - .get_validator_aggregate_attestation_v1( + .get_validator_aggregate_attestation_v2( attestation.data().slot, attestation.data().tree_hash_root(), + attestation.committee_index().expect("committee index"), ) .await .unwrap() @@ -3603,7 +3601,6 @@ impl ApiTester { assert_eq!(result, expected); } - self } @@ -5692,16 +5689,10 @@ impl ApiTester { pub async fn test_get_lighthouse_database_info(self) -> Self { let info = self.client.get_lighthouse_database_info().await.unwrap(); + assert_eq!(info.anchor, self.chain.store.get_anchor_info()); + assert_eq!(info.split, self.chain.store.get_split_info()); assert_eq!( - serde_json::from_value::(info.get("anchor").unwrap().clone()).unwrap(), - self.chain.store.get_anchor_info() - ); - assert_eq!( - serde_json::from_value::(info.get("split").unwrap().clone()).unwrap(), - self.chain.store.get_split_info() - ); - assert_eq!( - serde_json::from_value::(info.get("schema_version").unwrap().clone()).unwrap(), + info.schema_version, store::metadata::CURRENT_SCHEMA_VERSION.as_u64() ); @@ -6782,19 +6773,36 @@ async fn get_validator_attestation_data_with_skip_slots() { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_validator_aggregate_attestation() { +async fn get_validator_aggregate_attestation_v1() { ApiTester::new() .await - .test_get_validator_aggregate_attestation() + .test_get_validator_aggregate_attestation_v1() .await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn get_validator_aggregate_attestation_with_skip_slots() { +async fn get_validator_aggregate_attestation_v2() { + ApiTester::new() + .await + .test_get_validator_aggregate_attestation_v2() + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_validator_aggregate_attestation_with_skip_slots_v1() { ApiTester::new() .await .skip_slots(E::slots_per_epoch() * 2) - .test_get_validator_aggregate_attestation() + .test_get_validator_aggregate_attestation_v1() + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_validator_aggregate_attestation_with_skip_slots_v2() { + ApiTester::new() + .await + .skip_slots(E::slots_per_epoch() * 2) + .test_get_validator_aggregate_attestation_v2() .await; } diff --git a/beacon_node/http_metrics/Cargo.toml b/beacon_node/http_metrics/Cargo.toml index 9ad073439d..e12053ac43 100644 --- a/beacon_node/http_metrics/Cargo.toml +++ b/beacon_node/http_metrics/Cargo.toml @@ -10,12 +10,13 @@ beacon_chain = { workspace = true } health_metrics = { workspace = true } lighthouse_network = { workspace = true } lighthouse_version = { workspace = true } +logging = { workspace = true } malloc_utils = { workspace = true } metrics = { workspace = true } serde = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } store = { workspace = true } +tracing = { workspace = true } warp = { workspace = true } warp_utils = { workspace = true } diff --git a/beacon_node/http_metrics/src/lib.rs b/beacon_node/http_metrics/src/lib.rs index 2895506c3b..6cbb485d71 100644 --- a/beacon_node/http_metrics/src/lib.rs +++ b/beacon_node/http_metrics/src/lib.rs @@ -6,12 +6,13 @@ mod metrics; use beacon_chain::{BeaconChain, BeaconChainTypes}; use lighthouse_network::prometheus_client::registry::Registry; use lighthouse_version::version_with_platform; +use logging::crit; use serde::{Deserialize, Serialize}; -use slog::{crit, info, Logger}; use std::future::Future; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; +use tracing::info; use warp::{http::Response, Filter}; #[derive(Debug)] @@ -41,7 +42,6 @@ pub struct Context { pub db_path: Option, pub freezer_db_path: Option, pub gossipsub_registry: Option>, - pub log: Logger, } /// Configuration for the HTTP server. @@ -86,7 +86,6 @@ pub fn serve( shutdown: impl Future + Send + Sync + 'static, ) -> Result<(SocketAddr, impl Future), Error> { let config = &ctx.config; - let log = ctx.log.clone(); // Configure CORS. let cors_builder = { @@ -103,7 +102,7 @@ pub fn serve( // Sanity check. if !config.enabled { - crit!(log, "Cannot start disabled metrics HTTP server"); + crit!("Cannot start disabled metrics HTTP server"); return Err(Error::Other( "A disabled metrics server should not be started".to_string(), )); @@ -144,9 +143,8 @@ pub fn serve( )?; info!( - log, - "Metrics HTTP server started"; - "listen_address" => listening_socket.to_string(), + listen_address = listening_socket.to_string(), + "Metrics HTTP server started" ); Ok((listening_socket, server)) diff --git a/beacon_node/http_metrics/tests/tests.rs b/beacon_node/http_metrics/tests/tests.rs index d903e233fb..2de2fd96f8 100644 --- a/beacon_node/http_metrics/tests/tests.rs +++ b/beacon_node/http_metrics/tests/tests.rs @@ -1,6 +1,6 @@ use beacon_chain::test_utils::EphemeralHarnessType; use http_metrics::Config; -use logging::test_logger; +use logging::create_test_tracing_subscriber; use reqwest::header::HeaderValue; use reqwest::StatusCode; use std::net::{IpAddr, Ipv4Addr}; @@ -12,9 +12,8 @@ type Context = http_metrics::Context>; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn returns_200_ok() { + create_test_tracing_subscriber(); async { - let log = test_logger(); - let context = Arc::new(Context { config: Config { enabled: true, @@ -27,7 +26,6 @@ async fn returns_200_ok() { db_path: None, freezer_db_path: None, gossipsub_registry: None, - log, }); let ctx = context.clone(); diff --git a/beacon_node/lighthouse_network/Cargo.toml b/beacon_node/lighthouse_network/Cargo.toml index bccf9b9f29..d60bfc3735 100644 --- a/beacon_node/lighthouse_network/Cargo.toml +++ b/beacon_node/lighthouse_network/Cargo.toml @@ -13,17 +13,17 @@ directory = { workspace = true } dirs = { workspace = true } discv5 = { workspace = true } either = { workspace = true } -eth2 = { workspace = true } ethereum_ssz = { workspace = true } ethereum_ssz_derive = { workspace = true } fnv = { workspace = true } futures = { workspace = true } -gossipsub = { workspace = true } +gossipsub = { package = "libp2p-gossipsub", git = "https://github.com/sigp/rust-libp2p.git", branch = "sigp-gossipsub" } hex = { workspace = true } itertools = { workspace = true } libp2p-mplex = "0.43" lighthouse_version = { workspace = true } local-ip-address = "0.6" +logging = { workspace = true } lru = { workspace = true } lru_cache = { workspace = true } metrics = { workspace = true } @@ -33,7 +33,6 @@ rand = { workspace = true } regex = { workspace = true } serde = { workspace = true } sha2 = { workspace = true } -slog = { workspace = true } smallvec = { workspace = true } snap = { workspace = true } ssz_types = { workspace = true } @@ -44,13 +43,12 @@ tiny-keccak = "2" tokio = { workspace = true } tokio-io-timeout = "1" tokio-util = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } unsigned-varint = { version = "0.8", features = ["codec"] } unused_port = { workspace = true } -# Local dependencies -void = "1.0.2" - [dependencies.libp2p] version = "0.55" default-features = false @@ -61,8 +59,6 @@ async-channel = { workspace = true } logging = { workspace = true } quickcheck = { workspace = true } quickcheck_macros = { workspace = true } -slog-async = { workspace = true } -slog-term = { workspace = true } tempfile = { workspace = true } [features] diff --git a/beacon_node/lighthouse_network/gossipsub/CHANGELOG.md b/beacon_node/lighthouse_network/gossipsub/CHANGELOG.md deleted file mode 100644 index aba85f6184..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/CHANGELOG.md +++ /dev/null @@ -1,386 +0,0 @@ -## 0.5 Sigma Prime fork -- Remove the beta tag from the v1.2 upgrade. - See [PR 6344](https://github.com/sigp/lighthouse/pull/6344) - -- Correct state inconsistencies with the mesh and connected peers due to the fanout mapping. - See [PR 6244](https://github.com/sigp/lighthouse/pull/6244) - -- Implement IDONTWANT messages as per [spec](https://github.com/libp2p/specs/pull/548). - See [PR 5422](https://github.com/sigp/lighthouse/pull/5422) - -- Attempt to publish to at least mesh_n peers when publishing a message when flood publish is disabled. - See [PR 5357](https://github.com/sigp/lighthouse/pull/5357). -- Drop `Publish` and `Forward` gossipsub stale messages when polling ConnectionHandler. - See [PR 5175](https://github.com/sigp/lighthouse/pull/5175). -- Apply back pressure by setting a limit in the ConnectionHandler message queue. - See [PR 5066](https://github.com/sigp/lighthouse/pull/5066). - -## 0.46.1 - -- Deprecate `Rpc` in preparation for removing it from the public API because it is an internal type. - See [PR 4833](https://github.com/libp2p/rust-libp2p/pull/4833). - -## 0.46.0 - -- Remove `fast_message_id_fn` mechanism from `Config`. - See [PR 4285](https://github.com/libp2p/rust-libp2p/pull/4285). -- Remove deprecated `gossipsub::Config::idle_timeout` in favor of `SwarmBuilder::idle_connection_timeout`. - See [PR 4642](https://github.com/libp2p/rust-libp2p/pull/4642). -- Return typed error from config builder. - See [PR 4445](https://github.com/libp2p/rust-libp2p/pull/4445). -- Process outbound stream before inbound stream in `EnabledHandler::poll(..)`. - See [PR 4778](https://github.com/libp2p/rust-libp2p/pull/4778). - -## 0.45.2 - -- Deprecate `gossipsub::Config::idle_timeout` in favor of `SwarmBuilder::idle_connection_timeout`. - See [PR 4648]. - - - -[PR 4648]: (https://github.com/libp2p/rust-libp2p/pull/4648) - - - -## 0.45.1 - -- Add getter function to o btain `TopicScoreParams`. - See [PR 4231]. - -[PR 4231]: https://github.com/libp2p/rust-libp2p/pull/4231 - -## 0.45.0 - -- Raise MSRV to 1.65. - See [PR 3715]. -- Remove deprecated items. See [PR 3862]. - -[PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 -[PR 3862]: https://github.com/libp2p/rust-libp2p/pull/3862 - -## 0.44.4 - -- Deprecate `metrics`, `protocol`, `subscription_filter`, `time_cache` modules to make them private. See [PR 3777]. -- Honor the `gossipsub::Config::support_floodsub` in all cases. - Previously, it was ignored when a custom protocol id was set via `gossipsub::Config::protocol_id`. - See [PR 3837]. - -[PR 3777]: https://github.com/libp2p/rust-libp2p/pull/3777 -[PR 3837]: https://github.com/libp2p/rust-libp2p/pull/3837 - -## 0.44.3 - -- Fix erroneously duplicate message IDs. See [PR 3716]. - -- Gracefully disable handler on stream errors. Deprecate a few variants of `HandlerError`. - See [PR 3625]. - -[PR 3716]: https://github.com/libp2p/rust-libp2p/pull/3716 -[PR 3625]: https://github.com/libp2p/rust-libp2p/pull/3325 - -## 0.44.2 - -- Signed messages now use sequential integers in the sequence number field. - See [PR 3551]. - -[PR 3551]: https://github.com/libp2p/rust-libp2p/pull/3551 - -## 0.44.1 - -- Migrate from `prost` to `quick-protobuf`. This removes `protoc` dependency. See [PR 3312]. - -[PR 3312]: https://github.com/libp2p/rust-libp2p/pull/3312 - -## 0.44.0 - -- Update to `prometheus-client` `v0.19.0`. See [PR 3207]. - -- Update to `libp2p-core` `v0.39.0`. - -- Update to `libp2p-swarm` `v0.42.0`. - -- Initialize `ProtocolConfig` via `GossipsubConfig`. See [PR 3381]. - -- Rename types as per [discussion 2174]. - `Gossipsub` has been renamed to `Behaviour`. - The `Gossipsub` prefix has been removed from various types like `GossipsubConfig` or `GossipsubMessage`. - It is preferred to import the gossipsub protocol as a module (`use libp2p::gossipsub;`), and refer to its types via `gossipsub::`. - For example: `gossipsub::Behaviour` or `gossipsub::RawMessage`. See [PR 3303]. - -[PR 3207]: https://github.com/libp2p/rust-libp2p/pull/3207/ -[PR 3303]: https://github.com/libp2p/rust-libp2p/pull/3303/ -[PR 3381]: https://github.com/libp2p/rust-libp2p/pull/3381/ -[discussion 2174]: https://github.com/libp2p/rust-libp2p/discussions/2174 - -## 0.43.0 - -- Update to `libp2p-core` `v0.38.0`. - -- Update to `libp2p-swarm` `v0.41.0`. - -- Update to `prost-codec` `v0.3.0`. - -- Refactoring GossipsubCodec to use common protobuf Codec. See [PR 3070]. - -- Replace `Gossipsub`'s `NetworkBehaviour` implementation `inject_*` methods with the new `on_*` methods. - See [PR 3011]. - -- Replace `GossipsubHandler`'s `ConnectionHandler` implementation `inject_*` methods with the new `on_*` methods. - See [PR 3085]. - -- Update `rust-version` to reflect the actual MSRV: 1.62.0. See [PR 3090]. - -[PR 3085]: https://github.com/libp2p/rust-libp2p/pull/3085 -[PR 3070]: https://github.com/libp2p/rust-libp2p/pull/3070 -[PR 3011]: https://github.com/libp2p/rust-libp2p/pull/3011 -[PR 3090]: https://github.com/libp2p/rust-libp2p/pull/3090 - -## 0.42.0 - -- Bump rand to 0.8 and quickcheck to 1. See [PR 2857]. - -- Update to `libp2p-core` `v0.37.0`. - -- Update to `libp2p-swarm` `v0.40.0`. - -[PR 2857]: https://github.com/libp2p/rust-libp2p/pull/2857 - -## 0.41.0 - -- Update to `libp2p-swarm` `v0.39.0`. - -- Update to `libp2p-core` `v0.36.0`. - -- Allow publishing with any `impl Into` as a topic. See [PR 2862]. - -[PR 2862]: https://github.com/libp2p/rust-libp2p/pull/2862 - -## 0.40.0 - -- Update prost requirement from 0.10 to 0.11 which no longer installs the protoc Protobuf compiler. - Thus you will need protoc installed locally. See [PR 2788]. - -- Update to `libp2p-swarm` `v0.38.0`. - -- Update to `libp2p-core` `v0.35.0`. - -- Update to `prometheus-client` `v0.18.0`. See [PR 2822]. - -[PR 2822]: https://github.com/libp2p/rust-libp2p/pull/2761/ -[PR 2788]: https://github.com/libp2p/rust-libp2p/pull/2788 - -## 0.39.0 - -- Update to `libp2p-core` `v0.34.0`. - -- Update to `libp2p-swarm` `v0.37.0`. - -- Allow for custom protocol ID via `GossipsubConfigBuilder::protocol_id()`. See [PR 2718]. - -[PR 2718]: https://github.com/libp2p/rust-libp2p/pull/2718/ - -## 0.38.1 - -- Fix duplicate connection id. See [PR 2702]. - -[PR 2702]: https://github.com/libp2p/rust-libp2p/pull/2702 - -## 0.38.0 - -- Update to `libp2p-core` `v0.33.0`. - -- Update to `libp2p-swarm` `v0.36.0`. - -- changed `TimeCache::contains_key` and `DuplicateCache::contains` to immutable methods. See [PR 2620]. - -- Update to `prometheus-client` `v0.16.0`. See [PR 2631]. - -[PR 2620]: https://github.com/libp2p/rust-libp2p/pull/2620 -[PR 2631]: https://github.com/libp2p/rust-libp2p/pull/2631 - -## 0.37.0 - -- Update to `libp2p-swarm` `v0.35.0`. - -- Fix gossipsub metric (see [PR 2558]). - -- Allow the user to set the buckets for the score histogram, and to adjust them from the score thresholds. See [PR 2595]. - -[PR 2558]: https://github.com/libp2p/rust-libp2p/pull/2558 -[PR 2595]: https://github.com/libp2p/rust-libp2p/pull/2595 - -## 0.36.0 [2022-02-22] - -- Update to `libp2p-core` `v0.32.0`. - -- Update to `libp2p-swarm` `v0.34.0`. - -- Move from `open-metrics-client` to `prometheus-client` (see [PR 2442]). - -- Emit gossip of all non empty topics (see [PR 2481]). - -- Merge NetworkBehaviour's inject_\* paired methods (see [PR 2445]). - -- Revert to wasm-timer (see [PR 2506]). - -- Do not overwrite msg's peers if put again into mcache (see [PR 2493]). - -[PR 2442]: https://github.com/libp2p/rust-libp2p/pull/2442 -[PR 2481]: https://github.com/libp2p/rust-libp2p/pull/2481 -[PR 2445]: https://github.com/libp2p/rust-libp2p/pull/2445 -[PR 2506]: https://github.com/libp2p/rust-libp2p/pull/2506 -[PR 2493]: https://github.com/libp2p/rust-libp2p/pull/2493 - -## 0.35.0 [2022-01-27] - -- Update dependencies. - -- Migrate to Rust edition 2021 (see [PR 2339]). - -- Add metrics for network and configuration performance analysis (see [PR 2346]). - -- Improve bandwidth performance by tracking IWANTs and reducing duplicate sends - (see [PR 2327]). - -- Implement `Serialize` and `Deserialize` for `MessageId` and `FastMessageId` (see [PR 2408]) - -- Fix `GossipsubConfigBuilder::build()` requiring `&self` to live for `'static` (see [PR 2409]) - -- Implement Unsubscribe backoff as per [libp2p specs PR 383] (see [PR 2403]). - -[PR 2346]: https://github.com/libp2p/rust-libp2p/pull/2346 -[PR 2339]: https://github.com/libp2p/rust-libp2p/pull/2339 -[PR 2327]: https://github.com/libp2p/rust-libp2p/pull/2327 -[PR 2408]: https://github.com/libp2p/rust-libp2p/pull/2408 -[PR 2409]: https://github.com/libp2p/rust-libp2p/pull/2409 -[PR 2403]: https://github.com/libp2p/rust-libp2p/pull/2403 -[libp2p specs PR 383]: https://github.com/libp2p/specs/pull/383 - -## 0.34.0 [2021-11-16] - -- Add topic and mesh metrics (see [PR 2316]). - -- Fix bug in internal peer's topics tracking (see [PR 2325]). - -- Use `instant` and `futures-timer` instead of `wasm-timer` (see [PR 2245]). - -- Update dependencies. - -[PR 2245]: https://github.com/libp2p/rust-libp2p/pull/2245 -[PR 2325]: https://github.com/libp2p/rust-libp2p/pull/2325 -[PR 2316]: https://github.com/libp2p/rust-libp2p/pull/2316 - -## 0.33.0 [2021-11-01] - -- Add an event to register peers that do not support the gossipsub protocol - [PR 2241](https://github.com/libp2p/rust-libp2p/pull/2241) - -- Make default features of `libp2p-core` optional. - [PR 2181](https://github.com/libp2p/rust-libp2p/pull/2181) - -- Improve internal peer tracking. - [PR 2175](https://github.com/libp2p/rust-libp2p/pull/2175) - -- Update dependencies. - -- Allow `message_id_fn`s to accept closures that capture variables. - [PR 2103](https://github.com/libp2p/rust-libp2p/pull/2103) - -- Implement std::error::Error for error types. - [PR 2254](https://github.com/libp2p/rust-libp2p/pull/2254) - -## 0.32.0 [2021-07-12] - -- Update dependencies. - -- Reduce log levels across the crate to lessen noisiness of libp2p-gossipsub (see [PR 2101]). - -[PR 2101]: https://github.com/libp2p/rust-libp2p/pull/2101 - -## 0.31.0 [2021-05-17] - -- Keep connections to peers in a mesh alive. Allow closing idle connections to peers not in a mesh - [PR-2043]. - -[PR-2043]: https://github.com/libp2p/rust-libp2p/pull/2043https://github.com/libp2p/rust-libp2p/pull/2043 - -## 0.30.1 [2021-04-27] - -- Remove `regex-filter` feature flag thus always enabling `regex::RegexSubscriptionFilter` [PR - 2056](https://github.com/libp2p/rust-libp2p/pull/2056). - -## 0.30.0 [2021-04-13] - -- Update `libp2p-swarm`. - -- Update dependencies. - -## 0.29.0 [2021-03-17] - -- Update `libp2p-swarm`. - -- Update dependencies. - -## 0.28.0 [2021-02-15] - -- Prevent non-published messages being added to caches. - [PR 1930](https://github.com/libp2p/rust-libp2p/pull/1930) - -- Update dependencies. - -## 0.27.0 [2021-01-12] - -- Update dependencies. - -- Implement Gossipsub v1.1 specification. - [PR 1720](https://github.com/libp2p/rust-libp2p/pull/1720) - -## 0.26.0 [2020-12-17] - -- Update `libp2p-swarm` and `libp2p-core`. - -## 0.25.0 [2020-11-25] - -- Update `libp2p-swarm` and `libp2p-core`. - -## 0.24.0 [2020-11-09] - -- Update dependencies. - -## 0.23.0 [2020-10-16] - -- Update dependencies. - -## 0.22.0 [2020-09-09] - -- Update `libp2p-swarm` and `libp2p-core`. - -## 0.21.0 [2020-08-18] - -- Add public API to list topics and peers. [PR 1677](https://github.com/libp2p/rust-libp2p/pull/1677). - -- Add message signing and extended privacy/validation configurations. [PR 1583](https://github.com/libp2p/rust-libp2p/pull/1583). - -- `Debug` instance for `Gossipsub`. [PR 1673](https://github.com/libp2p/rust-libp2p/pull/1673). - -- Bump `libp2p-core` and `libp2p-swarm` dependency. - -## 0.20.0 [2020-07-01] - -- Updated dependencies. - -## 0.19.3 [2020-06-23] - -- Maintenance release fixing linter warnings. - -## 0.19.2 [2020-06-22] - -- Updated dependencies. diff --git a/beacon_node/lighthouse_network/gossipsub/Cargo.toml b/beacon_node/lighthouse_network/gossipsub/Cargo.toml deleted file mode 100644 index 239caae47a..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/Cargo.toml +++ /dev/null @@ -1,49 +0,0 @@ -[package] -name = "gossipsub" -edition = "2021" -description = "Sigma prime's version of Gossipsub protocol for libp2p" -version = "0.5.0" -authors = ["Age Manning "] -license = "MIT" -repository = "https://github.com/sigp/lighthouse/" -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] - -[features] -wasm-bindgen = ["getrandom/js", "futures-timer/wasm-bindgen"] -rsa = [] - -[dependencies] -async-channel = { workspace = true } -asynchronous-codec = "0.7.0" -base64 = "0.21.7" -byteorder = "1.5.0" -bytes = "1.5" -either = "1.9" -fnv = "1.0.7" -futures = "0.3.30" -futures-timer = "3.0.2" -getrandom = "0.2.12" -hashlink = { workspace = true } -hex_fmt = "0.3.0" -libp2p = { version = "0.55", default-features = false } -prometheus-client = "0.22.0" -quick-protobuf = "0.8" -quick-protobuf-codec = "0.3" -rand = "0.8" -regex = "1.10.3" -serde = { version = "1", optional = true, features = ["derive"] } -sha2 = "0.10.8" -tracing = "0.1.37" -void = "1.0.2" -web-time = "1.1.0" - -[dev-dependencies] -quickcheck = { workspace = true } - -# Passing arguments to the docsrs builder in order to properly document cfg's. -# More information: https://docs.rs/about/builds#cross-compiling -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] -rustc-args = ["--cfg", "docsrs"] diff --git a/beacon_node/lighthouse_network/gossipsub/src/backoff.rs b/beacon_node/lighthouse_network/gossipsub/src/backoff.rs deleted file mode 100644 index 0d77e2cd0f..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/backoff.rs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Data structure for efficiently storing known back-off's when pruning peers. -use crate::topic::TopicHash; -use libp2p::identity::PeerId; -use std::collections::{ - hash_map::{Entry, HashMap}, - HashSet, -}; -use std::time::Duration; -use web_time::Instant; - -#[derive(Copy, Clone)] -struct HeartbeatIndex(usize); - -/// Stores backoffs in an efficient manner. -pub(crate) struct BackoffStorage { - /// Stores backoffs and the index in backoffs_by_heartbeat per peer per topic. - backoffs: HashMap>, - /// Stores peer topic pairs per heartbeat (this is cyclic the current index is - /// heartbeat_index). - backoffs_by_heartbeat: Vec>, - /// The index in the backoffs_by_heartbeat vector corresponding to the current heartbeat. - heartbeat_index: HeartbeatIndex, - /// The heartbeat interval duration from the config. - heartbeat_interval: Duration, - /// Backoff slack from the config. - backoff_slack: u32, -} - -impl BackoffStorage { - fn heartbeats(d: &Duration, heartbeat_interval: &Duration) -> usize { - d.as_nanos().div_ceil(heartbeat_interval.as_nanos()) as usize - } - - pub(crate) fn new( - prune_backoff: &Duration, - heartbeat_interval: Duration, - backoff_slack: u32, - ) -> BackoffStorage { - // We add one additional slot for partial heartbeat - let max_heartbeats = - Self::heartbeats(prune_backoff, &heartbeat_interval) + backoff_slack as usize + 1; - BackoffStorage { - backoffs: HashMap::new(), - backoffs_by_heartbeat: vec![HashSet::new(); max_heartbeats], - heartbeat_index: HeartbeatIndex(0), - heartbeat_interval, - backoff_slack, - } - } - - /// Updates the backoff for a peer (if there is already a more restrictive backoff then this call - /// doesn't change anything). - pub(crate) fn update_backoff(&mut self, topic: &TopicHash, peer: &PeerId, time: Duration) { - let instant = Instant::now() + time; - let insert_into_backoffs_by_heartbeat = - |heartbeat_index: HeartbeatIndex, - backoffs_by_heartbeat: &mut Vec>, - heartbeat_interval, - backoff_slack| { - let pair = (topic.clone(), *peer); - let index = (heartbeat_index.0 - + Self::heartbeats(&time, heartbeat_interval) - + backoff_slack as usize) - % backoffs_by_heartbeat.len(); - backoffs_by_heartbeat[index].insert(pair); - HeartbeatIndex(index) - }; - match self.backoffs.entry(topic.clone()).or_default().entry(*peer) { - Entry::Occupied(mut o) => { - let (backoff, index) = o.get(); - if backoff < &instant { - let pair = (topic.clone(), *peer); - if let Some(s) = self.backoffs_by_heartbeat.get_mut(index.0) { - s.remove(&pair); - } - let index = insert_into_backoffs_by_heartbeat( - self.heartbeat_index, - &mut self.backoffs_by_heartbeat, - &self.heartbeat_interval, - self.backoff_slack, - ); - o.insert((instant, index)); - } - } - Entry::Vacant(v) => { - let index = insert_into_backoffs_by_heartbeat( - self.heartbeat_index, - &mut self.backoffs_by_heartbeat, - &self.heartbeat_interval, - self.backoff_slack, - ); - v.insert((instant, index)); - } - }; - } - - /// Checks if a given peer is backoffed for the given topic. This method respects the - /// configured BACKOFF_SLACK and may return true even if the backup is already over. - /// It is guaranteed to return false if the backoff is not over and eventually if enough time - /// passed true if the backoff is over. - /// - /// This method should be used for deciding if we can already send a GRAFT to a previously - /// backoffed peer. - pub(crate) fn is_backoff_with_slack(&self, topic: &TopicHash, peer: &PeerId) -> bool { - self.backoffs - .get(topic) - .is_some_and(|m| m.contains_key(peer)) - } - - pub(crate) fn get_backoff_time(&self, topic: &TopicHash, peer: &PeerId) -> Option { - Self::get_backoff_time_from_backoffs(&self.backoffs, topic, peer) - } - - fn get_backoff_time_from_backoffs( - backoffs: &HashMap>, - topic: &TopicHash, - peer: &PeerId, - ) -> Option { - backoffs - .get(topic) - .and_then(|m| m.get(peer).map(|(i, _)| *i)) - } - - /// Applies a heartbeat. That should be called regularly in intervals of length - /// `heartbeat_interval`. - pub(crate) fn heartbeat(&mut self) { - // Clean up backoffs_by_heartbeat - if let Some(s) = self.backoffs_by_heartbeat.get_mut(self.heartbeat_index.0) { - let backoffs = &mut self.backoffs; - let slack = self.heartbeat_interval * self.backoff_slack; - let now = Instant::now(); - s.retain(|(topic, peer)| { - let keep = match Self::get_backoff_time_from_backoffs(backoffs, topic, peer) { - Some(backoff_time) => backoff_time + slack > now, - None => false, - }; - if !keep { - //remove from backoffs - if let Entry::Occupied(mut m) = backoffs.entry(topic.clone()) { - if m.get_mut().remove(peer).is_some() && m.get().is_empty() { - m.remove(); - } - } - } - - keep - }); - } - - // Increase heartbeat index - self.heartbeat_index = - HeartbeatIndex((self.heartbeat_index.0 + 1) % self.backoffs_by_heartbeat.len()); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/behaviour.rs b/beacon_node/lighthouse_network/gossipsub/src/behaviour.rs deleted file mode 100644 index 7eb35cc49b..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/behaviour.rs +++ /dev/null @@ -1,3672 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use std::{ - cmp::{max, Ordering}, - collections::HashSet, - collections::VecDeque, - collections::{BTreeSet, HashMap}, - fmt, - net::IpAddr, - task::{Context, Poll}, - time::Duration, -}; - -use futures::FutureExt; -use hashlink::LinkedHashMap; -use prometheus_client::registry::Registry; -use rand::{seq::SliceRandom, thread_rng}; - -use libp2p::core::{ - multiaddr::Protocol::{Ip4, Ip6}, - transport::PortUse, - Endpoint, Multiaddr, -}; -use libp2p::identity::Keypair; -use libp2p::identity::PeerId; -use libp2p::swarm::{ - behaviour::{AddressChange, ConnectionClosed, ConnectionEstablished, FromSwarm}, - dial_opts::DialOpts, - ConnectionDenied, ConnectionId, NetworkBehaviour, NotifyHandler, THandler, THandlerInEvent, - THandlerOutEvent, ToSwarm, -}; -use web_time::{Instant, SystemTime}; - -use crate::types::IDontWant; - -use super::gossip_promises::GossipPromises; -use super::handler::{Handler, HandlerEvent, HandlerIn}; -use super::mcache::MessageCache; -use super::metrics::{Churn, Config as MetricsConfig, Inclusion, Metrics, Penalty}; -use super::peer_score::{PeerScore, PeerScoreParams, PeerScoreThresholds, RejectReason}; -use super::protocol::SIGNING_PREFIX; -use super::rpc_proto::proto; -use super::subscription_filter::{AllowAllSubscriptionFilter, TopicSubscriptionFilter}; -use super::time_cache::DuplicateCache; -use super::topic::{Hasher, Topic, TopicHash}; -use super::transform::{DataTransform, IdentityTransform}; -use super::types::{ - ControlAction, FailedMessages, Message, MessageAcceptance, MessageId, PeerInfo, RawMessage, - Subscription, SubscriptionAction, -}; -use super::types::{Graft, IHave, IWant, PeerConnections, PeerKind, Prune}; -use super::{backoff::BackoffStorage, types::RpcSender}; -use super::{ - config::{Config, ValidationMode}, - types::RpcOut, -}; -use super::{PublishError, SubscriptionError, TopicScoreParams, ValidationError}; -use futures_timer::Delay; -use quick_protobuf::{MessageWrite, Writer}; -use std::{cmp::Ordering::Equal, fmt::Debug}; - -#[cfg(test)] -mod tests; - -/// IDONTWANT cache capacity. -const IDONTWANT_CAP: usize = 10_000; - -/// IDONTWANT timeout before removal. -const IDONTWANT_TIMEOUT: Duration = Duration::new(3, 0); - -/// Determines if published messages should be signed or not. -/// -/// Without signing, a number of privacy preserving modes can be selected. -/// -/// NOTE: The default validation settings are to require signatures. The [`ValidationMode`] -/// should be updated in the [`Config`] to allow for unsigned messages. -#[derive(Clone)] -pub enum MessageAuthenticity { - /// Message signing is enabled. The author will be the owner of the key and the sequence number - /// will be linearly increasing. - Signed(Keypair), - /// Message signing is disabled. - /// - /// The specified [`PeerId`] will be used as the author of all published messages. The sequence - /// number will be randomized. - Author(PeerId), - /// Message signing is disabled. - /// - /// A random [`PeerId`] will be used when publishing each message. The sequence number will be - /// randomized. - RandomAuthor, - /// Message signing is disabled. - /// - /// The author of the message and the sequence numbers are excluded from the message. - /// - /// NOTE: Excluding these fields may make these messages invalid by other nodes who - /// enforce validation of these fields. See [`ValidationMode`] in the [`Config`] - /// for how to customise this for rust-libp2p gossipsub. A custom `message_id` - /// function will need to be set to prevent all messages from a peer being filtered - /// as duplicates. - Anonymous, -} - -impl MessageAuthenticity { - /// Returns true if signing is enabled. - pub fn is_signing(&self) -> bool { - matches!(self, MessageAuthenticity::Signed(_)) - } - - pub fn is_anonymous(&self) -> bool { - matches!(self, MessageAuthenticity::Anonymous) - } -} - -/// Event that can be emitted by the gossipsub behaviour. -#[derive(Debug)] -pub enum Event { - /// A message has been received. - Message { - /// The peer that forwarded us this message. - propagation_source: PeerId, - /// The [`MessageId`] of the message. This should be referenced by the application when - /// validating a message (if required). - message_id: MessageId, - /// The decompressed message itself. - message: Message, - }, - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: TopicHash, - }, - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: TopicHash, - }, - /// A peer that does not support gossipsub has connected. - GossipsubNotSupported { peer_id: PeerId }, - /// A peer is not able to download messages in time. - SlowPeer { - /// The peer_id - peer_id: PeerId, - /// The types and amounts of failed messages that are occurring for this peer. - failed_messages: FailedMessages, - }, -} - -/// A data structure for storing configuration for publishing messages. See [`MessageAuthenticity`] -/// for further details. -#[allow(clippy::large_enum_variant)] -enum PublishConfig { - Signing { - keypair: Keypair, - author: PeerId, - inline_key: Option>, - last_seq_no: SequenceNumber, - }, - Author(PeerId), - RandomAuthor, - Anonymous, -} - -/// A strictly linearly increasing sequence number. -/// -/// We start from the current time as unix timestamp in milliseconds. -#[derive(Debug)] -struct SequenceNumber(u64); - -impl SequenceNumber { - fn new() -> Self { - let unix_timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("time to be linear") - .as_nanos(); - - Self(unix_timestamp as u64) - } - - fn next(&mut self) -> u64 { - self.0 = self - .0 - .checked_add(1) - .expect("to not exhaust u64 space for sequence numbers"); - - self.0 - } -} - -impl PublishConfig { - pub(crate) fn get_own_id(&self) -> Option<&PeerId> { - match self { - Self::Signing { author, .. } => Some(author), - Self::Author(author) => Some(author), - _ => None, - } - } -} - -impl From for PublishConfig { - fn from(authenticity: MessageAuthenticity) -> Self { - match authenticity { - MessageAuthenticity::Signed(keypair) => { - let public_key = keypair.public(); - let key_enc = public_key.encode_protobuf(); - let key = if key_enc.len() <= 42 { - // The public key can be inlined in [`rpc_proto::proto::::Message::from`], so we don't include it - // specifically in the [`rpc_proto::proto::Message::key`] field. - None - } else { - // Include the protobuf encoding of the public key in the message. - Some(key_enc) - }; - - PublishConfig::Signing { - keypair, - author: public_key.to_peer_id(), - inline_key: key, - last_seq_no: SequenceNumber::new(), - } - } - MessageAuthenticity::Author(peer_id) => PublishConfig::Author(peer_id), - MessageAuthenticity::RandomAuthor => PublishConfig::RandomAuthor, - MessageAuthenticity::Anonymous => PublishConfig::Anonymous, - } - } -} - -/// Network behaviour that handles the gossipsub protocol. -/// -/// NOTE: Initialisation requires a [`MessageAuthenticity`] and [`Config`] instance. If -/// message signing is disabled, the [`ValidationMode`] in the config should be adjusted to an -/// appropriate level to accept unsigned messages. -/// -/// The DataTransform trait allows applications to optionally add extra encoding/decoding -/// functionality to the underlying messages. This is intended for custom compression algorithms. -/// -/// The TopicSubscriptionFilter allows applications to implement specific filters on topics to -/// prevent unwanted messages being propagated and evaluated. -pub struct Behaviour { - /// Configuration providing gossipsub performance parameters. - config: Config, - - /// Events that need to be yielded to the outside when polling. - events: VecDeque>, - - /// Information used for publishing messages. - publish_config: PublishConfig, - - /// An LRU Time cache for storing seen messages (based on their ID). This cache prevents - /// duplicates from being propagated to the application and on the network. - duplicate_cache: DuplicateCache, - - /// A set of connected peers, indexed by their [`PeerId`] tracking both the [`PeerKind`] and - /// the set of [`ConnectionId`]s. - connected_peers: HashMap, - - /// A set of all explicit peers. These are peers that remain connected and we unconditionally - /// forward messages to, outside of the scoring system. - explicit_peers: HashSet, - - /// A list of peers that have been blacklisted by the user. - /// Messages are not sent to and are rejected from these peers. - blacklisted_peers: HashSet, - - /// Overlay network of connected peers - Maps topics to connected gossipsub peers. - mesh: HashMap>, - - /// Map of topics to list of peers that we publish to, but don't subscribe to. - fanout: HashMap>, - - /// The last publish time for fanout topics. - fanout_last_pub: HashMap, - - ///Storage for backoffs - backoffs: BackoffStorage, - - /// Message cache for the last few heartbeats. - mcache: MessageCache, - - /// Heartbeat interval stream. - heartbeat: Delay, - - /// Number of heartbeats since the beginning of time; this allows us to amortize some resource - /// clean up -- eg backoff clean up. - heartbeat_ticks: u64, - - /// We remember all peers we found through peer exchange, since those peers are not considered - /// as safe as randomly discovered outbound peers. This behaviour diverges from the go - /// implementation to avoid possible love bombing attacks in PX. When disconnecting peers will - /// be removed from this list which may result in a true outbound rediscovery. - px_peers: HashSet, - - /// Set of connected outbound peers (we only consider true outbound peers found through - /// discovery and not by PX). - outbound_peers: HashSet, - - /// Stores optional peer score data together with thresholds and decay interval. - peer_score: Option<(PeerScore, PeerScoreThresholds, Delay)>, - - /// Counts the number of `IHAVE` received from each peer since the last heartbeat. - count_received_ihave: HashMap, - - /// Counts the number of `IWANT` that we sent the each peer since the last heartbeat. - count_sent_iwant: HashMap, - - /// Short term cache for published message ids. This is used for penalizing peers sending - /// our own messages back if the messages are anonymous or use a random author. - published_message_ids: DuplicateCache, - - /// The filter used to handle message subscriptions. - subscription_filter: F, - - /// A general transformation function that can be applied to data received from the wire before - /// calculating the message-id and sending to the application. This is designed to allow the - /// user to implement arbitrary topic-based compression algorithms. - data_transform: D, - - /// Keep track of a set of internal metrics relating to gossipsub. - metrics: Option, - - /// Tracks the numbers of failed messages per peer-id. - failed_messages: HashMap, - - /// Tracks recently sent `IWANT` messages and checks if peers respond to them. - gossip_promises: GossipPromises, -} - -impl Behaviour -where - D: DataTransform + Default, - F: TopicSubscriptionFilter + Default, -{ - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`]. This has no subscription filter and uses no compression. - pub fn new(privacy: MessageAuthenticity, config: Config) -> Result { - Self::new_with_subscription_filter_and_transform( - privacy, - config, - None, - F::default(), - D::default(), - ) - } - - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`]. This has no subscription filter and uses no compression. - /// Metrics can be evaluated by passing a reference to a [`Registry`]. - pub fn new_with_metrics( - privacy: MessageAuthenticity, - config: Config, - metrics_registry: &mut Registry, - metrics_config: MetricsConfig, - ) -> Result { - Self::new_with_subscription_filter_and_transform( - privacy, - config, - Some((metrics_registry, metrics_config)), - F::default(), - D::default(), - ) - } -} - -impl Behaviour -where - D: DataTransform + Default, - F: TopicSubscriptionFilter, -{ - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`] and a custom subscription filter. - pub fn new_with_subscription_filter( - privacy: MessageAuthenticity, - config: Config, - metrics: Option<(&mut Registry, MetricsConfig)>, - subscription_filter: F, - ) -> Result { - Self::new_with_subscription_filter_and_transform( - privacy, - config, - metrics, - subscription_filter, - D::default(), - ) - } -} - -impl Behaviour -where - D: DataTransform, - F: TopicSubscriptionFilter + Default, -{ - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`] and a custom data transform. - pub fn new_with_transform( - privacy: MessageAuthenticity, - config: Config, - metrics: Option<(&mut Registry, MetricsConfig)>, - data_transform: D, - ) -> Result { - Self::new_with_subscription_filter_and_transform( - privacy, - config, - metrics, - F::default(), - data_transform, - ) - } -} - -impl Behaviour -where - D: DataTransform, - F: TopicSubscriptionFilter, -{ - /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a - /// [`Config`] and a custom subscription filter and data transform. - pub fn new_with_subscription_filter_and_transform( - privacy: MessageAuthenticity, - config: Config, - metrics: Option<(&mut Registry, MetricsConfig)>, - subscription_filter: F, - data_transform: D, - ) -> Result { - // Set up the router given the configuration settings. - - // We do not allow configurations where a published message would also be rejected if it - // were received locally. - validate_config(&privacy, config.validation_mode())?; - - Ok(Behaviour { - metrics: metrics.map(|(registry, cfg)| Metrics::new(registry, cfg)), - events: VecDeque::new(), - publish_config: privacy.into(), - duplicate_cache: DuplicateCache::new(config.duplicate_cache_time()), - explicit_peers: HashSet::new(), - blacklisted_peers: HashSet::new(), - mesh: HashMap::new(), - fanout: HashMap::new(), - fanout_last_pub: HashMap::new(), - backoffs: BackoffStorage::new( - &config.prune_backoff(), - config.heartbeat_interval(), - config.backoff_slack(), - ), - mcache: MessageCache::new(config.history_gossip(), config.history_length()), - heartbeat: Delay::new(config.heartbeat_interval() + config.heartbeat_initial_delay()), - heartbeat_ticks: 0, - px_peers: HashSet::new(), - outbound_peers: HashSet::new(), - peer_score: None, - count_received_ihave: HashMap::new(), - count_sent_iwant: HashMap::new(), - connected_peers: HashMap::new(), - published_message_ids: DuplicateCache::new(config.published_message_ids_cache_time()), - config, - subscription_filter, - data_transform, - failed_messages: Default::default(), - gossip_promises: Default::default(), - }) - } -} - -impl Behaviour -where - D: DataTransform + Send + 'static, - F: TopicSubscriptionFilter + Send + 'static, -{ - /// Lists the hashes of the topics we are currently subscribed to. - pub fn topics(&self) -> impl Iterator { - self.mesh.keys() - } - - /// Lists all mesh peers for a certain topic hash. - pub fn mesh_peers(&self, topic_hash: &TopicHash) -> impl Iterator { - self.mesh.get(topic_hash).into_iter().flat_map(|x| x.iter()) - } - - pub fn all_mesh_peers(&self) -> impl Iterator { - let mut res = BTreeSet::new(); - for peers in self.mesh.values() { - res.extend(peers); - } - res.into_iter() - } - - /// Lists all known peers and their associated subscribed topics. - pub fn all_peers(&self) -> impl Iterator)> { - self.connected_peers - .iter() - .map(|(peer_id, peer)| (peer_id, peer.topics.iter().collect())) - } - - /// Lists all known peers and their associated protocol. - pub fn peer_protocol(&self) -> impl Iterator { - self.connected_peers.iter().map(|(k, v)| (k, &v.kind)) - } - - /// Returns the gossipsub score for a given peer, if one exists. - pub fn peer_score(&self, peer_id: &PeerId) -> Option { - self.peer_score - .as_ref() - .map(|(score, ..)| score.score(peer_id)) - } - - /// Subscribe to a topic. - /// - /// Returns [`Ok(true)`] if the subscription worked. Returns [`Ok(false)`] if we were already - /// subscribed. - pub fn subscribe(&mut self, topic: &Topic) -> Result { - tracing::debug!(%topic, "Subscribing to topic"); - let topic_hash = topic.hash(); - if !self.subscription_filter.can_subscribe(&topic_hash) { - return Err(SubscriptionError::NotAllowed); - } - - if self.mesh.contains_key(&topic_hash) { - tracing::debug!(%topic, "Topic is already in the mesh"); - return Ok(false); - } - - // send subscription request to all peers - for (peer_id, peer) in self.connected_peers.iter_mut() { - tracing::debug!(%peer_id, "Sending SUBSCRIBE to peer"); - - peer.sender.subscribe(topic_hash.clone()); - } - - // call JOIN(topic) - // this will add new peers to the mesh for the topic - self.join(&topic_hash); - tracing::debug!(%topic, "Subscribed to topic"); - Ok(true) - } - - /// Unsubscribes from a topic. - /// - /// Returns [`Ok(true)`] if we were subscribed to this topic. - pub fn unsubscribe(&mut self, topic: &Topic) -> Result { - tracing::debug!(%topic, "Unsubscribing from topic"); - let topic_hash = topic.hash(); - - if !self.mesh.contains_key(&topic_hash) { - tracing::debug!(topic=%topic_hash, "Already unsubscribed from topic"); - // we are not subscribed - return Ok(false); - } - - // announce to all peers - for (peer_id, peer) in self.connected_peers.iter_mut() { - tracing::debug!(%peer_id, "Sending UNSUBSCRIBE to peer"); - peer.sender.unsubscribe(topic_hash.clone()); - } - - // call LEAVE(topic) - // this will remove the topic from the mesh - self.leave(&topic_hash); - - tracing::debug!(topic=%topic_hash, "Unsubscribed from topic"); - Ok(true) - } - - /// Publishes a message with multiple topics to the network. - pub fn publish( - &mut self, - topic: impl Into, - data: impl Into>, - ) -> Result { - let data = data.into(); - let topic = topic.into(); - - // Transform the data before building a raw_message. - let transformed_data = self - .data_transform - .outbound_transform(&topic, data.clone())?; - - let raw_message = self.build_raw_message(topic, transformed_data)?; - - // calculate the message id from the un-transformed data - let msg_id = self.config.message_id(&Message { - source: raw_message.source, - data, // the uncompressed form - sequence_number: raw_message.sequence_number, - topic: raw_message.topic.clone(), - }); - - // check that the size doesn't exceed the max transmission size - if raw_message.raw_protobuf_len() > self.config.max_transmit_size() { - return Err(PublishError::MessageTooLarge); - } - - // Check the if the message has been published before - if self.duplicate_cache.contains(&msg_id) { - // This message has already been seen. We don't re-publish messages that have already - // been published on the network. - tracing::warn!( - message=%msg_id, - "Not publishing a message that has already been published" - ); - return Err(PublishError::Duplicate); - } - - tracing::trace!(message=%msg_id, "Publishing message"); - - let topic_hash = raw_message.topic.clone(); - - let mut peers_on_topic = self - .connected_peers - .iter() - .filter(|(_, p)| p.topics.contains(&topic_hash)) - .map(|(peer_id, _)| peer_id) - .peekable(); - - if peers_on_topic.peek().is_none() { - return Err(PublishError::InsufficientPeers); - } - - let mut recipient_peers = HashSet::new(); - - if self.config.flood_publish() { - // Forward to all peers above score and all explicit peers - recipient_peers.extend(peers_on_topic.filter(|p| { - self.explicit_peers.contains(*p) - || !self.score_below_threshold(p, |ts| ts.publish_threshold).0 - })); - } else { - match self.mesh.get(&topic_hash) { - // Mesh peers - Some(mesh_peers) => { - // We have a mesh set. We want to make sure to publish to at least `mesh_n` - // peers (if possible). - let needed_extra_peers = self.config.mesh_n().saturating_sub(mesh_peers.len()); - - if needed_extra_peers > 0 { - // We don't have `mesh_n` peers in our mesh, we will randomly select extras - // and publish to them. - - // Get a random set of peers that are appropriate to send messages too. - let peer_list = get_random_peers( - &self.connected_peers, - &topic_hash, - needed_extra_peers, - |peer| { - !mesh_peers.contains(peer) - && !self.explicit_peers.contains(peer) - && !self - .score_below_threshold(peer, |pst| pst.publish_threshold) - .0 - }, - ); - recipient_peers.extend(peer_list); - } - - recipient_peers.extend(mesh_peers); - } - // Gossipsub peers - None => { - tracing::debug!(topic=%topic_hash, "Topic not in the mesh"); - // `fanout_peers` is always non-empty if it's `Some`. - let fanout_peers = self - .fanout - .get(&topic_hash) - .map(|peers| if peers.is_empty() { None } else { Some(peers) }) - .unwrap_or(None); - // If we have fanout peers add them to the map. - if let Some(peers) = fanout_peers { - for peer in peers { - recipient_peers.insert(*peer); - } - } else { - // We have no fanout peers, select mesh_n of them and add them to the fanout - let mesh_n = self.config.mesh_n(); - let new_peers = - get_random_peers(&self.connected_peers, &topic_hash, mesh_n, { - |p| { - !self.explicit_peers.contains(p) - && !self - .score_below_threshold(p, |pst| pst.publish_threshold) - .0 - } - }); - // Add the new peers to the fanout and recipient peers - self.fanout.insert(topic_hash.clone(), new_peers.clone()); - for peer in new_peers { - tracing::debug!(%peer, "Peer added to fanout"); - recipient_peers.insert(peer); - } - } - // We are publishing to fanout peers - update the time we published - self.fanout_last_pub - .insert(topic_hash.clone(), Instant::now()); - } - } - - // Explicit peers that are part of the topic - recipient_peers - .extend(peers_on_topic.filter(|peer_id| self.explicit_peers.contains(peer_id))); - - // Floodsub peers - for (peer, connections) in &self.connected_peers { - if connections.kind == PeerKind::Floodsub - && !self - .score_below_threshold(peer, |ts| ts.publish_threshold) - .0 - { - recipient_peers.insert(*peer); - } - } - } - - // If the message isn't a duplicate and we have sent it to some peers add it to the - // duplicate cache and memcache. - self.duplicate_cache.insert(msg_id.clone()); - self.mcache.put(&msg_id, raw_message.clone()); - - // If the message is anonymous or has a random author add it to the published message ids - // cache. - if let PublishConfig::RandomAuthor | PublishConfig::Anonymous = self.publish_config { - if !self.config.allow_self_origin() { - self.published_message_ids.insert(msg_id.clone()); - } - } - - // Send to peers we know are subscribed to the topic. - let mut publish_failed = true; - for peer_id in recipient_peers.iter() { - if let Some(peer) = self.connected_peers.get_mut(peer_id) { - tracing::trace!(peer=%peer_id, "Sending message to peer"); - match peer.sender.publish( - raw_message.clone(), - self.config.publish_queue_duration(), - self.metrics.as_mut(), - ) { - Ok(_) => publish_failed = false, - Err(_) => { - self.failed_messages.entry(*peer_id).or_default().priority += 1; - - tracing::warn!(peer_id=%peer_id, "Publish queue full. Could not publish to peer"); - // Downscore the peer due to failed message. - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - } - } - } else { - tracing::error!(peer_id = %peer_id, - "Could not send PUBLISH, peer doesn't exist in connected peer list"); - } - } - - if recipient_peers.is_empty() { - return Err(PublishError::InsufficientPeers); - } - - if publish_failed { - return Err(PublishError::AllQueuesFull(recipient_peers.len())); - } - - // Broadcast IDONTWANT messages - if raw_message.raw_protobuf_len() > self.config.idontwant_message_size_threshold() { - self.send_idontwant(&raw_message, &msg_id, raw_message.source.as_ref()); - } - - tracing::debug!(message=%msg_id, "Published message"); - - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_published_message(&topic_hash); - } - - Ok(msg_id) - } - - /// This function should be called when [`Config::validate_messages()`] is `true` after - /// the message got validated by the caller. Messages are stored in the ['Memcache'] and - /// validation is expected to be fast enough that the messages should still exist in the cache. - /// There are three possible validation outcomes and the outcome is given in acceptance. - /// - /// If acceptance = [`MessageAcceptance::Accept`] the message will get propagated to the - /// network. The `propagation_source` parameter indicates who the message was received by and - /// will not be forwarded back to that peer. - /// - /// If acceptance = [`MessageAcceptance::Reject`] the message will be deleted from the memcache - /// and the Pâ‚„ penalty will be applied to the `propagation_source`. - // - /// If acceptance = [`MessageAcceptance::Ignore`] the message will be deleted from the memcache - /// but no Pâ‚„ penalty will be applied. - /// - /// This function will return true if the message was found in the cache and false if was not - /// in the cache anymore. - /// - /// This should only be called once per message. - pub fn report_message_validation_result( - &mut self, - msg_id: &MessageId, - propagation_source: &PeerId, - acceptance: MessageAcceptance, - ) -> Result { - let reject_reason = match acceptance { - MessageAcceptance::Accept => { - let (raw_message, originating_peers) = match self.mcache.validate(msg_id) { - Some((raw_message, originating_peers)) => { - (raw_message.clone(), originating_peers) - } - None => { - tracing::warn!( - message=%msg_id, - "Message not in cache. Ignoring forwarding" - ); - if let Some(metrics) = self.metrics.as_mut() { - metrics.memcache_miss(); - } - return Ok(false); - } - }; - - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_msg_validation(&raw_message.topic, &acceptance); - } - - self.forward_msg( - msg_id, - raw_message, - Some(propagation_source), - originating_peers, - )?; - return Ok(true); - } - MessageAcceptance::Reject => RejectReason::ValidationFailed, - MessageAcceptance::Ignore => RejectReason::ValidationIgnored, - }; - - if let Some((raw_message, originating_peers)) = self.mcache.remove(msg_id) { - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_msg_validation(&raw_message.topic, &acceptance); - } - - // Tell peer_score about reject - // Reject the original source, and any duplicates we've seen from other peers. - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.reject_message( - propagation_source, - msg_id, - &raw_message.topic, - reject_reason, - ); - for peer in originating_peers.iter() { - peer_score.reject_message(peer, msg_id, &raw_message.topic, reject_reason); - } - } - Ok(true) - } else { - tracing::warn!(message=%msg_id, "Rejected message not in cache"); - Ok(false) - } - } - - /// Register topics to ensure metrics are recorded correctly for these topics. - pub fn register_topics_for_metrics(&mut self, topics: Vec) { - if let Some(metrics) = &mut self.metrics { - metrics.register_allowed_topics(topics); - } - } - - /// Adds a new peer to the list of explicitly connected peers. - pub fn add_explicit_peer(&mut self, peer_id: &PeerId) { - tracing::debug!(peer=%peer_id, "Adding explicit peer"); - - self.explicit_peers.insert(*peer_id); - - self.check_explicit_peer_connection(peer_id); - } - - /// This removes the peer from explicitly connected peers, note that this does not disconnect - /// the peer. - pub fn remove_explicit_peer(&mut self, peer_id: &PeerId) { - tracing::debug!(peer=%peer_id, "Removing explicit peer"); - self.explicit_peers.remove(peer_id); - } - - /// Blacklists a peer. All messages from this peer will be rejected and any message that was - /// created by this peer will be rejected. - pub fn blacklist_peer(&mut self, peer_id: &PeerId) { - if self.blacklisted_peers.insert(*peer_id) { - tracing::debug!(peer=%peer_id, "Peer has been blacklisted"); - } - } - - /// Removes a peer from the blacklist if it has previously been blacklisted. - pub fn remove_blacklisted_peer(&mut self, peer_id: &PeerId) { - if self.blacklisted_peers.remove(peer_id) { - tracing::debug!(peer=%peer_id, "Peer has been removed from the blacklist"); - } - } - - /// Activates the peer scoring system with the given parameters. This will reset all scores - /// if there was already another peer scoring system activated. Returns an error if the - /// params are not valid or if they got already set. - pub fn with_peer_score( - &mut self, - params: PeerScoreParams, - threshold: PeerScoreThresholds, - ) -> Result<(), String> { - self.with_peer_score_and_message_delivery_time_callback(params, threshold, None) - } - - /// Activates the peer scoring system with the given parameters and a message delivery time - /// callback. Returns an error if the parameters got already set. - pub fn with_peer_score_and_message_delivery_time_callback( - &mut self, - params: PeerScoreParams, - threshold: PeerScoreThresholds, - callback: Option, - ) -> Result<(), String> { - params.validate()?; - threshold.validate()?; - - if self.peer_score.is_some() { - return Err("Peer score set twice".into()); - } - - let interval = Delay::new(params.decay_interval); - let peer_score = PeerScore::new_with_message_delivery_time_callback(params, callback); - self.peer_score = Some((peer_score, threshold, interval)); - Ok(()) - } - - /// Sets scoring parameters for a topic. - /// - /// The [`Self::with_peer_score()`] must first be called to initialise peer scoring. - pub fn set_topic_params( - &mut self, - topic: Topic, - params: TopicScoreParams, - ) -> Result<(), &'static str> { - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.set_topic_params(topic.hash(), params); - Ok(()) - } else { - Err("Peer score must be initialised with `with_peer_score()`") - } - } - - /// Returns a scoring parameters for a topic if existent. - pub fn get_topic_params(&self, topic: &Topic) -> Option<&TopicScoreParams> { - self.peer_score.as_ref()?.0.get_topic_params(&topic.hash()) - } - - /// Sets the application specific score for a peer. Returns true if scoring is active and - /// the peer is connected or if the score of the peer is not yet expired, false otherwise. - pub fn set_application_score(&mut self, peer_id: &PeerId, new_score: f64) -> bool { - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.set_application_score(peer_id, new_score) - } else { - false - } - } - - /// Gossipsub JOIN(topic) - adds topic peers to mesh and sends them GRAFT messages. - fn join(&mut self, topic_hash: &TopicHash) { - tracing::debug!(topic=%topic_hash, "Running JOIN for topic"); - - // if we are already in the mesh, return - if self.mesh.contains_key(topic_hash) { - tracing::debug!(topic=%topic_hash, "JOIN: The topic is already in the mesh, ignoring JOIN"); - return; - } - - let mut added_peers = HashSet::new(); - - if let Some(m) = self.metrics.as_mut() { - m.joined(topic_hash) - } - - // check if we have mesh_n peers in fanout[topic] and add them to the mesh if we do, - // removing the fanout entry. - if let Some((_, mut peers)) = self.fanout.remove_entry(topic_hash) { - tracing::debug!( - topic=%topic_hash, - "JOIN: Removing peers from the fanout for topic" - ); - - // remove explicit peers, peers with negative scores, and backoffed peers - peers.retain(|p| { - !self.explicit_peers.contains(p) - && !self.score_below_threshold(p, |_| 0.0).0 - && !self.backoffs.is_backoff_with_slack(topic_hash, p) - }); - - // Add up to mesh_n of them them to the mesh - // NOTE: These aren't randomly added, currently FIFO - let add_peers = std::cmp::min(peers.len(), self.config.mesh_n()); - tracing::debug!( - topic=%topic_hash, - "JOIN: Adding {:?} peers from the fanout for topic", - add_peers - ); - added_peers.extend(peers.iter().take(add_peers)); - - self.mesh.insert( - topic_hash.clone(), - peers.into_iter().take(add_peers).collect(), - ); - - // remove the last published time - self.fanout_last_pub.remove(topic_hash); - } - - let fanaout_added = added_peers.len(); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Fanout, fanaout_added) - } - - // check if we need to get more peers, which we randomly select - if added_peers.len() < self.config.mesh_n() { - // get the peers - let new_peers = get_random_peers( - &self.connected_peers, - topic_hash, - self.config.mesh_n() - added_peers.len(), - |peer| { - !added_peers.contains(peer) - && !self.explicit_peers.contains(peer) - && !self.score_below_threshold(peer, |_| 0.0).0 - && !self.backoffs.is_backoff_with_slack(topic_hash, peer) - }, - ); - added_peers.extend(new_peers.clone()); - // add them to the mesh - tracing::debug!( - "JOIN: Inserting {:?} random peers into the mesh", - new_peers.len() - ); - let mesh_peers = self.mesh.entry(topic_hash.clone()).or_default(); - mesh_peers.extend(new_peers); - } - - let random_added = added_peers.len() - fanaout_added; - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Random, random_added) - } - - for peer_id in added_peers { - // Send a GRAFT control message - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.graft(&peer_id, topic_hash.clone()); - } - if let Some(peer) = &mut self.connected_peers.get_mut(&peer_id) { - tracing::debug!(peer=%peer_id, "JOIN: Sending Graft message to peer"); - peer.sender.graft(Graft { - topic_hash: topic_hash.clone(), - }); - } else { - tracing::error!(peer = %peer_id, - "Could not send GRAFT, peer doesn't exist in connected peer list"); - } - - // If the peer did not previously exist in any mesh, inform the handler - peer_added_to_mesh( - peer_id, - vec![topic_hash], - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - - let mesh_peers = self.mesh_peers(topic_hash).count(); - if let Some(m) = self.metrics.as_mut() { - m.set_mesh_peers(topic_hash, mesh_peers) - } - - tracing::debug!(topic=%topic_hash, "Completed JOIN for topic"); - } - - /// Creates a PRUNE gossipsub action. - fn make_prune( - &mut self, - topic_hash: &TopicHash, - peer: &PeerId, - do_px: bool, - on_unsubscribe: bool, - ) -> Prune { - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.prune(peer, topic_hash.clone()); - } - - match self.connected_peers.get(peer).map(|v| &v.kind) { - Some(PeerKind::Floodsub) => { - tracing::error!("Attempted to prune a Floodsub peer"); - } - Some(PeerKind::Gossipsub) => { - // GossipSub v1.0 -- no peer exchange, the peer won't be able to parse it anyway - return Prune { - topic_hash: topic_hash.clone(), - peers: Vec::new(), - backoff: None, - }; - } - None => { - tracing::error!("Attempted to Prune an unknown peer"); - } - _ => {} // Gossipsub 1.1 peer perform the `Prune` - } - - // Select peers for peer exchange - let peers = if do_px { - get_random_peers( - &self.connected_peers, - topic_hash, - self.config.prune_peers(), - |p| p != peer && !self.score_below_threshold(p, |_| 0.0).0, - ) - .into_iter() - .map(|p| PeerInfo { peer_id: Some(p) }) - .collect() - } else { - Vec::new() - }; - - let backoff = if on_unsubscribe { - self.config.unsubscribe_backoff() - } else { - self.config.prune_backoff() - }; - - // update backoff - self.backoffs.update_backoff(topic_hash, peer, backoff); - - Prune { - topic_hash: topic_hash.clone(), - peers, - backoff: Some(backoff.as_secs()), - } - } - - /// Gossipsub LEAVE(topic) - Notifies mesh\[topic\] peers with PRUNE messages. - fn leave(&mut self, topic_hash: &TopicHash) { - tracing::debug!(topic=%topic_hash, "Running LEAVE for topic"); - - // If our mesh contains the topic, send prune to peers and delete it from the mesh - if let Some((_, peers)) = self.mesh.remove_entry(topic_hash) { - if let Some(m) = self.metrics.as_mut() { - m.left(topic_hash) - } - for peer_id in peers { - // Send a PRUNE control message - let prune = self.make_prune(topic_hash, &peer_id, self.config.do_px(), true); - if let Some(peer) = &mut self.connected_peers.get_mut(&peer_id) { - tracing::debug!(%peer_id, "LEAVE: Sending PRUNE to peer"); - peer.sender.prune(prune); - } else { - tracing::error!(peer = %peer_id, - "Could not send PRUNE, peer doesn't exist in connected peer list"); - } - - // If the peer did not previously exist in any mesh, inform the handler - peer_removed_from_mesh( - peer_id, - topic_hash, - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - } - tracing::debug!(topic=%topic_hash, "Completed LEAVE for topic"); - } - - /// Checks if the given peer is still connected and if not dials the peer again. - fn check_explicit_peer_connection(&mut self, peer_id: &PeerId) { - if !self.connected_peers.contains_key(peer_id) { - // Connect to peer - tracing::debug!(peer=%peer_id, "Connecting to explicit peer"); - self.events.push_back(ToSwarm::Dial { - opts: DialOpts::peer_id(*peer_id).build(), - }); - } - } - - /// Determines if a peer's score is below a given `PeerScoreThreshold` chosen via the - /// `threshold` parameter. - fn score_below_threshold( - &self, - peer_id: &PeerId, - threshold: impl Fn(&PeerScoreThresholds) -> f64, - ) -> (bool, f64) { - Self::score_below_threshold_from_scores(&self.peer_score, peer_id, threshold) - } - - fn score_below_threshold_from_scores( - peer_score: &Option<(PeerScore, PeerScoreThresholds, Delay)>, - peer_id: &PeerId, - threshold: impl Fn(&PeerScoreThresholds) -> f64, - ) -> (bool, f64) { - if let Some((peer_score, thresholds, ..)) = peer_score { - let score = peer_score.score(peer_id); - if score < threshold(thresholds) { - return (true, score); - } - (false, score) - } else { - (false, 0.0) - } - } - - /// Handles an IHAVE control message. Checks our cache of messages. If the message is unknown, - /// requests it with an IWANT control message. - fn handle_ihave(&mut self, peer_id: &PeerId, ihave_msgs: Vec<(TopicHash, Vec)>) { - // We ignore IHAVE gossip from any peer whose score is below the gossip threshold - if let (true, score) = self.score_below_threshold(peer_id, |pst| pst.gossip_threshold) { - tracing::debug!( - peer=%peer_id, - %score, - "IHAVE: ignoring peer with score below threshold" - ); - return; - } - - // IHAVE flood protection - let peer_have = self.count_received_ihave.entry(*peer_id).or_insert(0); - *peer_have += 1; - if *peer_have > self.config.max_ihave_messages() { - tracing::debug!( - peer=%peer_id, - "IHAVE: peer has advertised too many times ({}) within this heartbeat \ - interval; ignoring", - *peer_have - ); - return; - } - - if let Some(iasked) = self.count_sent_iwant.get(peer_id) { - if *iasked >= self.config.max_ihave_length() { - tracing::debug!( - peer=%peer_id, - "IHAVE: peer has already advertised too many messages ({}); ignoring", - *iasked - ); - return; - } - } - - tracing::trace!(peer=%peer_id, "Handling IHAVE for peer"); - - let mut iwant_ids = HashSet::new(); - - let want_message = |id: &MessageId| { - if self.duplicate_cache.contains(id) { - return false; - } - - !self.gossip_promises.contains(id) - }; - - for (topic, ids) in ihave_msgs { - // only process the message if we are subscribed - if !self.mesh.contains_key(&topic) { - tracing::debug!( - %topic, - "IHAVE: Ignoring IHAVE - Not subscribed to topic" - ); - continue; - } - - for id in ids.into_iter().filter(want_message) { - // have not seen this message and are not currently requesting it - if iwant_ids.insert(id) { - // Register the IWANT metric - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_iwant(&topic); - } - } - } - } - - if !iwant_ids.is_empty() { - let iasked = self.count_sent_iwant.entry(*peer_id).or_insert(0); - let mut iask = iwant_ids.len(); - if *iasked + iask > self.config.max_ihave_length() { - iask = self.config.max_ihave_length().saturating_sub(*iasked); - } - - // Send the list of IWANT control messages - tracing::debug!( - peer=%peer_id, - "IHAVE: Asking for {} out of {} messages from peer", - iask, - iwant_ids.len() - ); - - // Ask in random order - let mut iwant_ids_vec: Vec<_> = iwant_ids.into_iter().collect(); - let mut rng = thread_rng(); - iwant_ids_vec.partial_shuffle(&mut rng, iask); - - iwant_ids_vec.truncate(iask); - *iasked += iask; - - self.gossip_promises.add_promise( - *peer_id, - &iwant_ids_vec, - Instant::now() + self.config.iwant_followup_time(), - ); - - if let Some(peer) = &mut self.connected_peers.get_mut(peer_id) { - tracing::trace!( - peer=%peer_id, - "IHAVE: Asking for the following messages from peer: {:?}", - iwant_ids_vec - ); - - if peer - .sender - .iwant(IWant { - message_ids: iwant_ids_vec, - }) - .is_err() - { - tracing::warn!(peer=%peer_id, "Send Queue full. Could not send IWANT"); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - // Increment failed message count - self.failed_messages - .entry(*peer_id) - .or_default() - .non_priority += 1; - } - } else { - tracing::error!(peer = %peer_id, - "Could not send IWANT, peer doesn't exist in connected peer list"); - } - } - tracing::trace!(peer=%peer_id, "Completed IHAVE handling for peer"); - } - - /// Handles an IWANT control message. Checks our cache of messages. If the message exists it is - /// forwarded to the requesting peer. - fn handle_iwant(&mut self, peer_id: &PeerId, iwant_msgs: Vec) { - // We ignore IWANT gossip from any peer whose score is below the gossip threshold - if let (true, score) = self.score_below_threshold(peer_id, |pst| pst.gossip_threshold) { - tracing::debug!( - peer=%peer_id, - "IWANT: ignoring peer with score below threshold [score = {}]", - score - ); - return; - } - - tracing::debug!(peer=%peer_id, "Handling IWANT for peer"); - - for id in iwant_msgs { - // If we have it and the IHAVE count is not above the threshold, - // forward the message. - if let Some((msg, count)) = self - .mcache - .get_with_iwant_counts(&id, peer_id) - .map(|(msg, count)| (msg.clone(), count)) - { - if count > self.config.gossip_retransimission() { - tracing::debug!( - peer=%peer_id, - message=%id, - "IWANT: Peer has asked for message too many times; ignoring request" - ); - } else if let Some(peer) = &mut self.connected_peers.get_mut(peer_id) { - if peer.dont_send_received.get(&id).is_some() { - tracing::debug!(%peer_id, message=%id, "Peer already sent IDONTWANT for this message"); - continue; - } - - tracing::debug!(peer=%peer_id, "IWANT: Sending cached messages to peer"); - if peer - .sender - .forward( - msg, - self.config.forward_queue_duration(), - self.metrics.as_mut(), - ) - .is_err() - { - // Downscore the peer - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - // Increment the failed message count - self.failed_messages - .entry(*peer_id) - .or_default() - .non_priority += 1; - } - } else { - tracing::error!(peer = %peer_id, - "Could not send IWANT, peer doesn't exist in connected peer list"); - } - } - } - tracing::debug!(peer=%peer_id, "Completed IWANT handling for peer"); - } - - /// Handles GRAFT control messages. If subscribed to the topic, adds the peer to mesh, if not, - /// responds with PRUNE messages. - fn handle_graft(&mut self, peer_id: &PeerId, topics: Vec) { - tracing::debug!(peer=%peer_id, "Handling GRAFT message for peer"); - - let mut to_prune_topics = HashSet::new(); - - let mut do_px = self.config.do_px(); - - // For each topic, if a peer has grafted us, then we necessarily must be in their mesh - // and they must be subscribed to the topic. Ensure we have recorded the mapping. - for topic in &topics { - let Some(connected_peer) = self.connected_peers.get_mut(peer_id) else { - tracing::error!(peer_id = %peer_id, "Peer non-existent when handling graft"); - return; - }; - if connected_peer.topics.insert(topic.clone()) { - if let Some(m) = self.metrics.as_mut() { - m.inc_topic_peers(topic); - } - } - } - - // we don't GRAFT to/from explicit peers; complain loudly if this happens - if self.explicit_peers.contains(peer_id) { - tracing::warn!(peer=%peer_id, "GRAFT: ignoring request from direct peer"); - // this is possibly a bug from non-reciprocal configuration; send a PRUNE for all topics - to_prune_topics = topics.into_iter().collect(); - // but don't PX - do_px = false - } else { - let (below_zero, score) = self.score_below_threshold(peer_id, |_| 0.0); - let now = Instant::now(); - for topic_hash in topics { - if let Some(peers) = self.mesh.get_mut(&topic_hash) { - // if the peer is already in the mesh ignore the graft - if peers.contains(peer_id) { - tracing::debug!( - peer=%peer_id, - topic=%&topic_hash, - "GRAFT: Received graft for peer that is already in topic" - ); - continue; - } - - // make sure we are not backing off that peer - if let Some(backoff_time) = self.backoffs.get_backoff_time(&topic_hash, peer_id) - { - if backoff_time > now { - tracing::warn!( - peer=%peer_id, - "[Penalty] Peer attempted graft within backoff time, penalizing" - ); - // add behavioural penalty - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_score_penalty(Penalty::GraftBackoff); - } - peer_score.add_penalty(peer_id, 1); - - // check the flood cutoff - // See: https://github.com/rust-lang/rust-clippy/issues/10061 - #[allow(unknown_lints, clippy::unchecked_duration_subtraction)] - let flood_cutoff = (backoff_time - + self.config.graft_flood_threshold()) - - self.config.prune_backoff(); - if flood_cutoff > now { - //extra penalty - peer_score.add_penalty(peer_id, 1); - } - } - // no PX - do_px = false; - - to_prune_topics.insert(topic_hash.clone()); - continue; - } - } - - // check the score - if below_zero { - // we don't GRAFT peers with negative score - tracing::debug!( - peer=%peer_id, - %score, - topic=%topic_hash, - "GRAFT: ignoring peer with negative score" - ); - // we do send them PRUNE however, because it's a matter of protocol correctness - to_prune_topics.insert(topic_hash.clone()); - // but we won't PX to them - do_px = false; - continue; - } - - // check mesh upper bound and only allow graft if the upper bound is not reached or - // if it is an outbound peer - if peers.len() >= self.config.mesh_n_high() - && !self.outbound_peers.contains(peer_id) - { - to_prune_topics.insert(topic_hash.clone()); - continue; - } - - // add peer to the mesh - tracing::debug!( - peer=%peer_id, - topic=%topic_hash, - "GRAFT: Mesh link added for peer in topic" - ); - - if peers.insert(*peer_id) { - if let Some(m) = self.metrics.as_mut() { - m.peers_included(&topic_hash, Inclusion::Subscribed, 1) - } - } - - // If the peer did not previously exist in any mesh, inform the handler - peer_added_to_mesh( - *peer_id, - vec![&topic_hash], - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.graft(peer_id, topic_hash); - } - } else { - // don't do PX when there is an unknown topic to avoid leaking our peers - do_px = false; - tracing::debug!( - peer=%peer_id, - topic=%topic_hash, - "GRAFT: Received graft for unknown topic from peer" - ); - // spam hardening: ignore GRAFTs for unknown topics - continue; - } - } - } - - if !to_prune_topics.is_empty() { - // build the prune messages to send - let on_unsubscribe = false; - - let mut sender = match self.connected_peers.get_mut(peer_id) { - Some(connected_peer) => connected_peer.sender.clone(), - None => { - tracing::error!(peer_id = %peer_id, "Peer non-existent when handling graft and obtaining a sender"); - return; - } - }; - - for prune in to_prune_topics - .iter() - .map(|t| self.make_prune(t, peer_id, do_px, on_unsubscribe)) - { - sender.prune(prune); - } - // Send the prune messages to the peer - tracing::debug!( - peer=%peer_id, - "GRAFT: Not subscribed to topics - Sending PRUNE to peer" - ); - } - tracing::debug!(peer=%peer_id, "Completed GRAFT handling for peer"); - } - - fn remove_peer_from_mesh( - &mut self, - peer_id: &PeerId, - topic_hash: &TopicHash, - backoff: Option, - always_update_backoff: bool, - reason: Churn, - ) { - let mut update_backoff = always_update_backoff; - if let Some(peers) = self.mesh.get_mut(topic_hash) { - // remove the peer if it exists in the mesh - if peers.remove(peer_id) { - tracing::debug!( - peer=%peer_id, - topic=%topic_hash, - "PRUNE: Removing peer from the mesh for topic" - ); - if let Some(m) = self.metrics.as_mut() { - m.peers_removed(topic_hash, reason, 1) - } - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.prune(peer_id, topic_hash.clone()); - } - - update_backoff = true; - - // inform the handler - peer_removed_from_mesh( - *peer_id, - topic_hash, - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - } - if update_backoff { - let time = if let Some(backoff) = backoff { - Duration::from_secs(backoff) - } else { - self.config.prune_backoff() - }; - // is there a backoff specified by the peer? if so obey it. - self.backoffs.update_backoff(topic_hash, peer_id, time); - } - } - - /// Handles PRUNE control messages. Removes peer from the mesh. - fn handle_prune( - &mut self, - peer_id: &PeerId, - prune_data: Vec<(TopicHash, Vec, Option)>, - ) { - tracing::debug!(peer=%peer_id, "Handling PRUNE message for peer"); - let (below_threshold, score) = - self.score_below_threshold(peer_id, |pst| pst.accept_px_threshold); - for (topic_hash, px, backoff) in prune_data { - self.remove_peer_from_mesh(peer_id, &topic_hash, backoff, true, Churn::Prune); - - if self.mesh.contains_key(&topic_hash) { - //connect to px peers - if !px.is_empty() { - // we ignore PX from peers with insufficient score - if below_threshold { - tracing::debug!( - peer=%peer_id, - %score, - topic=%topic_hash, - "PRUNE: ignoring PX from peer with insufficient score" - ); - continue; - } - - // NOTE: We cannot dial any peers from PX currently as we typically will not - // know their multiaddr. Until SignedRecords are spec'd this - // remains a stub. By default `config.prune_peers()` is set to zero and - // this is skipped. If the user modifies this, this will only be able to - // dial already known peers (from an external discovery mechanism for - // example). - if self.config.prune_peers() > 0 { - self.px_connect(px); - } - } - } - } - tracing::debug!(peer=%peer_id, "Completed PRUNE handling for peer"); - } - - fn px_connect(&mut self, mut px: Vec) { - let n = self.config.prune_peers(); - // Ignore peerInfo with no ID - // - //TODO: Once signed records are spec'd: Can we use peerInfo without any IDs if they have a - // signed peer record? - px.retain(|p| p.peer_id.is_some()); - if px.len() > n { - // only use at most prune_peers many random peers - let mut rng = thread_rng(); - px.partial_shuffle(&mut rng, n); - px = px.into_iter().take(n).collect(); - } - - for p in px { - // TODO: Once signed records are spec'd: extract signed peer record if given and handle - // it, see https://github.com/libp2p/specs/pull/217 - if let Some(peer_id) = p.peer_id { - // mark as px peer - self.px_peers.insert(peer_id); - - // dial peer - self.events.push_back(ToSwarm::Dial { - opts: DialOpts::peer_id(peer_id).build(), - }); - } - } - } - - /// Applies some basic checks to whether this message is valid. Does not apply user validation - /// checks. - fn message_is_valid( - &mut self, - msg_id: &MessageId, - raw_message: &mut RawMessage, - propagation_source: &PeerId, - ) -> bool { - tracing::debug!( - peer=%propagation_source, - message=%msg_id, - "Handling message from peer" - ); - - // Reject any message from a blacklisted peer - if self.blacklisted_peers.contains(propagation_source) { - tracing::debug!( - peer=%propagation_source, - "Rejecting message from blacklisted peer" - ); - self.gossip_promises - .reject_message(msg_id, &RejectReason::BlackListedPeer); - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.reject_message( - propagation_source, - msg_id, - &raw_message.topic, - RejectReason::BlackListedPeer, - ); - } - return false; - } - - // Also reject any message that originated from a blacklisted peer - if let Some(source) = raw_message.source.as_ref() { - if self.blacklisted_peers.contains(source) { - tracing::debug!( - peer=%propagation_source, - %source, - "Rejecting message from peer because of blacklisted source" - ); - self.handle_invalid_message( - propagation_source, - raw_message, - RejectReason::BlackListedSource, - ); - return false; - } - } - - // If we are not validating messages, assume this message is validated - // This will allow the message to be gossiped without explicitly calling - // `validate_message`. - if !self.config.validate_messages() { - raw_message.validated = true; - } - - // reject messages claiming to be from ourselves but not locally published - let self_published = !self.config.allow_self_origin() - && if let Some(own_id) = self.publish_config.get_own_id() { - own_id != propagation_source && raw_message.source.as_ref() == Some(own_id) - } else { - self.published_message_ids.contains(msg_id) - }; - - if self_published { - tracing::debug!( - message=%msg_id, - source=%propagation_source, - "Dropping message claiming to be from self but forwarded from source" - ); - self.handle_invalid_message(propagation_source, raw_message, RejectReason::SelfOrigin); - return false; - } - - true - } - - /// Handles a newly received [`RawMessage`]. - /// - /// Forwards the message to all peers in the mesh. - fn handle_received_message( - &mut self, - mut raw_message: RawMessage, - propagation_source: &PeerId, - ) { - // Record the received metric - if let Some(metrics) = self.metrics.as_mut() { - metrics.msg_recvd_unfiltered(&raw_message.topic, raw_message.raw_protobuf_len()); - } - - // Try and perform the data transform to the message. If it fails, consider it invalid. - let message = match self.data_transform.inbound_transform(raw_message.clone()) { - Ok(message) => message, - Err(e) => { - tracing::debug!("Invalid message. Transform error: {:?}", e); - // Reject the message and return - self.handle_invalid_message( - propagation_source, - &raw_message, - RejectReason::ValidationError(ValidationError::TransformFailed), - ); - return; - } - }; - - // Calculate the message id on the transformed data. - let msg_id = self.config.message_id(&message); - - if let Some(metrics) = self.metrics.as_mut() { - if let Some(peer) = self.connected_peers.get_mut(propagation_source) { - // Record if we received a message that we already sent a IDONTWANT for to the peer - if peer.dont_send_sent.contains_key(&msg_id) { - metrics.register_idontwant_messages_ignored_per_topic(&raw_message.topic); - } - } - } - - // Check the validity of the message - // Peers get penalized if this message is invalid. We don't add it to the duplicate cache - // and instead continually penalize peers that repeatedly send this message. - if !self.message_is_valid(&msg_id, &mut raw_message, propagation_source) { - return; - } - - if !self.duplicate_cache.insert(msg_id.clone()) { - tracing::debug!(message=%msg_id, "Message already received, ignoring"); - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.duplicated_message(propagation_source, &msg_id, &message.topic); - } - self.mcache.observe_duplicate(&msg_id, propagation_source); - // track metrics for the source of the duplicates - if let Some(metrics) = self.metrics.as_mut() { - if self - .mesh - .get(&message.topic) - .is_some_and(|peers| peers.contains(propagation_source)) - { - // duplicate was received from a mesh peer - metrics.mesh_duplicates(&message.topic); - } else if self - .gossip_promises - .contains_peer(&msg_id, propagation_source) - { - // duplicate was received from an iwant request - metrics.iwant_duplicates(&message.topic); - } else { - tracing::warn!( - messsage=%msg_id, - peer=%propagation_source, - topic=%message.topic, - "Peer should not have sent message" - ); - } - } - return; - } - - // Broadcast IDONTWANT messages - if raw_message.raw_protobuf_len() > self.config.idontwant_message_size_threshold() { - self.send_idontwant(&raw_message, &msg_id, Some(propagation_source)); - } - - tracing::debug!( - message=%msg_id, - "Put message in duplicate_cache and resolve promises" - ); - - // Record the received message with the metrics - if let Some(metrics) = self.metrics.as_mut() { - metrics.msg_recvd(&message.topic); - } - - // Consider the message as delivered for gossip promises. - self.gossip_promises.message_delivered(&msg_id); - - // Tells score that message arrived (but is maybe not fully validated yet). - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.validate_message(propagation_source, &msg_id, &message.topic); - } - - // Add the message to our memcache - self.mcache.put(&msg_id, raw_message.clone()); - - // Dispatch the message to the user if we are subscribed to any of the topics - if self.mesh.contains_key(&message.topic) { - tracing::debug!("Sending received message to user"); - self.events - .push_back(ToSwarm::GenerateEvent(Event::Message { - propagation_source: *propagation_source, - message_id: msg_id.clone(), - message, - })); - } else { - tracing::debug!( - topic=%message.topic, - "Received message on a topic we are not subscribed to" - ); - return; - } - - // forward the message to mesh peers, if no validation is required - if !self.config.validate_messages() { - if self - .forward_msg( - &msg_id, - raw_message, - Some(propagation_source), - HashSet::new(), - ) - .is_err() - { - tracing::error!("Failed to forward message. Too large"); - } - tracing::debug!(message=%msg_id, "Completed message handling for message"); - } - } - - // Handles invalid messages received. - fn handle_invalid_message( - &mut self, - propagation_source: &PeerId, - raw_message: &RawMessage, - reject_reason: RejectReason, - ) { - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_invalid_message(&raw_message.topic); - } - - if let Ok(message) = self.data_transform.inbound_transform(raw_message.clone()) { - let message_id = self.config.message_id(&message); - - peer_score.reject_message( - propagation_source, - &message_id, - &message.topic, - reject_reason, - ); - - self.gossip_promises - .reject_message(&message_id, &reject_reason); - } else { - // The message is invalid, we reject it ignoring any gossip promises. If a peer is - // advertising this message via an IHAVE and it's invalid it will be double - // penalized, one for sending us an invalid and again for breaking a promise. - peer_score.reject_invalid_message(propagation_source, &raw_message.topic); - } - } - } - - /// Handles received subscriptions. - fn handle_received_subscriptions( - &mut self, - subscriptions: &[Subscription], - propagation_source: &PeerId, - ) { - tracing::debug!( - source=%propagation_source, - "Handling subscriptions: {:?}", - subscriptions, - ); - - let mut unsubscribed_peers = Vec::new(); - - let Some(peer) = self.connected_peers.get_mut(propagation_source) else { - tracing::error!( - peer=%propagation_source, - "Subscription by unknown peer" - ); - return; - }; - - // Collect potential graft topics for the peer. - let mut topics_to_graft = Vec::new(); - - // Notify the application about the subscription, after the grafts are sent. - let mut application_event = Vec::new(); - - let filtered_topics = match self - .subscription_filter - .filter_incoming_subscriptions(subscriptions, &peer.topics) - { - Ok(topics) => topics, - Err(s) => { - tracing::error!( - peer=%propagation_source, - "Subscription filter error: {}; ignoring RPC from peer", - s - ); - return; - } - }; - - for subscription in filtered_topics { - // get the peers from the mapping, or insert empty lists if the topic doesn't exist - let topic_hash = &subscription.topic_hash; - - match subscription.action { - SubscriptionAction::Subscribe => { - // add to the peer_topics mapping - if peer.topics.insert(topic_hash.clone()) { - tracing::debug!( - peer=%propagation_source, - topic=%topic_hash, - "SUBSCRIPTION: Adding gossip peer to topic" - ); - - if let Some(m) = self.metrics.as_mut() { - m.inc_topic_peers(topic_hash); - } - } - // if the mesh needs peers add the peer to the mesh - if !self.explicit_peers.contains(propagation_source) - && peer.kind.is_gossipsub() - && !Self::score_below_threshold_from_scores( - &self.peer_score, - propagation_source, - |_| 0.0, - ) - .0 - && !self - .backoffs - .is_backoff_with_slack(topic_hash, propagation_source) - { - if let Some(peers) = self.mesh.get_mut(topic_hash) { - if peers.len() < self.config.mesh_n_low() - && peers.insert(*propagation_source) - { - tracing::debug!( - peer=%propagation_source, - topic=%topic_hash, - "SUBSCRIPTION: Adding peer to the mesh for topic" - ); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Subscribed, 1) - } - // send graft to the peer - tracing::debug!( - peer=%propagation_source, - topic=%topic_hash, - "Sending GRAFT to peer for topic" - ); - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.graft(propagation_source, topic_hash.clone()); - } - topics_to_graft.push(topic_hash.clone()); - } - } - } - // generates a subscription event to be polled - application_event.push(ToSwarm::GenerateEvent(Event::Subscribed { - peer_id: *propagation_source, - topic: topic_hash.clone(), - })); - } - SubscriptionAction::Unsubscribe => { - // remove topic from the peer_topics mapping - if peer.topics.remove(topic_hash) { - tracing::debug!( - peer=%propagation_source, - topic=%topic_hash, - "SUBSCRIPTION: Removing gossip peer from topic" - ); - - if let Some(m) = self.metrics.as_mut() { - m.dec_topic_peers(topic_hash); - } - } - - unsubscribed_peers.push((*propagation_source, topic_hash.clone())); - // generate an unsubscribe event to be polled - application_event.push(ToSwarm::GenerateEvent(Event::Unsubscribed { - peer_id: *propagation_source, - topic: topic_hash.clone(), - })); - } - } - } - - // remove unsubscribed peers from the mesh and fanout if they exist there. - for (peer_id, topic_hash) in unsubscribed_peers { - self.fanout - .get_mut(&topic_hash) - .map(|peers| peers.remove(&peer_id)); - self.remove_peer_from_mesh(&peer_id, &topic_hash, None, false, Churn::Unsub); - } - - // Potentially inform the handler if we have added this peer to a mesh for the first time. - let topics_joined = topics_to_graft.iter().collect::>(); - if !topics_joined.is_empty() { - peer_added_to_mesh( - *propagation_source, - topics_joined, - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - - // If we need to send grafts to peer, do so immediately, rather than waiting for the - // heartbeat. - if let Some(peer) = &mut self.connected_peers.get_mut(propagation_source) { - for topic_hash in topics_to_graft.into_iter() { - peer.sender.graft(Graft { topic_hash }); - } - } else { - tracing::error!(peer = %propagation_source, - "Could not send GRAFT, peer doesn't exist in connected peer list"); - } - - // Notify the application of the subscriptions - for event in application_event { - self.events.push_back(event); - } - - tracing::trace!( - source=%propagation_source, - "Completed handling subscriptions from source" - ); - } - - /// Applies penalties to peers that did not respond to our IWANT requests. - fn apply_iwant_penalties(&mut self) { - if let Some((peer_score, ..)) = &mut self.peer_score { - for (peer, count) in self.gossip_promises.get_broken_promises() { - // We do not apply penalties to nodes that have disconnected. - if self.connected_peers.contains_key(&peer) { - peer_score.add_penalty(&peer, count); - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_score_penalty(Penalty::BrokenPromise); - } - } - } - } - } - - /// Heartbeat function which shifts the memcache and updates the mesh. - fn heartbeat(&mut self) { - tracing::debug!("Starting heartbeat"); - let start = Instant::now(); - - // Every heartbeat we sample the send queues to add to our metrics. We do this intentionally - // before we add all the gossip from this heartbeat in order to gain a true measure of - // steady-state size of the queues. - if let Some(m) = &mut self.metrics { - for sender_queue in self.connected_peers.values_mut().map(|v| &v.sender) { - m.observe_priority_queue_size(sender_queue.priority_len()); - m.observe_non_priority_queue_size(sender_queue.non_priority_len()); - } - } - - self.heartbeat_ticks += 1; - - let mut to_graft = HashMap::new(); - let mut to_prune = HashMap::new(); - let mut no_px = HashSet::new(); - - // clean up expired backoffs - self.backoffs.heartbeat(); - - // clean up ihave counters - self.count_sent_iwant.clear(); - self.count_received_ihave.clear(); - - // apply iwant penalties - self.apply_iwant_penalties(); - - // check connections to explicit peers - if self.heartbeat_ticks % self.config.check_explicit_peers_ticks() == 0 { - for p in self.explicit_peers.clone() { - self.check_explicit_peer_connection(&p); - } - } - - // Cache the scores of all connected peers, and record metrics for current penalties. - let mut scores = HashMap::with_capacity(self.connected_peers.len()); - if let Some((peer_score, ..)) = &self.peer_score { - for peer_id in self.connected_peers.keys() { - scores - .entry(peer_id) - .or_insert_with(|| peer_score.metric_score(peer_id, self.metrics.as_mut())); - } - } - - // maintain the mesh for each topic - for (topic_hash, peers) in self.mesh.iter_mut() { - let explicit_peers = &self.explicit_peers; - let backoffs = &self.backoffs; - let outbound_peers = &self.outbound_peers; - - // drop all peers with negative score, without PX - // if there is at some point a stable retain method for BTreeSet the following can be - // written more efficiently with retain. - let mut to_remove_peers = Vec::new(); - for peer_id in peers.iter() { - let peer_score = *scores.get(peer_id).unwrap_or(&0.0); - - // Record the score per mesh - if let Some(metrics) = self.metrics.as_mut() { - metrics.observe_mesh_peers_score(topic_hash, peer_score); - } - - if peer_score < 0.0 { - tracing::debug!( - peer=%peer_id, - score=%peer_score, - topic=%topic_hash, - "HEARTBEAT: Prune peer with negative score" - ); - - let current_topic = to_prune.entry(*peer_id).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - no_px.insert(*peer_id); - to_remove_peers.push(*peer_id); - } - } - - if let Some(m) = self.metrics.as_mut() { - m.peers_removed(topic_hash, Churn::BadScore, to_remove_peers.len()) - } - - for peer_id in to_remove_peers { - peers.remove(&peer_id); - } - - // too little peers - add some - if peers.len() < self.config.mesh_n_low() { - tracing::debug!( - topic=%topic_hash, - "HEARTBEAT: Mesh low. Topic contains: {} needs: {}", - peers.len(), - self.config.mesh_n_low() - ); - // not enough peers - get mesh_n - current_length more - let desired_peers = self.config.mesh_n() - peers.len(); - let peer_list = - get_random_peers(&self.connected_peers, topic_hash, desired_peers, |peer| { - !peers.contains(peer) - && !explicit_peers.contains(peer) - && !backoffs.is_backoff_with_slack(topic_hash, peer) - && *scores.get(peer).unwrap_or(&0.0) >= 0.0 - }); - for peer in &peer_list { - let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - // update the mesh - tracing::debug!("Updating mesh, new mesh: {:?}", peer_list); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Random, peer_list.len()) - } - peers.extend(peer_list); - } - - // too many peers - remove some - if peers.len() > self.config.mesh_n_high() { - tracing::debug!( - topic=%topic_hash, - "HEARTBEAT: Mesh high. Topic contains: {} needs: {}", - peers.len(), - self.config.mesh_n_high() - ); - let excess_peer_no = peers.len() - self.config.mesh_n(); - - // shuffle the peers and then sort by score ascending beginning with the worst - let mut rng = thread_rng(); - let mut shuffled = peers.iter().copied().collect::>(); - shuffled.shuffle(&mut rng); - shuffled.sort_by(|p1, p2| { - let score_p1 = *scores.get(p1).unwrap_or(&0.0); - let score_p2 = *scores.get(p2).unwrap_or(&0.0); - - score_p1.partial_cmp(&score_p2).unwrap_or(Ordering::Equal) - }); - // shuffle everything except the last retain_scores many peers (the best ones) - shuffled[..peers.len() - self.config.retain_scores()].shuffle(&mut rng); - - // count total number of outbound peers - let mut outbound = { - let outbound_peers = &self.outbound_peers; - shuffled - .iter() - .filter(|p| outbound_peers.contains(*p)) - .count() - }; - - // remove the first excess_peer_no allowed (by outbound restrictions) peers adding - // them to to_prune - let mut removed = 0; - for peer in shuffled { - if removed == excess_peer_no { - break; - } - if self.outbound_peers.contains(&peer) { - if outbound <= self.config.mesh_outbound_min() { - // do not remove anymore outbound peers - continue; - } - // an outbound peer gets removed - outbound -= 1; - } - - // remove the peer - peers.remove(&peer); - let current_topic = to_prune.entry(peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - removed += 1; - } - - if let Some(m) = self.metrics.as_mut() { - m.peers_removed(topic_hash, Churn::Excess, removed) - } - } - - // do we have enough outbound peers? - if peers.len() >= self.config.mesh_n_low() { - // count number of outbound peers we have - let outbound = { peers.iter().filter(|p| outbound_peers.contains(*p)).count() }; - - // if we have not enough outbound peers, graft to some new outbound peers - if outbound < self.config.mesh_outbound_min() { - let needed = self.config.mesh_outbound_min() - outbound; - let peer_list = - get_random_peers(&self.connected_peers, topic_hash, needed, |peer| { - !peers.contains(peer) - && !explicit_peers.contains(peer) - && !backoffs.is_backoff_with_slack(topic_hash, peer) - && *scores.get(peer).unwrap_or(&0.0) >= 0.0 - && outbound_peers.contains(peer) - }); - for peer in &peer_list { - let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - // update the mesh - tracing::debug!("Updating mesh, new mesh: {:?}", peer_list); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Outbound, peer_list.len()) - } - peers.extend(peer_list); - } - } - - // should we try to improve the mesh with opportunistic grafting? - if self.heartbeat_ticks % self.config.opportunistic_graft_ticks() == 0 - && peers.len() > 1 - && self.peer_score.is_some() - { - if let Some((_, thresholds, _)) = &self.peer_score { - // Opportunistic grafting works as follows: we check the median score of peers - // in the mesh; if this score is below the opportunisticGraftThreshold, we - // select a few peers at random with score over the median. - // The intention is to (slowly) improve an underperforming mesh by introducing - // good scoring peers that may have been gossiping at us. This allows us to - // get out of sticky situations where we are stuck with poor peers and also - // recover from churn of good peers. - - // now compute the median peer score in the mesh - let mut peers_by_score: Vec<_> = peers.iter().collect(); - peers_by_score.sort_by(|p1, p2| { - let p1_score = *scores.get(p1).unwrap_or(&0.0); - let p2_score = *scores.get(p2).unwrap_or(&0.0); - p1_score.partial_cmp(&p2_score).unwrap_or(Equal) - }); - - let middle = peers_by_score.len() / 2; - let median = if peers_by_score.len() % 2 == 0 { - let sub_middle_peer = *peers_by_score - .get(middle - 1) - .expect("middle < vector length and middle > 0 since peers.len() > 0"); - let sub_middle_score = *scores.get(sub_middle_peer).unwrap_or(&0.0); - let middle_peer = - *peers_by_score.get(middle).expect("middle < vector length"); - let middle_score = *scores.get(middle_peer).unwrap_or(&0.0); - - (sub_middle_score + middle_score) * 0.5 - } else { - *scores - .get(*peers_by_score.get(middle).expect("middle < vector length")) - .unwrap_or(&0.0) - }; - - // if the median score is below the threshold, select a better peer (if any) and - // GRAFT - if median < thresholds.opportunistic_graft_threshold { - let peer_list = get_random_peers( - &self.connected_peers, - topic_hash, - self.config.opportunistic_graft_peers(), - |peer_id| { - !peers.contains(peer_id) - && !explicit_peers.contains(peer_id) - && !backoffs.is_backoff_with_slack(topic_hash, peer_id) - && *scores.get(peer_id).unwrap_or(&0.0) > median - }, - ); - for peer in &peer_list { - let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - // update the mesh - tracing::debug!( - topic=%topic_hash, - "Opportunistically graft in topic with peers {:?}", - peer_list - ); - if let Some(m) = self.metrics.as_mut() { - m.peers_included(topic_hash, Inclusion::Random, peer_list.len()) - } - peers.extend(peer_list); - } - } - } - // Register the final count of peers in the mesh - if let Some(m) = self.metrics.as_mut() { - m.set_mesh_peers(topic_hash, peers.len()) - } - } - - // remove expired fanout topics - { - let fanout = &mut self.fanout; // help the borrow checker - let fanout_ttl = self.config.fanout_ttl(); - self.fanout_last_pub.retain(|topic_hash, last_pub_time| { - if *last_pub_time + fanout_ttl < Instant::now() { - tracing::debug!( - topic=%topic_hash, - "HEARTBEAT: Fanout topic removed due to timeout" - ); - fanout.remove(topic_hash); - return false; - } - true - }); - } - - // maintain fanout - // check if our peers are still a part of the topic - for (topic_hash, peers) in self.fanout.iter_mut() { - let mut to_remove_peers = Vec::new(); - let publish_threshold = match &self.peer_score { - Some((_, thresholds, _)) => thresholds.publish_threshold, - _ => 0.0, - }; - for peer_id in peers.iter() { - // is the peer still subscribed to the topic? - let peer_score = *scores.get(peer_id).unwrap_or(&0.0); - match self.connected_peers.get(peer_id) { - Some(peer) => { - if !peer.topics.contains(topic_hash) || peer_score < publish_threshold { - tracing::debug!( - topic=%topic_hash, - "HEARTBEAT: Peer removed from fanout for topic" - ); - to_remove_peers.push(*peer_id); - } - } - None => { - // remove if the peer has disconnected - to_remove_peers.push(*peer_id); - } - } - } - for to_remove in to_remove_peers { - peers.remove(&to_remove); - } - - // not enough peers - if peers.len() < self.config.mesh_n() { - tracing::debug!( - "HEARTBEAT: Fanout low. Contains: {:?} needs: {:?}", - peers.len(), - self.config.mesh_n() - ); - let needed_peers = self.config.mesh_n() - peers.len(); - let explicit_peers = &self.explicit_peers; - let new_peers = - get_random_peers(&self.connected_peers, topic_hash, needed_peers, |peer_id| { - !peers.contains(peer_id) - && !explicit_peers.contains(peer_id) - && *scores.get(peer_id).unwrap_or(&0.0) < publish_threshold - }); - peers.extend(new_peers); - } - } - - if self.peer_score.is_some() { - tracing::trace!("Mesh message deliveries: {:?}", { - self.mesh - .iter() - .map(|(t, peers)| { - ( - t.clone(), - peers - .iter() - .map(|p| { - ( - *p, - self.peer_score - .as_ref() - .expect("peer_score.is_some()") - .0 - .mesh_message_deliveries(p, t) - .unwrap_or(0.0), - ) - }) - .collect::>(), - ) - }) - .collect::>>() - }) - } - - self.emit_gossip(); - - // send graft/prunes - if !to_graft.is_empty() | !to_prune.is_empty() { - self.send_graft_prune(to_graft, to_prune, no_px); - } - - // shift the memcache - self.mcache.shift(); - - // Report expired messages - for (peer_id, failed_messages) in self.failed_messages.drain() { - tracing::debug!("Peer couldn't consume messages: {:?}", failed_messages); - self.events - .push_back(ToSwarm::GenerateEvent(Event::SlowPeer { - peer_id, - failed_messages, - })); - } - self.failed_messages.shrink_to_fit(); - - // Flush stale IDONTWANTs. - for peer in self.connected_peers.values_mut() { - while let Some((_front, instant)) = peer.dont_send_received.front() { - if (*instant + IDONTWANT_TIMEOUT) >= Instant::now() { - break; - } else { - peer.dont_send_received.pop_front(); - } - } - // If metrics are not enabled, this queue would be empty. - while let Some((_front, instant)) = peer.dont_send_sent.front() { - if (*instant + IDONTWANT_TIMEOUT) >= Instant::now() { - break; - } else { - peer.dont_send_sent.pop_front(); - } - } - } - - tracing::debug!("Completed Heartbeat"); - if let Some(metrics) = self.metrics.as_mut() { - let duration = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX); - metrics.observe_heartbeat_duration(duration); - } - } - - /// Emits gossip - Send IHAVE messages to a random set of gossip peers. This is applied to mesh - /// and fanout peers - fn emit_gossip(&mut self) { - let mut rng = thread_rng(); - for (topic_hash, peers) in self.mesh.iter().chain(self.fanout.iter()) { - let mut message_ids = self.mcache.get_gossip_message_ids(topic_hash); - if message_ids.is_empty() { - continue; - } - - // if we are emitting more than GossipSubMaxIHaveLength message_ids, truncate the list - if message_ids.len() > self.config.max_ihave_length() { - // we do the truncation (with shuffling) per peer below - tracing::debug!( - "too many messages for gossip; will truncate IHAVE list ({} messages)", - message_ids.len() - ); - } else { - // shuffle to emit in random order - message_ids.shuffle(&mut rng); - } - - // dynamic number of peers to gossip based on `gossip_factor` with minimum `gossip_lazy` - let n_map = |m| { - max( - self.config.gossip_lazy(), - (self.config.gossip_factor() * m as f64) as usize, - ) - }; - // get gossip_lazy random peers - let to_msg_peers = - get_random_peers_dynamic(&self.connected_peers, topic_hash, n_map, |peer| { - !peers.contains(peer) - && !self.explicit_peers.contains(peer) - && !self.score_below_threshold(peer, |ts| ts.gossip_threshold).0 - }); - - tracing::debug!("Gossiping IHAVE to {} peers", to_msg_peers.len()); - - for peer_id in to_msg_peers { - let mut peer_message_ids = message_ids.clone(); - - if peer_message_ids.len() > self.config.max_ihave_length() { - // We do this per peer so that we emit a different set for each peer. - // we have enough redundancy in the system that this will significantly increase - // the message coverage when we do truncate. - peer_message_ids.partial_shuffle(&mut rng, self.config.max_ihave_length()); - peer_message_ids.truncate(self.config.max_ihave_length()); - } - - // send an IHAVE message - if let Some(peer) = &mut self.connected_peers.get_mut(&peer_id) { - if peer - .sender - .ihave(IHave { - topic_hash: topic_hash.clone(), - message_ids: peer_message_ids, - }) - .is_err() - { - tracing::warn!(peer=%peer_id, "Send Queue full. Could not send IHAVE"); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(&peer_id); - } - // Increment failed message count - self.failed_messages - .entry(peer_id) - .or_default() - .non_priority += 1; - } - } else { - tracing::error!(peer = %peer_id, - "Could not send IHAVE, peer doesn't exist in connected peer list"); - } - } - } - } - - /// Handles multiple GRAFT/PRUNE messages and coalesces them into chunked gossip control - /// messages. - fn send_graft_prune( - &mut self, - to_graft: HashMap>, - mut to_prune: HashMap>, - no_px: HashSet, - ) { - // handle the grafts and overlapping prunes per peer - for (peer_id, topics) in to_graft.into_iter() { - for topic in &topics { - // inform scoring of graft - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.graft(&peer_id, topic.clone()); - } - - // inform the handler of the peer being added to the mesh - // If the peer did not previously exist in any mesh, inform the handler - peer_added_to_mesh( - peer_id, - vec![topic], - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - - // If there are prunes associated with the same peer add them. - // NOTE: In this case a peer has been added to a topic mesh, and removed from another. - // It therefore must be in at least one mesh and we do not need to inform the handler - // of its removal from another. - - // send the control messages - let mut sender = match self.connected_peers.get_mut(&peer_id) { - Some(connected_peer) => connected_peer.sender.clone(), - None => { - tracing::error!(peer_id = %peer_id, "Peer non-existent when sending graft/prune"); - return; - } - }; - - // The following prunes are not due to unsubscribing. - let prunes = to_prune - .remove(&peer_id) - .into_iter() - .flatten() - .map(|topic_hash| { - self.make_prune( - &topic_hash, - &peer_id, - self.config.do_px() && !no_px.contains(&peer_id), - false, - ) - }); - - for topic_hash in topics { - sender.graft(Graft { - topic_hash: topic_hash.clone(), - }); - } - - for prune in prunes { - sender.prune(prune); - } - } - - // handle the remaining prunes - // The following prunes are not due to unsubscribing. - for (peer_id, topics) in to_prune.iter() { - for topic_hash in topics { - let prune = self.make_prune( - topic_hash, - peer_id, - self.config.do_px() && !no_px.contains(peer_id), - false, - ); - if let Some(peer) = self.connected_peers.get_mut(peer_id) { - peer.sender.prune(prune); - } else { - tracing::error!(peer = %peer_id, - "Could not send PRUNE, peer doesn't exist in connected peer list"); - } - - // inform the handler - peer_removed_from_mesh( - *peer_id, - topic_hash, - &self.mesh, - &mut self.events, - &self.connected_peers, - ); - } - } - } - - /// Helper function which sends an IDONTWANT message to mesh\[topic\] peers. - fn send_idontwant( - &mut self, - message: &RawMessage, - msg_id: &MessageId, - propagation_source: Option<&PeerId>, - ) { - let Some(mesh_peers) = self.mesh.get(&message.topic) else { - return; - }; - - let iwant_peers = self.gossip_promises.peers_for_message(msg_id); - - let recipient_peers = mesh_peers - .iter() - .chain(iwant_peers.iter()) - .filter(|&peer_id| { - Some(peer_id) != propagation_source && Some(peer_id) != message.source.as_ref() - }); - - for peer_id in recipient_peers { - let Some(peer) = self.connected_peers.get_mut(peer_id) else { - // It can be the case that promises to disconnected peers appear here. In this case - // we simply ignore the peer-id. - continue; - }; - - // Only gossipsub 1.2 peers support IDONTWANT. - if peer.kind != PeerKind::Gossipsubv1_2 { - continue; - } - - if peer - .sender - .idontwant(IDontWant { - message_ids: vec![msg_id.clone()], - }) - .is_err() - { - tracing::warn!(peer=%peer_id, "Send Queue full. Could not send IDONTWANT"); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - // Increment failed message count - self.failed_messages - .entry(*peer_id) - .or_default() - .non_priority += 1; - return; - } - // IDONTWANT sent successfully. - if let Some(metrics) = self.metrics.as_mut() { - peer.dont_send_sent.insert(msg_id.clone(), Instant::now()); - // Don't exceed capacity. - if peer.dont_send_sent.len() > IDONTWANT_CAP { - peer.dont_send_sent.pop_front(); - } - metrics.register_idontwant_messages_sent_per_topic(&message.topic); - } - } - } - - /// Helper function which forwards a message to mesh\[topic\] peers. - /// - /// Returns true if at least one peer was messaged. - fn forward_msg( - &mut self, - msg_id: &MessageId, - message: RawMessage, - propagation_source: Option<&PeerId>, - originating_peers: HashSet, - ) -> Result { - // message is fully validated inform peer_score - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(peer) = propagation_source { - peer_score.deliver_message(peer, msg_id, &message.topic); - } - } - - tracing::debug!(message=%msg_id, "Forwarding message"); - let mut recipient_peers = HashSet::new(); - - // Populate the recipient peers mapping - - // Add explicit peers - for peer_id in &self.explicit_peers { - if let Some(peer) = self.connected_peers.get(peer_id) { - if Some(peer_id) != propagation_source - && !originating_peers.contains(peer_id) - && Some(peer_id) != message.source.as_ref() - && peer.topics.contains(&message.topic) - { - recipient_peers.insert(*peer_id); - } - } - } - - // add mesh peers - let topic = &message.topic; - // mesh - if let Some(mesh_peers) = self.mesh.get(topic) { - for peer_id in mesh_peers { - if Some(peer_id) != propagation_source - && !originating_peers.contains(peer_id) - && Some(peer_id) != message.source.as_ref() - { - recipient_peers.insert(*peer_id); - } - } - } - - // forward the message to peers - if !recipient_peers.is_empty() { - for peer_id in recipient_peers.iter() { - if let Some(peer) = self.connected_peers.get_mut(peer_id) { - if peer.dont_send_received.get(msg_id).is_some() { - tracing::debug!(%peer_id, message=%msg_id, "Peer doesn't want message"); - continue; - } - - tracing::debug!(%peer_id, message=%msg_id, "Sending message to peer"); - if peer - .sender - .forward( - message.clone(), - self.config.forward_queue_duration(), - self.metrics.as_mut(), - ) - .is_err() - { - // Downscore the peer - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(peer_id); - } - // Increment the failed message count - self.failed_messages - .entry(*peer_id) - .or_default() - .non_priority += 1; - } - } else { - tracing::error!(peer = %peer_id, - "Could not FORWARD, peer doesn't exist in connected peer list"); - } - } - tracing::debug!("Completed forwarding message"); - Ok(true) - } else { - Ok(false) - } - } - - /// Constructs a [`RawMessage`] performing message signing if required. - pub(crate) fn build_raw_message( - &mut self, - topic: TopicHash, - data: Vec, - ) -> Result { - match &mut self.publish_config { - PublishConfig::Signing { - ref keypair, - author, - inline_key, - last_seq_no, - } => { - let sequence_number = last_seq_no.next(); - - let signature = { - let message = proto::Message { - from: Some(author.to_bytes()), - data: Some(data.clone()), - seqno: Some(sequence_number.to_be_bytes().to_vec()), - topic: topic.clone().into_string(), - signature: None, - key: None, - }; - - let mut buf = Vec::with_capacity(message.get_size()); - let mut writer = Writer::new(&mut buf); - - message - .write_message(&mut writer) - .expect("Encoding to succeed"); - - // the signature is over the bytes "libp2p-pubsub:" - let mut signature_bytes = SIGNING_PREFIX.to_vec(); - signature_bytes.extend_from_slice(&buf); - Some(keypair.sign(&signature_bytes)?) - }; - - Ok(RawMessage { - source: Some(*author), - data, - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: Some(sequence_number), - topic, - signature, - key: inline_key.clone(), - validated: true, // all published messages are valid - }) - } - PublishConfig::Author(peer_id) => { - Ok(RawMessage { - source: Some(*peer_id), - data, - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: Some(rand::random()), - topic, - signature: None, - key: None, - validated: true, // all published messages are valid - }) - } - PublishConfig::RandomAuthor => { - Ok(RawMessage { - source: Some(PeerId::random()), - data, - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: Some(rand::random()), - topic, - signature: None, - key: None, - validated: true, // all published messages are valid - }) - } - PublishConfig::Anonymous => { - Ok(RawMessage { - source: None, - data, - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: None, - topic, - signature: None, - key: None, - validated: true, // all published messages are valid - }) - } - } - } - - fn on_connection_established( - &mut self, - ConnectionEstablished { - peer_id, - endpoint, - other_established, - .. - }: ConnectionEstablished, - ) { - // Diverging from the go implementation we only want to consider a peer as outbound peer - // if its first connection is outbound. - - if endpoint.is_dialer() && other_established == 0 && !self.px_peers.contains(&peer_id) { - // The first connection is outbound and it is not a peer from peer exchange => mark - // it as outbound peer - self.outbound_peers.insert(peer_id); - } - - // Add the IP to the peer scoring system - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(ip) = get_ip_addr(endpoint.get_remote_address()) { - peer_score.add_ip(&peer_id, ip); - } else { - tracing::trace!( - peer=%peer_id, - "Couldn't extract ip from endpoint of peer with endpoint {:?}", - endpoint - ) - } - } - - if other_established > 0 { - return; // Not our first connection to this peer, hence nothing to do. - } - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.add_peer(peer_id); - } - - // Ignore connections from blacklisted peers. - if self.blacklisted_peers.contains(&peer_id) { - tracing::debug!(peer=%peer_id, "Ignoring connection from blacklisted peer"); - return; - } - - tracing::debug!(peer=%peer_id, "New peer connected"); - // We need to send our subscriptions to the newly-connected node. - if let Some(peer) = self.connected_peers.get_mut(&peer_id) { - for topic_hash in self.mesh.clone().into_keys() { - peer.sender.subscribe(topic_hash); - } - } else { - tracing::error!(peer = %peer_id, - "Could not send SUBSCRIBE, peer doesn't exist in connected peer list"); - } - } - - fn on_connection_closed( - &mut self, - ConnectionClosed { - peer_id, - connection_id, - endpoint, - remaining_established, - .. - }: ConnectionClosed, - ) { - // Remove IP from peer scoring system - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(ip) = get_ip_addr(endpoint.get_remote_address()) { - peer_score.remove_ip(&peer_id, &ip); - } else { - tracing::trace!( - peer=%peer_id, - "Couldn't extract ip from endpoint of peer with endpoint {:?}", - endpoint - ) - } - } - - if remaining_established != 0 { - // Remove the connection from the list - if let Some(peer) = self.connected_peers.get_mut(&peer_id) { - let index = peer - .connections - .iter() - .position(|v| v == &connection_id) - .expect("Previously established connection to peer must be present"); - peer.connections.remove(index); - - // If there are more connections and this peer is in a mesh, inform the first connection - // handler. - if !peer.connections.is_empty() { - for topic in &peer.topics { - if let Some(mesh_peers) = self.mesh.get(topic) { - if mesh_peers.contains(&peer_id) { - self.events.push_back(ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::JoinedMesh, - handler: NotifyHandler::One(peer.connections[0]), - }); - break; - } - } - } - } - } - } else { - // remove from mesh, topic_peers, peer_topic and the fanout - tracing::debug!(peer=%peer_id, "Peer disconnected"); - - let Some(connected_peer) = self.connected_peers.get(&peer_id) else { - tracing::error!(peer_id = %peer_id, "Peer non-existent when handling disconnection"); - return; - }; - - // remove peer from all mappings - for topic in &connected_peer.topics { - // check the mesh for the topic - if let Some(mesh_peers) = self.mesh.get_mut(topic) { - // check if the peer is in the mesh and remove it - if mesh_peers.remove(&peer_id) { - if let Some(m) = self.metrics.as_mut() { - m.peers_removed(topic, Churn::Dc, 1); - m.set_mesh_peers(topic, mesh_peers.len()); - } - }; - } - - if let Some(m) = self.metrics.as_mut() { - m.dec_topic_peers(topic); - } - - // remove from fanout - self.fanout - .get_mut(topic) - .map(|peers| peers.remove(&peer_id)); - } - - // Forget px and outbound status for this peer - self.px_peers.remove(&peer_id); - self.outbound_peers.remove(&peer_id); - - // If metrics are enabled, register the disconnection of a peer based on its protocol. - if let Some(metrics) = self.metrics.as_mut() { - metrics.peer_protocol_disconnected(connected_peer.kind.clone()); - } - - self.connected_peers.remove(&peer_id); - - if let Some((peer_score, ..)) = &mut self.peer_score { - peer_score.remove_peer(&peer_id); - } - } - } - - fn on_address_change( - &mut self, - AddressChange { - peer_id, - old: endpoint_old, - new: endpoint_new, - .. - }: AddressChange, - ) { - // Exchange IP in peer scoring system - if let Some((peer_score, ..)) = &mut self.peer_score { - if let Some(ip) = get_ip_addr(endpoint_old.get_remote_address()) { - peer_score.remove_ip(&peer_id, &ip); - } else { - tracing::trace!( - peer=%&peer_id, - "Couldn't extract ip from endpoint of peer with endpoint {:?}", - endpoint_old - ) - } - if let Some(ip) = get_ip_addr(endpoint_new.get_remote_address()) { - peer_score.add_ip(&peer_id, ip); - } else { - tracing::trace!( - peer=%peer_id, - "Couldn't extract ip from endpoint of peer with endpoint {:?}", - endpoint_new - ) - } - } - } -} - -fn get_ip_addr(addr: &Multiaddr) -> Option { - addr.iter().find_map(|p| match p { - Ip4(addr) => Some(IpAddr::V4(addr)), - Ip6(addr) => Some(IpAddr::V6(addr)), - _ => None, - }) -} - -impl NetworkBehaviour for Behaviour -where - C: Send + 'static + DataTransform, - F: Send + 'static + TopicSubscriptionFilter, -{ - type ConnectionHandler = Handler; - type ToSwarm = Event; - - fn handle_established_inbound_connection( - &mut self, - connection_id: ConnectionId, - peer_id: PeerId, - _: &Multiaddr, - _: &Multiaddr, - ) -> Result, ConnectionDenied> { - // By default we assume a peer is only a floodsub peer. - // - // The protocol negotiation occurs once a message is sent/received. Once this happens we - // update the type of peer that this is in order to determine which kind of routing should - // occur. - let connected_peer = self - .connected_peers - .entry(peer_id) - .or_insert(PeerConnections { - kind: PeerKind::Floodsub, - connections: vec![], - sender: RpcSender::new(self.config.connection_handler_queue_len()), - topics: Default::default(), - dont_send_received: LinkedHashMap::new(), - dont_send_sent: LinkedHashMap::new(), - }); - // Add the new connection - connected_peer.connections.push(connection_id); - - Ok(Handler::new( - self.config.protocol_config(), - connected_peer.sender.new_receiver(), - )) - } - - fn handle_established_outbound_connection( - &mut self, - connection_id: ConnectionId, - peer_id: PeerId, - _: &Multiaddr, - _: Endpoint, - _: PortUse, - ) -> Result, ConnectionDenied> { - // By default we assume a peer is only a floodsub peer. - // - // The protocol negotiation occurs once a message is sent/received. Once this happens we - // update the type of peer that this is in order to determine which kind of routing should - // occur. - let connected_peer = self - .connected_peers - .entry(peer_id) - .or_insert(PeerConnections { - kind: PeerKind::Floodsub, - connections: vec![], - sender: RpcSender::new(self.config.connection_handler_queue_len()), - topics: Default::default(), - dont_send_received: LinkedHashMap::new(), - dont_send_sent: LinkedHashMap::new(), - }); - // Add the new connection - connected_peer.connections.push(connection_id); - - Ok(Handler::new( - self.config.protocol_config(), - connected_peer.sender.new_receiver(), - )) - } - - fn on_connection_handler_event( - &mut self, - propagation_source: PeerId, - _connection_id: ConnectionId, - handler_event: THandlerOutEvent, - ) { - match handler_event { - HandlerEvent::PeerKind(kind) => { - // We have identified the protocol this peer is using - - if let Some(metrics) = self.metrics.as_mut() { - metrics.peer_protocol_connected(kind.clone()); - } - - if let PeerKind::NotSupported = kind { - tracing::debug!( - peer=%propagation_source, - "Peer does not support gossipsub protocols" - ); - self.events - .push_back(ToSwarm::GenerateEvent(Event::GossipsubNotSupported { - peer_id: propagation_source, - })); - } else if let Some(conn) = self.connected_peers.get_mut(&propagation_source) { - // Only change the value if the old value is Floodsub (the default set in - // `NetworkBehaviour::on_event` with FromSwarm::ConnectionEstablished). - // All other PeerKind changes are ignored. - tracing::debug!( - peer=%propagation_source, - peer_type=%kind, - "New peer type found for peer" - ); - if let PeerKind::Floodsub = conn.kind { - conn.kind = kind; - } - } - } - HandlerEvent::MessageDropped(rpc) => { - // Account for this in the scoring logic - if let Some((peer_score, _, _)) = &mut self.peer_score { - peer_score.failed_message_slow_peer(&propagation_source); - } - - // Keep track of expired messages for the application layer. - match rpc { - RpcOut::Publish { .. } => { - self.failed_messages - .entry(propagation_source) - .or_default() - .publish += 1; - } - RpcOut::Forward { .. } => { - self.failed_messages - .entry(propagation_source) - .or_default() - .forward += 1; - } - _ => {} // - } - - // Record metrics on the failure. - if let Some(metrics) = self.metrics.as_mut() { - match rpc { - RpcOut::Publish { message, .. } => { - metrics.publish_msg_dropped(&message.topic); - } - RpcOut::Forward { message, .. } => { - metrics.forward_msg_dropped(&message.topic); - } - _ => {} - } - } - } - HandlerEvent::Message { - rpc, - invalid_messages, - } => { - // Handle the gossipsub RPC - - // Handle subscriptions - // Update connected peers topics - if !rpc.subscriptions.is_empty() { - self.handle_received_subscriptions(&rpc.subscriptions, &propagation_source); - } - - // Check if peer is graylisted in which case we ignore the event - if let (true, _) = - self.score_below_threshold(&propagation_source, |pst| pst.graylist_threshold) - { - tracing::debug!(peer=%propagation_source, "RPC Dropped from greylisted peer"); - return; - } - - // Handle any invalid messages from this peer - if self.peer_score.is_some() { - for (raw_message, validation_error) in invalid_messages { - self.handle_invalid_message( - &propagation_source, - &raw_message, - RejectReason::ValidationError(validation_error), - ) - } - } else { - // log the invalid messages - for (message, validation_error) in invalid_messages { - tracing::warn!( - peer=%propagation_source, - source=?message.source, - "Invalid message from peer. Reason: {:?}", - validation_error, - ); - } - } - - // Handle messages - for (count, raw_message) in rpc.messages.into_iter().enumerate() { - // Only process the amount of messages the configuration allows. - if self.config.max_messages_per_rpc().is_some() - && Some(count) >= self.config.max_messages_per_rpc() - { - tracing::warn!("Received more messages than permitted. Ignoring further messages. Processed: {}", count); - break; - } - self.handle_received_message(raw_message, &propagation_source); - } - - // Handle control messages - // group some control messages, this minimises SendEvents (code is simplified to handle each event at a time however) - let mut ihave_msgs = vec![]; - let mut graft_msgs = vec![]; - let mut prune_msgs = vec![]; - for control_msg in rpc.control_msgs { - match control_msg { - ControlAction::IHave(IHave { - topic_hash, - message_ids, - }) => { - ihave_msgs.push((topic_hash, message_ids)); - } - ControlAction::IWant(IWant { message_ids }) => { - self.handle_iwant(&propagation_source, message_ids) - } - ControlAction::Graft(Graft { topic_hash }) => graft_msgs.push(topic_hash), - ControlAction::Prune(Prune { - topic_hash, - peers, - backoff, - }) => prune_msgs.push((topic_hash, peers, backoff)), - ControlAction::IDontWant(IDontWant { message_ids }) => { - let Some(peer) = self.connected_peers.get_mut(&propagation_source) - else { - tracing::error!(peer = %propagation_source, - "Could not handle IDONTWANT, peer doesn't exist in connected peer list"); - continue; - }; - if let Some(metrics) = self.metrics.as_mut() { - metrics.register_idontwant(message_ids.len()); - let idontwant_size = message_ids.iter().map(|id| id.0.len()).sum(); - metrics.register_idontwant_bytes(idontwant_size); - } - for message_id in message_ids { - peer.dont_send_received.insert(message_id, Instant::now()); - // Don't exceed capacity. - if peer.dont_send_received.len() > IDONTWANT_CAP { - peer.dont_send_received.pop_front(); - } - } - } - } - } - if !ihave_msgs.is_empty() { - self.handle_ihave(&propagation_source, ihave_msgs); - } - if !graft_msgs.is_empty() { - self.handle_graft(&propagation_source, graft_msgs); - } - if !prune_msgs.is_empty() { - self.handle_prune(&propagation_source, prune_msgs); - } - } - } - } - - #[tracing::instrument(level = "trace", name = "NetworkBehaviour::poll", skip(self, cx))] - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll>> { - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - // update scores - if let Some((peer_score, _, delay)) = &mut self.peer_score { - if delay.poll_unpin(cx).is_ready() { - peer_score.refresh_scores(); - delay.reset(peer_score.params.decay_interval); - } - } - - if self.heartbeat.poll_unpin(cx).is_ready() { - self.heartbeat(); - self.heartbeat.reset(self.config.heartbeat_interval()); - } - - Poll::Pending - } - - fn on_swarm_event(&mut self, event: FromSwarm) { - match event { - FromSwarm::ConnectionEstablished(connection_established) => { - self.on_connection_established(connection_established) - } - FromSwarm::ConnectionClosed(connection_closed) => { - self.on_connection_closed(connection_closed) - } - FromSwarm::AddressChange(address_change) => self.on_address_change(address_change), - _ => {} - } - } -} - -/// This is called when peers are added to any mesh. It checks if the peer existed -/// in any other mesh. If this is the first mesh they have joined, it queues a message to notify -/// the appropriate connection handler to maintain a connection. -fn peer_added_to_mesh( - peer_id: PeerId, - new_topics: Vec<&TopicHash>, - mesh: &HashMap>, - events: &mut VecDeque>, - connections: &HashMap, -) { - // Ensure there is an active connection - let connection_id = match connections.get(&peer_id) { - Some(p) => p - .connections - .first() - .expect("There should be at least one connection to a peer."), - None => { - tracing::error!(peer_id=%peer_id, "Peer not existent when added to the mesh"); - return; - } - }; - - if let Some(peer) = connections.get(&peer_id) { - for topic in &peer.topics { - if !new_topics.contains(&topic) { - if let Some(mesh_peers) = mesh.get(topic) { - if mesh_peers.contains(&peer_id) { - // the peer is already in a mesh for another topic - return; - } - } - } - } - } - // This is the first mesh the peer has joined, inform the handler - events.push_back(ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::JoinedMesh, - handler: NotifyHandler::One(*connection_id), - }); -} - -/// This is called when peers are removed from a mesh. It checks if the peer exists -/// in any other mesh. If this is the last mesh they have joined, we return true, in order to -/// notify the handler to no longer maintain a connection. -fn peer_removed_from_mesh( - peer_id: PeerId, - old_topic: &TopicHash, - mesh: &HashMap>, - events: &mut VecDeque>, - connections: &HashMap, -) { - // Ensure there is an active connection - let connection_id = match connections.get(&peer_id) { - Some(p) => p - .connections - .first() - .expect("There should be at least one connection to a peer."), - None => { - tracing::error!(peer_id=%peer_id, "Peer not existent when removed from mesh"); - return; - } - }; - - if let Some(peer) = connections.get(&peer_id) { - for topic in &peer.topics { - if topic != old_topic { - if let Some(mesh_peers) = mesh.get(topic) { - if mesh_peers.contains(&peer_id) { - // the peer exists in another mesh still - return; - } - } - } - } - } - // The peer is not in any other mesh, inform the handler - events.push_back(ToSwarm::NotifyHandler { - peer_id, - event: HandlerIn::LeftMesh, - handler: NotifyHandler::One(*connection_id), - }); -} - -/// Helper function to get a subset of random gossipsub peers for a `topic_hash` -/// filtered by the function `f`. The number of peers to get equals the output of `n_map` -/// that gets as input the number of filtered peers. -fn get_random_peers_dynamic( - connected_peers: &HashMap, - topic_hash: &TopicHash, - // maps the number of total peers to the number of selected peers - n_map: impl Fn(usize) -> usize, - mut f: impl FnMut(&PeerId) -> bool, -) -> BTreeSet { - let mut gossip_peers = connected_peers - .iter() - .filter(|(_, p)| p.topics.contains(topic_hash)) - .filter(|(peer_id, _)| f(peer_id)) - .filter(|(_, p)| p.kind.is_gossipsub()) - .map(|(peer_id, _)| *peer_id) - .collect::>(); - - // if we have less than needed, return them - let n = n_map(gossip_peers.len()); - if gossip_peers.len() <= n { - tracing::debug!("RANDOM PEERS: Got {:?} peers", gossip_peers.len()); - return gossip_peers.into_iter().collect(); - } - - // we have more peers than needed, shuffle them and return n of them - let mut rng = thread_rng(); - gossip_peers.partial_shuffle(&mut rng, n); - - tracing::debug!("RANDOM PEERS: Got {:?} peers", n); - - gossip_peers.into_iter().take(n).collect() -} - -/// Helper function to get a set of `n` random gossipsub peers for a `topic_hash` -/// filtered by the function `f`. -fn get_random_peers( - connected_peers: &HashMap, - topic_hash: &TopicHash, - n: usize, - f: impl FnMut(&PeerId) -> bool, -) -> BTreeSet { - get_random_peers_dynamic(connected_peers, topic_hash, |_| n, f) -} - -/// Validates the combination of signing, privacy and message validation to ensure the -/// configuration will not reject published messages. -fn validate_config( - authenticity: &MessageAuthenticity, - validation_mode: &ValidationMode, -) -> Result<(), &'static str> { - match validation_mode { - ValidationMode::Anonymous => { - if authenticity.is_signing() { - return Err("Cannot enable message signing with an Anonymous validation mode. Consider changing either the ValidationMode or MessageAuthenticity"); - } - - if !authenticity.is_anonymous() { - return Err("Published messages contain an author but incoming messages with an author will be rejected. Consider adjusting the validation or privacy settings in the config"); - } - } - ValidationMode::Strict => { - if !authenticity.is_signing() { - return Err( - "Messages will be - published unsigned and incoming unsigned messages will be rejected. Consider adjusting - the validation or privacy settings in the config" - ); - } - } - _ => {} - } - Ok(()) -} - -impl fmt::Debug for Behaviour { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Behaviour") - .field("config", &self.config) - .field("events", &self.events.len()) - .field("publish_config", &self.publish_config) - .field("mesh", &self.mesh) - .field("fanout", &self.fanout) - .field("fanout_last_pub", &self.fanout_last_pub) - .field("mcache", &self.mcache) - .field("heartbeat", &self.heartbeat) - .finish() - } -} - -impl fmt::Debug for PublishConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PublishConfig::Signing { author, .. } => { - f.write_fmt(format_args!("PublishConfig::Signing({author})")) - } - PublishConfig::Author(author) => { - f.write_fmt(format_args!("PublishConfig::Author({author})")) - } - PublishConfig::RandomAuthor => f.write_fmt(format_args!("PublishConfig::RandomAuthor")), - PublishConfig::Anonymous => f.write_fmt(format_args!("PublishConfig::Anonymous")), - } - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/behaviour/tests.rs b/beacon_node/lighthouse_network/gossipsub/src/behaviour/tests.rs deleted file mode 100644 index 90b8fe43fb..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/behaviour/tests.rs +++ /dev/null @@ -1,5486 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -// Collection of tests for the gossipsub network behaviour - -use super::*; -use crate::subscription_filter::WhitelistSubscriptionFilter; -use crate::types::RpcReceiver; -use crate::{config::ConfigBuilder, types::Rpc, IdentTopic as Topic}; -use byteorder::{BigEndian, ByteOrder}; -use futures::StreamExt; -use libp2p::core::ConnectedPoint; -use rand::Rng; -use std::net::Ipv4Addr; -use std::thread::sleep; - -#[derive(Default, Debug)] -struct InjectNodes { - peer_no: usize, - topics: Vec, - to_subscribe: bool, - gs_config: Config, - explicit: usize, - outbound: usize, - scoring: Option<(PeerScoreParams, PeerScoreThresholds)>, - data_transform: D, - subscription_filter: F, - peer_kind: Option, -} - -impl InjectNodes -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - #[allow(clippy::type_complexity)] - pub(crate) fn create_network( - self, - ) -> ( - Behaviour, - Vec, - HashMap, - Vec, - ) { - let keypair = libp2p::identity::Keypair::generate_ed25519(); - // create a gossipsub struct - let mut gs: Behaviour = Behaviour::new_with_subscription_filter_and_transform( - MessageAuthenticity::Signed(keypair), - self.gs_config, - None, - self.subscription_filter, - self.data_transform, - ) - .unwrap(); - - if let Some((scoring_params, scoring_thresholds)) = self.scoring { - gs.with_peer_score(scoring_params, scoring_thresholds) - .unwrap(); - } - - let mut topic_hashes = vec![]; - - // subscribe to the topics - for t in self.topics { - let topic = Topic::new(t); - gs.subscribe(&topic).unwrap(); - topic_hashes.push(topic.hash().clone()); - } - - // build and connect peer_no random peers - let mut peers = vec![]; - let mut receivers = HashMap::new(); - - let empty = vec![]; - for i in 0..self.peer_no { - let (peer, receiver) = add_peer_with_addr_and_kind( - &mut gs, - if self.to_subscribe { - &topic_hashes - } else { - &empty - }, - i < self.outbound, - i < self.explicit, - Multiaddr::empty(), - self.peer_kind.clone().or(Some(PeerKind::Gossipsubv1_1)), - ); - peers.push(peer); - receivers.insert(peer, receiver); - } - - (gs, peers, receivers, topic_hashes) - } - - fn peer_no(mut self, peer_no: usize) -> Self { - self.peer_no = peer_no; - self - } - - fn topics(mut self, topics: Vec) -> Self { - self.topics = topics; - self - } - - #[allow(clippy::wrong_self_convention)] - fn to_subscribe(mut self, to_subscribe: bool) -> Self { - self.to_subscribe = to_subscribe; - self - } - - fn gs_config(mut self, gs_config: Config) -> Self { - self.gs_config = gs_config; - self - } - - fn explicit(mut self, explicit: usize) -> Self { - self.explicit = explicit; - self - } - - fn outbound(mut self, outbound: usize) -> Self { - self.outbound = outbound; - self - } - - fn scoring(mut self, scoring: Option<(PeerScoreParams, PeerScoreThresholds)>) -> Self { - self.scoring = scoring; - self - } - - fn subscription_filter(mut self, subscription_filter: F) -> Self { - self.subscription_filter = subscription_filter; - self - } - - fn peer_kind(mut self, peer_kind: PeerKind) -> Self { - self.peer_kind = Some(peer_kind); - self - } -} - -fn inject_nodes() -> InjectNodes -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - InjectNodes::default() -} - -fn inject_nodes1() -> InjectNodes { - InjectNodes::::default() -} - -// helper functions for testing - -fn add_peer( - gs: &mut Behaviour, - topic_hashes: &[TopicHash], - outbound: bool, - explicit: bool, -) -> (PeerId, RpcReceiver) -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - add_peer_with_addr(gs, topic_hashes, outbound, explicit, Multiaddr::empty()) -} - -fn add_peer_with_addr( - gs: &mut Behaviour, - topic_hashes: &[TopicHash], - outbound: bool, - explicit: bool, - address: Multiaddr, -) -> (PeerId, RpcReceiver) -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - add_peer_with_addr_and_kind( - gs, - topic_hashes, - outbound, - explicit, - address, - Some(PeerKind::Gossipsubv1_1), - ) -} - -fn add_peer_with_addr_and_kind( - gs: &mut Behaviour, - topic_hashes: &[TopicHash], - outbound: bool, - explicit: bool, - address: Multiaddr, - kind: Option, -) -> (PeerId, RpcReceiver) -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - let peer = PeerId::random(); - let endpoint = if outbound { - ConnectedPoint::Dialer { - address, - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - } - } else { - ConnectedPoint::Listener { - local_addr: Multiaddr::empty(), - send_back_addr: address, - } - }; - - let sender = RpcSender::new(gs.config.connection_handler_queue_len()); - let receiver = sender.new_receiver(); - let connection_id = ConnectionId::new_unchecked(0); - gs.connected_peers.insert( - peer, - PeerConnections { - kind: kind.clone().unwrap_or(PeerKind::Floodsub), - connections: vec![connection_id], - topics: Default::default(), - dont_send_received: LinkedHashMap::new(), - dont_send_sent: LinkedHashMap::new(), - sender, - }, - ); - - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: peer, - connection_id, - endpoint: &endpoint, - failed_addresses: &[], - other_established: 0, // first connection - })); - if let Some(kind) = kind { - gs.on_connection_handler_event( - peer, - ConnectionId::new_unchecked(0), - HandlerEvent::PeerKind(kind), - ); - } - if explicit { - gs.add_explicit_peer(&peer); - } - if !topic_hashes.is_empty() { - gs.handle_received_subscriptions( - &topic_hashes - .iter() - .cloned() - .map(|t| Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: t, - }) - .collect::>(), - &peer, - ); - } - (peer, receiver) -} - -fn disconnect_peer(gs: &mut Behaviour, peer_id: &PeerId) -where - D: DataTransform + Default + Clone + Send + 'static, - F: TopicSubscriptionFilter + Clone + Default + Send + 'static, -{ - if let Some(peer_connections) = gs.connected_peers.get(peer_id) { - let fake_endpoint = ConnectedPoint::Dialer { - address: Multiaddr::empty(), - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }; // this is not relevant - // peer_connections.connections should never be empty. - - let mut active_connections = peer_connections.connections.len(); - for connection_id in peer_connections.connections.clone() { - active_connections = active_connections.checked_sub(1).unwrap(); - - gs.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { - peer_id: *peer_id, - connection_id, - endpoint: &fake_endpoint, - remaining_established: active_connections, - cause: None, - })); - } - } -} - -// Converts a protobuf message into a gossipsub message for reading the Gossipsub event queue. -fn proto_to_message(rpc: &proto::RPC) -> Rpc { - // Store valid messages. - let mut messages = Vec::with_capacity(rpc.publish.len()); - let rpc = rpc.clone(); - for message in rpc.publish.into_iter() { - messages.push(RawMessage { - source: message.from.map(|x| PeerId::from_bytes(&x).unwrap()), - data: message.data.unwrap_or_default(), - sequence_number: message.seqno.map(|x| BigEndian::read_u64(&x)), // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: message.signature, // don't inform the application - key: None, - validated: false, - }); - } - let mut control_msgs = Vec::new(); - if let Some(rpc_control) = rpc.control { - // Collect the gossipsub control messages - let ihave_msgs: Vec = rpc_control - .ihave - .into_iter() - .map(|ihave| { - ControlAction::IHave(IHave { - topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), - message_ids: ihave - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - let iwant_msgs: Vec = rpc_control - .iwant - .into_iter() - .map(|iwant| { - ControlAction::IWant(IWant { - message_ids: iwant - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - let graft_msgs: Vec = rpc_control - .graft - .into_iter() - .map(|graft| { - ControlAction::Graft(Graft { - topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), - }) - }) - .collect(); - - let mut prune_msgs = Vec::new(); - - for prune in rpc_control.prune { - // filter out invalid peers - let peers = prune - .peers - .into_iter() - .filter_map(|info| { - info.peer_id - .and_then(|id| PeerId::from_bytes(&id).ok()) - .map(|peer_id| - //TODO signedPeerRecord, see https://github.com/libp2p/specs/pull/217 - PeerInfo { - peer_id: Some(peer_id), - }) - }) - .collect::>(); - - let topic_hash = TopicHash::from_raw(prune.topic_id.unwrap_or_default()); - prune_msgs.push(ControlAction::Prune(Prune { - topic_hash, - peers, - backoff: prune.backoff, - })); - } - - control_msgs.extend(ihave_msgs); - control_msgs.extend(iwant_msgs); - control_msgs.extend(graft_msgs); - control_msgs.extend(prune_msgs); - } - - Rpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| Subscription { - action: if Some(true) == sub.subscribe { - SubscriptionAction::Subscribe - } else { - SubscriptionAction::Unsubscribe - }, - topic_hash: TopicHash::from_raw(sub.topic_id.unwrap_or_default()), - }) - .collect(), - control_msgs, - } -} - -#[test] -/// Test local node subscribing to a topic -fn test_subscribe() { - // The node should: - // - Create an empty vector in mesh[topic] - // - Send subscription request to all peers - // - run JOIN(topic) - - let subscribe_topic = vec![String::from("test_subscribe")]; - let (gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(subscribe_topic) - .to_subscribe(true) - .create_network(); - - assert!( - gs.mesh.contains_key(&topic_hashes[0]), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // collect all the subscriptions - let subscriptions = receivers - .into_values() - .fold(0, |mut collected_subscriptions, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Subscribe(_)) = priority.try_recv() { - collected_subscriptions += 1 - } - } - collected_subscriptions - }); - - // we sent a subscribe to all known peers - assert_eq!(subscriptions, 20); -} - -/// Test unsubscribe. -#[test] -fn test_unsubscribe() { - // Unsubscribe should: - // - Remove the mesh entry for topic - // - Send UNSUBSCRIBE to all known peers - // - Call Leave - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - // subscribe to topic_strings - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(topic_strings) - .to_subscribe(true) - .create_network(); - - for topic_hash in &topic_hashes { - assert!( - gs.connected_peers - .values() - .any(|p| p.topics.contains(topic_hash)), - "Topic_peers contain a topic entry" - ); - assert!( - gs.mesh.contains_key(topic_hash), - "mesh should contain a topic entry" - ); - } - - // unsubscribe from both topics - assert!( - gs.unsubscribe(&topics[0]).unwrap(), - "should be able to unsubscribe successfully from each topic", - ); - assert!( - gs.unsubscribe(&topics[1]).unwrap(), - "should be able to unsubscribe successfully from each topic", - ); - - // collect all the subscriptions - let subscriptions = receivers - .into_values() - .fold(0, |mut collected_subscriptions, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Subscribe(_)) = priority.try_recv() { - collected_subscriptions += 1 - } - } - collected_subscriptions - }); - - // we sent a unsubscribe to all known peers, for two topics - assert_eq!(subscriptions, 40); - - // check we clean up internal structures - for topic_hash in &topic_hashes { - assert!( - !gs.mesh.contains_key(topic_hash), - "All topics should have been removed from the mesh" - ); - } -} - -/// Test JOIN(topic) functionality. -#[test] -fn test_join() { - // The Join function should: - // - Remove peers from fanout[topic] - // - Add any fanout[topic] peers to the mesh (up to mesh_n) - // - Fill up to mesh_n peers from known gossipsub peers in the topic - // - Send GRAFT messages to all nodes added to the mesh - - // This test is not an isolated unit test, rather it uses higher level, - // subscribe/unsubscribe to perform the test. - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - let (mut gs, _, mut receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(topic_strings) - .to_subscribe(true) - .create_network(); - - // Flush previous GRAFT messages. - receivers = flush_events(&mut gs, receivers); - - // unsubscribe, then call join to invoke functionality - assert!( - gs.unsubscribe(&topics[0]).unwrap(), - "should be able to unsubscribe successfully" - ); - assert!( - gs.unsubscribe(&topics[1]).unwrap(), - "should be able to unsubscribe successfully" - ); - - // re-subscribe - there should be peers associated with the topic - assert!( - gs.subscribe(&topics[0]).unwrap(), - "should be able to subscribe successfully" - ); - - // should have added mesh_n nodes to the mesh - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - - fn count_grafts( - receivers: HashMap, - ) -> (usize, HashMap) { - let mut new_receivers = HashMap::new(); - let mut acc = 0; - - for (peer_id, c) in receivers.into_iter() { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Graft(_)) = priority.try_recv() { - acc += 1; - } - } - new_receivers.insert( - peer_id, - RpcReceiver { - priority_len: c.priority_len, - priority: priority.peekable(), - non_priority: c.non_priority, - }, - ); - } - (acc, new_receivers) - } - - // there should be mesh_n GRAFT messages. - let (graft_messages, mut receivers) = count_grafts(receivers); - - assert_eq!( - graft_messages, 6, - "There should be 6 grafts messages sent to peers" - ); - - // verify fanout nodes - // add 3 random peers to the fanout[topic1] - gs.fanout - .insert(topic_hashes[1].clone(), Default::default()); - let mut new_peers: Vec = vec![]; - - for _ in 0..3 { - let random_peer = PeerId::random(); - // inform the behaviour of a new peer - let address = "/ip4/127.0.0.1".parse::().unwrap(); - gs.handle_established_inbound_connection( - ConnectionId::new_unchecked(0), - random_peer, - &address, - &address, - ) - .unwrap(); - let sender = RpcSender::new(gs.config.connection_handler_queue_len()); - let receiver = sender.new_receiver(); - let connection_id = ConnectionId::new_unchecked(0); - gs.connected_peers.insert( - random_peer, - PeerConnections { - kind: PeerKind::Floodsub, - connections: vec![connection_id], - topics: Default::default(), - dont_send_received: LinkedHashMap::new(), - dont_send_sent: LinkedHashMap::new(), - sender, - }, - ); - receivers.insert(random_peer, receiver); - - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: random_peer, - connection_id, - endpoint: &ConnectedPoint::Dialer { - address, - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }, - failed_addresses: &[], - other_established: 0, - })); - - // add the new peer to the fanout - let fanout_peers = gs.fanout.get_mut(&topic_hashes[1]).unwrap(); - fanout_peers.insert(random_peer); - new_peers.push(random_peer); - } - - // subscribe to topic1 - gs.subscribe(&topics[1]).unwrap(); - - // the three new peers should have been added, along with 3 more from the pool. - assert!( - gs.mesh.get(&topic_hashes[1]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - let mesh_peers = gs.mesh.get(&topic_hashes[1]).unwrap(); - for new_peer in new_peers { - assert!( - mesh_peers.contains(&new_peer), - "Fanout peer should be included in the mesh" - ); - } - - // there should now 6 graft messages to be sent - let (graft_messages, _) = count_grafts(receivers); - - assert_eq!( - graft_messages, 6, - "There should be 6 grafts messages sent to peers" - ); -} - -/// Test local node publish to subscribed topic -#[test] -fn test_publish_without_flood_publishing() { - // node should: - // - Send publish message to all peers - // - Insert message into gs.mcache and gs.received - - //turn off flood publish to test old behaviour - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - - let publish_topic = String::from("test_publish"); - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![publish_topic.clone()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - assert!( - gs.mesh.contains_key(&topic_hashes[0]), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // all peers should be subscribed to the topic - assert_eq!( - gs.connected_peers - .values() - .filter(|p| p.topics.contains(&topic_hashes[0])) - .count(), - 20, - "Peers should be subscribed to the topic" - ); - - // publish on topic - let publish_data = vec![0; 42]; - gs.publish(Topic::new(publish_topic), publish_data).unwrap(); - - // Collect all publish messages - let publishes = receivers - .into_values() - .fold(vec![], |mut collected_publish, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push(message); - } - } - collected_publish - }); - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform( - publishes - .first() - .expect("Should contain > 0 entries") - .clone(), - ) - .unwrap(); - - let msg_id = gs.config.message_id(message); - - let config: Config = Config::default(); - assert_eq!( - publishes.len(), - config.mesh_n(), - "Should send a publish message to at least mesh_n peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); -} - -/// Test local node publish to unsubscribed topic -#[test] -fn test_fanout() { - // node should: - // - Populate fanout peers - // - Send publish message to fanout peers - // - Insert message into gs.mcache and gs.received - - //turn off flood publish to test fanout behaviour - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - - let fanout_topic = String::from("test_fanout"); - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![fanout_topic.clone()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - assert!( - gs.mesh.contains_key(&topic_hashes[0]), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - // Unsubscribe from topic - assert!( - gs.unsubscribe(&Topic::new(fanout_topic.clone())).unwrap(), - "should be able to unsubscribe successfully from topic" - ); - - // Publish on unsubscribed topic - let publish_data = vec![0; 42]; - gs.publish(Topic::new(fanout_topic.clone()), publish_data) - .unwrap(); - - assert_eq!( - gs.fanout - .get(&TopicHash::from_raw(fanout_topic)) - .unwrap() - .len(), - gs.config.mesh_n(), - "Fanout should contain `mesh_n` peers for fanout topic" - ); - - // Collect all publish messages - let publishes = receivers - .into_values() - .fold(vec![], |mut collected_publish, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push(message); - } - } - collected_publish - }); - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform( - publishes - .first() - .expect("Should contain > 0 entries") - .clone(), - ) - .unwrap(); - - let msg_id = gs.config.message_id(message); - - assert_eq!( - publishes.len(), - gs.config.mesh_n(), - "Should send a publish message to `mesh_n` fanout peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); -} - -/// Test the gossipsub NetworkBehaviour peer connection logic. -#[test] -fn test_inject_connected() { - let (gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1"), String::from("topic2")]) - .to_subscribe(true) - .create_network(); - - // check that our subscriptions are sent to each of the peers - // collect all the SendEvents - let subscriptions = receivers.into_iter().fold( - HashMap::>::new(), - |mut collected_subscriptions, (peer, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Subscribe(topic)) = priority.try_recv() { - let mut peer_subs = collected_subscriptions.remove(&peer).unwrap_or_default(); - peer_subs.push(topic.into_string()); - collected_subscriptions.insert(peer, peer_subs); - } - } - collected_subscriptions - }, - ); - - // check that there are two subscriptions sent to each peer - for peer_subs in subscriptions.values() { - assert!(peer_subs.contains(&String::from("topic1"))); - assert!(peer_subs.contains(&String::from("topic2"))); - assert_eq!(peer_subs.len(), 2); - } - - // check that there are 20 send events created - assert_eq!(subscriptions.len(), 20); - - // should add the new peers to `peer_topics` with an empty vec as a gossipsub node - for peer in peers { - let peer = gs.connected_peers.get(&peer).unwrap(); - assert!( - peer.topics == topic_hashes.iter().cloned().collect(), - "The topics for each node should all topics" - ); - } -} - -/// Test subscription handling -#[test] -fn test_handle_received_subscriptions() { - // For every subscription: - // SUBSCRIBE: - Add subscribed topic to peer_topics for peer. - // - Add peer to topics_peer. - // UNSUBSCRIBE - Remove topic from peer_topics for peer. - // - Remove peer from topic_peers. - - let topics = ["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - let (mut gs, peers, _receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(topics) - .to_subscribe(false) - .create_network(); - - // The first peer sends 3 subscriptions and 1 unsubscription - let mut subscriptions = topic_hashes[..3] - .iter() - .map(|topic_hash| Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: topic_hash.clone(), - }) - .collect::>(); - - subscriptions.push(Subscription { - action: SubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[topic_hashes.len() - 1].clone(), - }); - - let unknown_peer = PeerId::random(); - // process the subscriptions - // first and second peers send subscriptions - gs.handle_received_subscriptions(&subscriptions, &peers[0]); - gs.handle_received_subscriptions(&subscriptions, &peers[1]); - // unknown peer sends the same subscriptions - gs.handle_received_subscriptions(&subscriptions, &unknown_peer); - - // verify the result - - let peer = gs.connected_peers.get(&peers[0]).unwrap(); - assert!( - peer.topics - == topic_hashes - .iter() - .take(3) - .cloned() - .collect::>(), - "First peer should be subscribed to three topics" - ); - let peer1 = gs.connected_peers.get(&peers[1]).unwrap(); - assert!( - peer1.topics - == topic_hashes - .iter() - .take(3) - .cloned() - .collect::>(), - "Second peer should be subscribed to three topics" - ); - - assert!( - !gs.connected_peers.contains_key(&unknown_peer), - "Unknown peer should not have been added" - ); - - for topic_hash in topic_hashes[..3].iter() { - let topic_peers = gs - .connected_peers - .iter() - .filter(|(_, p)| p.topics.contains(topic_hash)) - .map(|(peer_id, _)| *peer_id) - .collect::>(); - assert!( - topic_peers == peers[..2].iter().cloned().collect(), - "Two peers should be added to the first three topics" - ); - } - - // Peer 0 unsubscribes from the first topic - - gs.handle_received_subscriptions( - &[Subscription { - action: SubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[0].clone(), - }], - &peers[0], - ); - - let peer = gs.connected_peers.get(&peers[0]).unwrap(); - assert!( - peer.topics == topic_hashes[1..3].iter().cloned().collect::>(), - "Peer should be subscribed to two topics" - ); - - // only gossipsub at the moment - let topic_peers = gs - .connected_peers - .iter() - .filter(|(_, p)| p.topics.contains(&topic_hashes[0])) - .map(|(peer_id, _)| *peer_id) - .collect::>(); - - assert!( - topic_peers == peers[1..2].iter().cloned().collect(), - "Only the second peers should be in the first topic" - ); -} - -/// Test Gossipsub.get_random_peers() function -#[test] -fn test_get_random_peers() { - // generate a default Config - let gs_config = ConfigBuilder::default() - .validation_mode(ValidationMode::Anonymous) - .build() - .unwrap(); - // create a gossipsub struct - let mut gs: Behaviour = Behaviour::new(MessageAuthenticity::Anonymous, gs_config).unwrap(); - - // create a topic and fill it with some peers - let topic_hash = Topic::new("Test").hash(); - let mut peers = vec![]; - let mut topics = BTreeSet::new(); - topics.insert(topic_hash.clone()); - - for _ in 0..20 { - let peer_id = PeerId::random(); - peers.push(peer_id); - gs.connected_peers.insert( - peer_id, - PeerConnections { - kind: PeerKind::Gossipsubv1_1, - connections: vec![ConnectionId::new_unchecked(0)], - topics: topics.clone(), - sender: RpcSender::new(gs.config.connection_handler_queue_len()), - dont_send_sent: LinkedHashMap::new(), - dont_send_received: LinkedHashMap::new(), - }, - ); - } - - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 5, |_| true); - assert_eq!(random_peers.len(), 5, "Expected 5 peers to be returned"); - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 30, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!( - random_peers == peers.iter().cloned().collect(), - "Expected no shuffling" - ); - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 20, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!( - random_peers == peers.iter().cloned().collect(), - "Expected no shuffling" - ); - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 0, |_| true); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - // test the filter - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 5, |_| false); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - let random_peers = get_random_peers(&gs.connected_peers, &topic_hash, 10, { - |peer| peers.contains(peer) - }); - assert!(random_peers.len() == 10, "Expected 10 peers to be returned"); -} - -/// Tests that the correct message is sent when a peer asks for a message in our cache. -#[test] -fn test_handle_iwant_msg_cached() { - let (mut gs, peers, receivers, _) = inject_nodes1() - .peer_no(20) - .topics(Vec::new()) - .to_subscribe(true) - .create_network(); - - let raw_message = RawMessage { - source: Some(peers[11]), - data: vec![1, 2, 3, 4], - sequence_number: Some(1u64), - topic: TopicHash::from_raw("topic"), - signature: None, - key: None, - validated: true, - }; - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - - let msg_id = gs.config.message_id(message); - gs.mcache.put(&msg_id, raw_message); - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // the messages we are sending - let sent_messages = receivers - .into_values() - .fold(vec![], |mut collected_messages, c| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::Forward { message, .. }) = non_priority.try_recv() { - collected_messages.push(message) - } - } - collected_messages - }); - - assert!( - sent_messages - .iter() - .map(|msg| gs.data_transform.inbound_transform(msg.clone()).unwrap()) - .any(|msg| gs.config.message_id(&msg) == msg_id), - "Expected the cached message to be sent to an IWANT peer" - ); -} - -/// Tests that messages are sent correctly depending on the shifting of the message cache. -#[test] -fn test_handle_iwant_msg_cached_shifted() { - let (mut gs, peers, mut receivers, _) = inject_nodes1() - .peer_no(20) - .topics(Vec::new()) - .to_subscribe(true) - .create_network(); - - // perform 10 memshifts and check that it leaves the cache - for shift in 1..10 { - let raw_message = RawMessage { - source: Some(peers[11]), - data: vec![1, 2, 3, 4], - sequence_number: Some(shift), - topic: TopicHash::from_raw("topic"), - signature: None, - key: None, - validated: true, - }; - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - - let msg_id = gs.config.message_id(message); - gs.mcache.put(&msg_id, raw_message); - for _ in 0..shift { - gs.mcache.shift(); - } - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // is the message is being sent? - let mut message_exists = false; - receivers = receivers.into_iter().map(|(peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if matches!(non_priority.try_recv(), Ok(RpcOut::Forward{message, timeout: _ }) if - gs.config.message_id( - &gs.data_transform - .inbound_transform(message.clone()) - .unwrap(), - ) == msg_id) - { - message_exists = true; - } - } - ( - peer_id, - RpcReceiver { - priority_len: c.priority_len, - priority: c.priority, - non_priority: non_priority.peekable(), - }, - ) - }).collect(); - // default history_length is 5, expect no messages after shift > 5 - if shift < 5 { - assert!( - message_exists, - "Expected the cached message to be sent to an IWANT peer before 5 shifts" - ); - } else { - assert!( - !message_exists, - "Expected the cached message to not be sent to an IWANT peer after 5 shifts" - ); - } - } -} - -/// tests that an event is not created when a peers asks for a message not in our cache -#[test] -fn test_handle_iwant_msg_not_cached() { - let (mut gs, peers, _, _) = inject_nodes1() - .peer_no(20) - .topics(Vec::new()) - .to_subscribe(true) - .create_network(); - - let events_before = gs.events.len(); - gs.handle_iwant(&peers[7], vec![MessageId::new(b"unknown id")]); - let events_after = gs.events.len(); - - assert_eq!( - events_before, events_after, - "Expected event count to stay the same" - ); -} - -/// tests that an event is created when a peer shares that it has a message we want -#[test] -fn test_handle_ihave_subscribed_and_msg_not_cached() { - let (mut gs, peers, mut receivers, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - gs.handle_ihave( - &peers[7], - vec![(topic_hashes[0].clone(), vec![MessageId::new(b"unknown id")])], - ); - - // check that we sent an IWANT request for `unknown id` - let mut iwant_exists = false; - let receiver = receivers.remove(&peers[7]).unwrap(); - let non_priority = receiver.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::IWant(IWant { message_ids })) = non_priority.try_recv() { - if message_ids - .iter() - .any(|m| *m == MessageId::new(b"unknown id")) - { - iwant_exists = true; - break; - } - } - } - - assert!( - iwant_exists, - "Expected to send an IWANT control message for unkown message id" - ); -} - -/// tests that an event is not created when a peer shares that it has a message that -/// we already have -#[test] -fn test_handle_ihave_subscribed_and_msg_cached() { - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - let msg_id = MessageId::new(b"known id"); - - let events_before = gs.events.len(); - gs.handle_ihave(&peers[7], vec![(topic_hashes[0].clone(), vec![msg_id])]); - let events_after = gs.events.len(); - - assert_eq!( - events_before, events_after, - "Expected event count to stay the same" - ) -} - -/// test that an event is not created when a peer shares that it has a message in -/// a topic that we are not subscribed to -#[test] -fn test_handle_ihave_not_subscribed() { - let (mut gs, peers, _, _) = inject_nodes1() - .peer_no(20) - .topics(vec![]) - .to_subscribe(true) - .create_network(); - - let events_before = gs.events.len(); - gs.handle_ihave( - &peers[7], - vec![( - TopicHash::from_raw(String::from("unsubscribed topic")), - vec![MessageId::new(b"irrelevant id")], - )], - ); - let events_after = gs.events.len(); - - assert_eq!( - events_before, events_after, - "Expected event count to stay the same" - ) -} - -/// tests that a peer is added to our mesh when we are both subscribed -/// to the same topic -#[test] -fn test_handle_graft_is_subscribed() { - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - gs.handle_graft(&peers[7], topic_hashes.clone()); - - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); -} - -/// tests that a peer is not added to our mesh when they are subscribed to -/// a topic that we are not -#[test] -fn test_handle_graft_is_not_subscribed() { - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - gs.handle_graft( - &peers[7], - vec![TopicHash::from_raw(String::from("unsubscribed topic"))], - ); - - assert!( - !gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); -} - -/// tests multiple topics in a single graft message -#[test] -fn test_handle_graft_multiple_topics() { - let topics: Vec = ["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(topics) - .to_subscribe(true) - .create_network(); - - let mut their_topics = topic_hashes.clone(); - // their_topics = [topic1, topic2, topic3] - // our_topics = [topic1, topic2, topic4] - their_topics.pop(); - gs.leave(&their_topics[2]); - - gs.handle_graft(&peers[7], their_topics.clone()); - - for hash in topic_hashes.iter().take(2) { - assert!( - gs.mesh.get(hash).unwrap().contains(&peers[7]), - "Expected peer to be in the mesh for the first 2 topics" - ); - } - - assert!( - !gs.mesh.contains_key(&topic_hashes[2]), - "Expected the second topic to not be in the mesh" - ); -} - -/// tests that a peer is removed from our mesh -#[test] -fn test_handle_prune_peer_in_mesh() { - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(20) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - - // insert peer into our mesh for 'topic1' - gs.mesh - .insert(topic_hashes[0].clone(), peers.iter().cloned().collect()); - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be in mesh" - ); - - gs.handle_prune( - &peers[7], - topic_hashes - .iter() - .map(|h| (h.clone(), vec![], None)) - .collect(), - ); - assert!( - !gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be removed from mesh" - ); -} - -fn count_control_msgs( - receivers: HashMap, - mut filter: impl FnMut(&PeerId, &RpcOut) -> bool, -) -> (usize, HashMap) { - let mut new_receivers = HashMap::new(); - let mut collected_messages = 0; - for (peer_id, c) in receivers.into_iter() { - let priority = c.priority.into_inner(); - let non_priority = c.non_priority.into_inner(); - while !priority.is_empty() || !non_priority.is_empty() { - if let Ok(rpc) = priority.try_recv() { - if filter(&peer_id, &rpc) { - collected_messages += 1; - } - } - if let Ok(rpc) = non_priority.try_recv() { - if filter(&peer_id, &rpc) { - collected_messages += 1; - } - } - } - new_receivers.insert( - peer_id, - RpcReceiver { - priority_len: c.priority_len, - priority: priority.peekable(), - non_priority: non_priority.peekable(), - }, - ); - } - (collected_messages, new_receivers) -} - -fn flush_events( - gs: &mut Behaviour, - receivers: HashMap, -) -> HashMap { - gs.events.clear(); - let mut new_receivers = HashMap::new(); - for (peer_id, c) in receivers.into_iter() { - let priority = c.priority.into_inner(); - let non_priority = c.non_priority.into_inner(); - while !priority.is_empty() || !non_priority.is_empty() { - let _ = priority.try_recv(); - let _ = non_priority.try_recv(); - } - new_receivers.insert( - peer_id, - RpcReceiver { - priority_len: c.priority_len, - priority: priority.peekable(), - non_priority: non_priority.peekable(), - }, - ); - } - new_receivers -} - -/// tests that a peer added as explicit peer gets connected to -#[test] -fn test_explicit_peer_gets_connected() { - let (mut gs, _, _, _) = inject_nodes1() - .peer_no(0) - .topics(Vec::new()) - .to_subscribe(true) - .create_network(); - - //create new peer - let peer = PeerId::random(); - - //add peer as explicit peer - gs.add_explicit_peer(&peer); - - let num_events = gs - .events - .iter() - .filter(|e| match e { - ToSwarm::Dial { opts } => opts.get_peer_id() == Some(peer), - _ => false, - }) - .count(); - - assert_eq!( - num_events, 1, - "There was no dial peer event for the explicit peer" - ); -} - -#[test] -fn test_explicit_peer_reconnects() { - let config = ConfigBuilder::default() - .check_explicit_peers_ticks(2) - .build() - .unwrap(); - let (mut gs, others, receivers, _) = inject_nodes1() - .peer_no(1) - .topics(Vec::new()) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - let peer = others.first().unwrap(); - - //add peer as explicit peer - gs.add_explicit_peer(peer); - - flush_events(&mut gs, receivers); - - //disconnect peer - disconnect_peer(&mut gs, peer); - - gs.heartbeat(); - - //check that no reconnect after first heartbeat since `explicit_peer_ticks == 2` - assert_eq!( - gs.events - .iter() - .filter(|e| match e { - ToSwarm::Dial { opts } => opts.get_peer_id() == Some(*peer), - _ => false, - }) - .count(), - 0, - "There was a dial peer event before explicit_peer_ticks heartbeats" - ); - - gs.heartbeat(); - - //check that there is a reconnect after second heartbeat - assert!( - gs.events - .iter() - .filter(|e| match e { - ToSwarm::Dial { opts } => opts.get_peer_id() == Some(*peer), - _ => false, - }) - .count() - >= 1, - "There was no dial peer event for the explicit peer" - ); -} - -#[test] -fn test_handle_graft_explicit_peer() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(1) - .topics(vec![String::from("topic1"), String::from("topic2")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - let peer = peers.first().unwrap(); - - gs.handle_graft(peer, topic_hashes.clone()); - - //peer got not added to mesh - assert!(gs.mesh[&topic_hashes[0]].is_empty()); - assert!(gs.mesh[&topic_hashes[1]].is_empty()); - - //check prunes - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == peer - && match m { - RpcOut::Prune(Prune { topic_hash, .. }) => { - topic_hash == &topic_hashes[0] || topic_hash == &topic_hashes[1] - } - _ => false, - } - }); - assert!( - control_msgs >= 2, - "Not enough prunes sent when grafting from explicit peer" - ); -} - -#[test] -fn explicit_peers_not_added_to_mesh_on_receiving_subscription() { - let (gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(2) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - //only peer 1 is in the mesh not peer 0 (which is an explicit peer) - assert_eq!( - gs.mesh[&topic_hashes[0]], - vec![peers[1]].into_iter().collect() - ); - - //assert that graft gets created to non-explicit peer - let (control_msgs, receivers) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[1] && matches!(m, RpcOut::Graft { .. }) - }); - assert!( - control_msgs >= 1, - "No graft message got created to non-explicit peer" - ); - - //assert that no graft gets created to explicit peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] && matches!(m, RpcOut::Graft { .. }) - }); - assert_eq!( - control_msgs, 0, - "A graft message got created to an explicit peer" - ); -} - -#[test] -fn do_not_graft_explicit_peer() { - let (mut gs, others, receivers, topic_hashes) = inject_nodes1() - .peer_no(1) - .topics(vec![String::from("topic")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - gs.heartbeat(); - - //mesh stays empty - assert_eq!(gs.mesh[&topic_hashes[0]], BTreeSet::new()); - - //assert that no graft gets created to explicit peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &others[0] && matches!(m, RpcOut::Graft { .. }) - }); - assert_eq!( - control_msgs, 0, - "A graft message got created to an explicit peer" - ); -} - -#[test] -fn do_forward_messages_to_explicit_peers() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(2) - .topics(vec![String::from("topic1"), String::from("topic2")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![12], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(message.clone(), &local_id); - assert_eq!( - receivers.into_iter().fold(0, |mut fwds, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if matches!(non_priority.try_recv(), Ok(RpcOut::Forward{message: m, timeout: _}) if peer_id == peers[0] && m.data == message.data) { - fwds +=1; - } - } - fwds - }), - 1, - "The message did not get forwarded to the explicit peer" - ); -} - -#[test] -fn explicit_peers_not_added_to_mesh_on_subscribe() { - let (mut gs, peers, receivers, _) = inject_nodes1() - .peer_no(2) - .topics(Vec::new()) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - //create new topic, both peers subscribing to it but we do not subscribe to it - let topic = Topic::new(String::from("t")); - let topic_hash = topic.hash(); - for peer in peers.iter().take(2) { - gs.handle_received_subscriptions( - &[Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: topic_hash.clone(), - }], - peer, - ); - } - - //subscribe now to topic - gs.subscribe(&topic).unwrap(); - - //only peer 1 is in the mesh not peer 0 (which is an explicit peer) - assert_eq!(gs.mesh[&topic_hash], vec![peers[1]].into_iter().collect()); - - //assert that graft gets created to non-explicit peer - let (control_msgs, receivers) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[1] && matches!(m, RpcOut::Graft { .. }) - }); - assert!( - control_msgs > 0, - "No graft message got created to non-explicit peer" - ); - - //assert that no graft gets created to explicit peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] && matches!(m, RpcOut::Graft { .. }) - }); - assert_eq!( - control_msgs, 0, - "A graft message got created to an explicit peer" - ); -} - -#[test] -fn explicit_peers_not_added_to_mesh_from_fanout_on_subscribe() { - let (mut gs, peers, receivers, _) = inject_nodes1() - .peer_no(2) - .topics(Vec::new()) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - //create new topic, both peers subscribing to it but we do not subscribe to it - let topic = Topic::new(String::from("t")); - let topic_hash = topic.hash(); - for peer in peers.iter().take(2) { - gs.handle_received_subscriptions( - &[Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: topic_hash.clone(), - }], - peer, - ); - } - - //we send a message for this topic => this will initialize the fanout - gs.publish(topic.clone(), vec![1, 2, 3]).unwrap(); - - //subscribe now to topic - gs.subscribe(&topic).unwrap(); - - //only peer 1 is in the mesh not peer 0 (which is an explicit peer) - assert_eq!(gs.mesh[&topic_hash], vec![peers[1]].into_iter().collect()); - - //assert that graft gets created to non-explicit peer - let (control_msgs, receivers) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[1] && matches!(m, RpcOut::Graft { .. }) - }); - assert!( - control_msgs >= 1, - "No graft message got created to non-explicit peer" - ); - - //assert that no graft gets created to explicit peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] && matches!(m, RpcOut::Graft { .. }) - }); - assert_eq!( - control_msgs, 0, - "A graft message got created to an explicit peer" - ); -} - -#[test] -fn no_gossip_gets_sent_to_explicit_peers() { - let (mut gs, peers, mut receivers, topic_hashes) = inject_nodes1() - .peer_no(2) - .topics(vec![String::from("topic1"), String::from("topic2")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - - //forward the message - gs.handle_received_message(message, &local_id); - - //simulate multiple gossip calls (for randomness) - for _ in 0..3 { - gs.emit_gossip(); - } - - //assert that no gossip gets sent to explicit peer - let receiver = receivers.remove(&peers[0]).unwrap(); - let mut gossips = 0; - let non_priority = receiver.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::IHave(_)) = non_priority.try_recv() { - gossips += 1; - } - } - assert_eq!(gossips, 0, "Gossip got emitted to explicit peer"); -} - -/// Tests the mesh maintenance addition -#[test] -fn test_mesh_addition() { - let config: Config = Config::default(); - - // Adds mesh_low peers and PRUNE 2 giving us a deficit. - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n() + 1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - let to_remove_peers = config.mesh_n() + 1 - config.mesh_n_low() - 1; - - for peer in peers.iter().take(to_remove_peers) { - gs.handle_prune( - peer, - topics.iter().map(|h| (h.clone(), vec![], None)).collect(), - ); - } - - // Verify the pruned peers are removed from the mesh. - assert_eq!( - gs.mesh.get(&topics[0]).unwrap().len(), - config.mesh_n_low() - 1 - ); - - // run a heartbeat - gs.heartbeat(); - - // Peers should be added to reach mesh_n - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), config.mesh_n()); -} - -/// Tests the mesh maintenance subtraction -#[test] -fn test_mesh_subtraction() { - let config = Config::default(); - - // Adds mesh_low peers and PRUNE 2 giving us a deficit. - let n = config.mesh_n_high() + 10; - //make all outbound connections so that we allow grafting to all - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(n) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .outbound(n) - .create_network(); - - // graft all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - // run a heartbeat - gs.heartbeat(); - - // Peers should be removed to reach mesh_n - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), config.mesh_n()); -} - -#[test] -fn test_connect_to_px_peers_on_handle_prune() { - let config: Config = Config::default(); - - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - //handle prune from single peer with px peers - - let mut px = Vec::new(); - //propose more px peers than config.prune_peers() - for _ in 0..config.prune_peers() + 5 { - px.push(PeerInfo { - peer_id: Some(PeerId::random()), - }); - } - - gs.handle_prune( - &peers[0], - vec![( - topics[0].clone(), - px.clone(), - Some(config.prune_backoff().as_secs()), - )], - ); - - //Check DialPeer events for px peers - let dials: Vec<_> = gs - .events - .iter() - .filter_map(|e| match e { - ToSwarm::Dial { opts } => opts.get_peer_id(), - _ => None, - }) - .collect(); - - // Exactly config.prune_peers() many random peers should be dialled - assert_eq!(dials.len(), config.prune_peers()); - - let dials_set: HashSet<_> = dials.into_iter().collect(); - - // No duplicates - assert_eq!(dials_set.len(), config.prune_peers()); - - //all dial peers must be in px - assert!(dials_set.is_subset( - &px.iter() - .map(|i| *i.peer_id.as_ref().unwrap()) - .collect::>() - )); -} - -#[test] -fn test_send_px_and_backoff_in_prune() { - let config: Config = Config::default(); - - //build mesh with enough peers for px - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(config.prune_peers() + 1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - //send prune to peer - gs.send_graft_prune( - HashMap::new(), - vec![(peers[0], vec![topics[0].clone()])] - .into_iter() - .collect(), - HashSet::new(), - ); - - //check prune message - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] - && match m { - RpcOut::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - topic_hash == &topics[0] && - peers.len() == config.prune_peers() && - //all peers are different - peers.iter().collect::>().len() == - config.prune_peers() && - backoff.unwrap() == config.prune_backoff().as_secs() - } - _ => false, - } - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_prune_backoffed_peer_on_graft() { - let config: Config = Config::default(); - - //build mesh with enough peers for px - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(config.prune_peers() + 1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - //remove peer from mesh and send prune to peer => this adds a backoff for this peer - gs.mesh.get_mut(&topics[0]).unwrap().remove(&peers[0]); - gs.send_graft_prune( - HashMap::new(), - vec![(peers[0], vec![topics[0].clone()])] - .into_iter() - .collect(), - HashSet::new(), - ); - - //ignore all messages until now - let receivers = flush_events(&mut gs, receivers); - - //handle graft - gs.handle_graft(&peers[0], vec![topics[0].clone()]); - - //check prune message - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] - && match m { - RpcOut::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - topic_hash == &topics[0] && - //no px in this case - peers.is_empty() && - backoff.unwrap() == config.prune_backoff().as_secs() - } - _ => false, - } - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_do_not_graft_within_backoff_period() { - let config = ConfigBuilder::default() - .backoff_slack(1) - .heartbeat_interval(Duration::from_millis(100)) - .build() - .unwrap(); - //only one peer => mesh too small and will try to regraft as early as possible - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - //handle prune from peer with backoff of one second - gs.handle_prune(&peers[0], vec![(topics[0].clone(), Vec::new(), Some(1))]); - - //forget all events until now - let receivers = flush_events(&mut gs, receivers); - - //call heartbeat - gs.heartbeat(); - - //Sleep for one second and apply 10 regular heartbeats (interval = 100ms). - for _ in 0..10 { - sleep(Duration::from_millis(100)); - gs.heartbeat(); - } - - //Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat - // is needed). - let (control_msgs, receivers) = - count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert_eq!( - control_msgs, 0, - "Graft message created too early within backoff period" - ); - - //Heartbeat one more time this should graft now - sleep(Duration::from_millis(100)); - gs.heartbeat(); - - //check that graft got created - let (control_msgs, _) = count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert!( - control_msgs > 0, - "No graft message was created after backoff period" - ); -} - -#[test] -fn test_do_not_graft_within_default_backoff_period_after_receiving_prune_without_backoff() { - //set default backoff period to 1 second - let config = ConfigBuilder::default() - .prune_backoff(Duration::from_millis(90)) - .backoff_slack(1) - .heartbeat_interval(Duration::from_millis(100)) - .build() - .unwrap(); - //only one peer => mesh too small and will try to regraft as early as possible - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - //handle prune from peer without a specified backoff - gs.handle_prune(&peers[0], vec![(topics[0].clone(), Vec::new(), None)]); - - //forget all events until now - let receivers = flush_events(&mut gs, receivers); - - //call heartbeat - gs.heartbeat(); - - //Apply one more heartbeat - sleep(Duration::from_millis(100)); - gs.heartbeat(); - - //Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat - // is needed). - let (control_msgs, receivers) = - count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert_eq!( - control_msgs, 0, - "Graft message created too early within backoff period" - ); - - //Heartbeat one more time this should graft now - sleep(Duration::from_millis(100)); - gs.heartbeat(); - - //check that graft got created - let (control_msgs, _) = count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert!( - control_msgs > 0, - "No graft message was created after backoff period" - ); -} - -#[test] -fn test_unsubscribe_backoff() { - const HEARTBEAT_INTERVAL: Duration = Duration::from_millis(100); - let config = ConfigBuilder::default() - .backoff_slack(1) - // ensure a prune_backoff > unsubscribe_backoff - .prune_backoff(Duration::from_secs(5)) - .unsubscribe_backoff(1) - .heartbeat_interval(HEARTBEAT_INTERVAL) - .build() - .unwrap(); - - let topic = String::from("test"); - // only one peer => mesh too small and will try to regraft as early as possible - let (mut gs, _, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec![topic.clone()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - let _ = gs.unsubscribe(&Topic::new(topic)); - - let (control_msgs, receivers) = count_control_msgs(receivers, |_, m| match m { - RpcOut::Prune(Prune { backoff, .. }) => backoff == &Some(1), - _ => false, - }); - assert_eq!( - control_msgs, 1, - "Peer should be pruned with `unsubscribe_backoff`." - ); - - let _ = gs.subscribe(&Topic::new(topics[0].to_string())); - - // forget all events until now - let receivers = flush_events(&mut gs, receivers); - - // call heartbeat - gs.heartbeat(); - - // Sleep for one second and apply 10 regular heartbeats (interval = 100ms). - for _ in 0..10 { - sleep(HEARTBEAT_INTERVAL); - gs.heartbeat(); - } - - // Check that no graft got created (we have backoff_slack = 1 therefore one more heartbeat - // is needed). - let (control_msgs, receivers) = - count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert_eq!( - control_msgs, 0, - "Graft message created too early within backoff period" - ); - - // Heartbeat one more time this should graft now - sleep(HEARTBEAT_INTERVAL); - gs.heartbeat(); - - // check that graft got created - let (control_msgs, _) = count_control_msgs(receivers, |_, m| matches!(m, RpcOut::Graft { .. })); - assert!( - control_msgs > 0, - "No graft message was created after backoff period" - ); -} - -#[test] -fn test_flood_publish() { - let config: Config = Config::default(); - - let topic = "test"; - // Adds more peers than mesh can hold to test flood publishing - let (mut gs, _, receivers, _) = inject_nodes1() - .peer_no(config.mesh_n_high() + 10) - .topics(vec![topic.into()]) - .to_subscribe(true) - .create_network(); - - //publish message - let publish_data = vec![0; 42]; - gs.publish(Topic::new(topic), publish_data).unwrap(); - - // Collect all publish messages - let publishes = receivers - .into_values() - .fold(vec![], |mut collected_publish, c| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push(message); - } - } - collected_publish - }); - - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform( - publishes - .first() - .expect("Should contain > 0 entries") - .clone(), - ) - .unwrap(); - - let msg_id = gs.config.message_id(message); - - let config: Config = Config::default(); - assert_eq!( - publishes.len(), - config.mesh_n_high() + 10, - "Should send a publish message to all known peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); -} - -#[test] -fn test_gossip_to_at_least_gossip_lazy_peers() { - let config: Config = Config::default(); - - //add more peers than in mesh to test gossipping - //by default only mesh_n_low peers will get added to mesh - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(config.mesh_n_low() + config.gossip_lazy() + 1) - .topics(vec!["topic".into()]) - .to_subscribe(true) - .create_network(); - - //receive message - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(raw_message.clone(), &PeerId::random()); - - //emit gossip - gs.emit_gossip(); - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - - //check that exactly config.gossip_lazy() many gossip messages were sent. - let (control_msgs, _) = count_control_msgs(receivers, |_, action| match action { - RpcOut::IHave(IHave { - topic_hash, - message_ids, - }) => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id), - _ => false, - }); - assert_eq!(control_msgs, config.gossip_lazy()); -} - -#[test] -fn test_gossip_to_at_most_gossip_factor_peers() { - let config: Config = Config::default(); - - //add a lot of peers - let m = config.mesh_n_low() + config.gossip_lazy() * (2.0 / config.gossip_factor()) as usize; - let (mut gs, _, receivers, topic_hashes) = inject_nodes1() - .peer_no(m) - .topics(vec!["topic".into()]) - .to_subscribe(true) - .create_network(); - - //receive message - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(raw_message.clone(), &PeerId::random()); - - //emit gossip - gs.emit_gossip(); - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - //check that exactly config.gossip_lazy() many gossip messages were sent. - let (control_msgs, _) = count_control_msgs(receivers, |_, action| match action { - RpcOut::IHave(IHave { - topic_hash, - message_ids, - }) => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id), - _ => false, - }); - assert_eq!( - control_msgs, - ((m - config.mesh_n_low()) as f64 * config.gossip_factor()) as usize - ); -} - -#[test] -fn test_accept_only_outbound_peer_grafts_when_mesh_full() { - let config: Config = Config::default(); - - //enough peers to fill the mesh - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - // graft all the peers => this will fill the mesh - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //assert current mesh size - assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high()); - - //create an outbound and an inbound peer - let (inbound, _in_reciver) = add_peer(&mut gs, &topics, false, false); - let (outbound, _out_receiver) = add_peer(&mut gs, &topics, true, false); - - //send grafts - gs.handle_graft(&inbound, vec![topics[0].clone()]); - gs.handle_graft(&outbound, vec![topics[0].clone()]); - - //assert mesh size - assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high() + 1); - - //inbound is not in mesh - assert!(!gs.mesh[&topics[0]].contains(&inbound)); - - //outbound is in mesh - assert!(gs.mesh[&topics[0]].contains(&outbound)); -} - -#[test] -fn test_do_not_remove_too_many_outbound_peers() { - //use an extreme case to catch errors with high probability - let m = 50; - let n = 2 * m; - let config = ConfigBuilder::default() - .mesh_n_high(n) - .mesh_n(n) - .mesh_n_low(n) - .mesh_outbound_min(m) - .build() - .unwrap(); - - //fill the mesh with inbound connections - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(n) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .create_network(); - - // graft all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //create m outbound connections and graft (we will accept the graft) - let mut outbound = HashSet::new(); - for _ in 0..m { - let (peer, _) = add_peer(&mut gs, &topics, true, false); - outbound.insert(peer); - gs.handle_graft(&peer, topics.clone()); - } - - //mesh is overly full - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), n + m); - - // run a heartbeat - gs.heartbeat(); - - // Peers should be removed to reach n - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), n); - - //all outbound peers are still in the mesh - assert!(outbound.iter().all(|p| gs.mesh[&topics[0]].contains(p))); -} - -#[test] -fn test_add_outbound_peers_if_min_is_not_satisfied() { - let config: Config = Config::default(); - - // Fill full mesh with inbound peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - // graft all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //create config.mesh_outbound_min() many outbound connections without grafting - let mut peers = vec![]; - for _ in 0..config.mesh_outbound_min() { - peers.push(add_peer(&mut gs, &topics, true, false)); - } - - // Nothing changed in the mesh yet - assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high()); - - // run a heartbeat - gs.heartbeat(); - - // The outbound peers got additionally added - assert_eq!( - gs.mesh[&topics[0]].len(), - config.mesh_n_high() + config.mesh_outbound_min() - ); -} - -#[test] -fn test_prune_negative_scored_peers() { - let config = Config::default(); - - //build mesh with one peer - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - //add penalty to peer - gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1); - - //execute heartbeat - gs.heartbeat(); - - //peer should not be in mesh anymore - assert!(gs.mesh[&topics[0]].is_empty()); - - //check prune message - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[0] - && match m { - RpcOut::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - topic_hash == &topics[0] && - //no px in this case - peers.is_empty() && - backoff.unwrap() == config.prune_backoff().as_secs() - } - _ => false, - } - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_dont_graft_to_negative_scored_peers() { - let config = Config::default(); - //init full mesh - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - //add two additional peers that will not be part of the mesh - let (p1, _receiver1) = add_peer(&mut gs, &topics, false, false); - let (p2, _receiver2) = add_peer(&mut gs, &topics, false, false); - - //reduce score of p1 to negative - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 1); - - //handle prunes of all other peers - for p in peers { - gs.handle_prune(&p, vec![(topics[0].clone(), Vec::new(), None)]); - } - - //heartbeat - gs.heartbeat(); - - //assert that mesh only contains p2 - assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), 1); - assert!(gs.mesh.get(&topics[0]).unwrap().contains(&p2)); -} - -///Note that in this test also without a penalty the px would be ignored because of the -/// acceptPXThreshold, but the spec still explicitely states the rule that px from negative -/// peers should get ignored, therefore we test it here. -#[test] -fn test_ignore_px_from_negative_scored_peer() { - let config = Config::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - //penalize peer - gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1); - - //handle prune from single peer with px peers - let px = vec![PeerInfo { - peer_id: Some(PeerId::random()), - }]; - - gs.handle_prune( - &peers[0], - vec![( - topics[0].clone(), - px, - Some(config.prune_backoff().as_secs()), - )], - ); - - //assert no dials - assert_eq!( - gs.events - .iter() - .filter(|e| matches!(e, ToSwarm::Dial { .. })) - .count(), - 0 - ); -} - -#[test] -fn test_only_send_nonnegative_scoring_peers_in_px() { - let config = ConfigBuilder::default() - .prune_peers(16) - .do_px() - .build() - .unwrap(); - - // Build mesh with three peer - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(3) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - // Penalize first peer - gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1); - - // Prune second peer - gs.send_graft_prune( - HashMap::new(), - vec![(peers[1], vec![topics[0].clone()])] - .into_iter() - .collect(), - HashSet::new(), - ); - - // Check that px in prune message only contains third peer - let (control_msgs, _) = count_control_msgs(receivers, |peer_id, m| { - peer_id == &peers[1] - && match m { - RpcOut::Prune(Prune { - topic_hash, - peers: px, - .. - }) => { - topic_hash == &topics[0] - && px.len() == 1 - && px[0].peer_id.as_ref().unwrap() == &peers[2] - } - _ => false, - } - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_do_not_gossip_to_peers_below_gossip_threshold() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - - // Build full mesh - let (mut gs, peers, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - // Graft all the peer - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - // Add two additional peers that will not be part of the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - // Reduce score of p1 below peer_score_thresholds.gossip_threshold - // note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - // Reduce score of p2 below 0 but not below peer_score_thresholds.gossip_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - // Receive message - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(raw_message.clone(), &PeerId::random()); - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - - // Emit gossip - gs.emit_gossip(); - - // Check that exactly one gossip messages got sent and it got sent to p2 - let (control_msgs, _) = count_control_msgs(receivers, |peer, action| match action { - RpcOut::IHave(IHave { - topic_hash, - message_ids, - }) => { - if topic_hash == &topics[0] && message_ids.iter().any(|id| id == &msg_id) { - assert_eq!(peer, &p2); - true - } else { - false - } - } - _ => false, - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_iwant_msg_from_peer_below_gossip_threshold_gets_ignored() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - - // Build full mesh - let (mut gs, peers, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - // Graft all the peer - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - // Add two additional peers that will not be part of the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - // Reduce score of p1 below peer_score_thresholds.gossip_threshold - // note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - // Reduce score of p2 below 0 but not below peer_score_thresholds.gossip_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - // Receive message - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - gs.handle_received_message(raw_message.clone(), &PeerId::random()); - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - - gs.handle_iwant(&p1, vec![msg_id.clone()]); - gs.handle_iwant(&p2, vec![msg_id.clone()]); - - // the messages we are sending - let sent_messages = - receivers - .into_iter() - .fold(vec![], |mut collected_messages, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::Forward { message, .. }) = non_priority.try_recv() { - collected_messages.push((peer_id, message)); - } - } - collected_messages - }); - - //the message got sent to p2 - assert!(sent_messages - .iter() - .map(|(peer_id, msg)| ( - peer_id, - gs.data_transform.inbound_transform(msg.clone()).unwrap() - )) - .any(|(peer_id, msg)| peer_id == &p2 && gs.config.message_id(&msg) == msg_id)); - //the message got not sent to p1 - assert!(sent_messages - .iter() - .map(|(peer_id, msg)| ( - peer_id, - gs.data_transform.inbound_transform(msg.clone()).unwrap() - )) - .all(|(peer_id, msg)| !(peer_id == &p1 && gs.config.message_id(&msg) == msg_id))); -} - -#[test] -fn test_ihave_msg_from_peer_below_gossip_threshold_gets_ignored() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - //build full mesh - let (mut gs, peers, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - // graft all the peer - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //add two additional peers that will not be part of the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - //reduce score of p1 below peer_score_thresholds.gossip_threshold - //note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - //reduce score of p2 below 0 but not below peer_score_thresholds.gossip_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - //message that other peers have - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![], - sequence_number: Some(0), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - // Transform the inbound message - let message = &gs.data_transform.inbound_transform(raw_message).unwrap(); - - let msg_id = gs.config.message_id(message); - - gs.handle_ihave(&p1, vec![(topics[0].clone(), vec![msg_id.clone()])]); - gs.handle_ihave(&p2, vec![(topics[0].clone(), vec![msg_id.clone()])]); - - // check that we sent exactly one IWANT request to p2 - let (control_msgs, _) = count_control_msgs(receivers, |peer, c| match c { - RpcOut::IWant(IWant { message_ids }) => { - if message_ids.iter().any(|m| m == &msg_id) { - assert_eq!(peer, &p2); - true - } else { - false - } - } - _ => false, - }); - assert_eq!(control_msgs, 1); -} - -#[test] -fn test_do_not_publish_to_peer_below_publish_threshold() { - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 0.5 * peer_score_params.behaviour_penalty_weight, - publish_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - - //build mesh with no peers and no subscribed topics - let (mut gs, _, mut receivers, _) = inject_nodes1() - .gs_config(config) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - //create a new topic for which we are not subscribed - let topic = Topic::new("test"); - let topics = vec![topic.hash()]; - - //add two additional peers that will be added to the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - //reduce score of p1 below peer_score_thresholds.publish_threshold - //note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - //reduce score of p2 below 0 but not below peer_score_thresholds.publish_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - //a heartbeat will remove the peers from the mesh - gs.heartbeat(); - - // publish on topic - let publish_data = vec![0; 42]; - gs.publish(topic, publish_data).unwrap(); - - // Collect all publish messages - let publishes = receivers - .into_iter() - .fold(vec![], |mut collected_publish, (peer_id, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push((peer_id, message)); - } - } - collected_publish - }); - - //assert only published to p2 - assert_eq!(publishes.len(), 1); - assert_eq!(publishes[0].0, p2); -} - -#[test] -fn test_do_not_flood_publish_to_peer_below_publish_threshold() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 0.5 * peer_score_params.behaviour_penalty_weight, - publish_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - //build mesh with no peers - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .topics(vec!["test".into()]) - .gs_config(config) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - //add two additional peers that will be added to the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - //reduce score of p1 below peer_score_thresholds.publish_threshold - //note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - //reduce score of p2 below 0 but not below peer_score_thresholds.publish_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - //a heartbeat will remove the peers from the mesh - gs.heartbeat(); - - // publish on topic - let publish_data = vec![0; 42]; - gs.publish(Topic::new("test"), publish_data).unwrap(); - - // Collect all publish messages - let publishes = receivers - .into_iter() - .fold(vec![], |mut collected_publish, (peer_id, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if let Ok(RpcOut::Publish { message, .. }) = priority.try_recv() { - collected_publish.push((peer_id, message)) - } - } - collected_publish - }); - - //assert only published to p2 - assert_eq!(publishes.len(), 1); - assert!(publishes[0].0 == p2); -} - -#[test] -fn test_ignore_rpc_from_peers_below_graylist_threshold() { - let config = Config::default(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - gossip_threshold: 0.5 * peer_score_params.behaviour_penalty_weight, - publish_threshold: 0.5 * peer_score_params.behaviour_penalty_weight, - graylist_threshold: 3.0 * peer_score_params.behaviour_penalty_weight, - ..PeerScoreThresholds::default() - }; - - //build mesh with no peers - let (mut gs, _, _, topics) = inject_nodes1() - .topics(vec!["test".into()]) - .gs_config(config.clone()) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - //add two additional peers that will be added to the mesh - let (p1, _receiver1) = add_peer(&mut gs, &topics, false, false); - let (p2, _receiver2) = add_peer(&mut gs, &topics, false, false); - - //reduce score of p1 below peer_score_thresholds.graylist_threshold - //note that penalties get squared so two penalties means a score of - // 4 * peer_score_params.behaviour_penalty_weight. - gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2); - - //reduce score of p2 below publish_threshold but not below graylist_threshold - gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - let raw_message1 = RawMessage { - source: Some(PeerId::random()), - data: vec![1, 2, 3, 4], - sequence_number: Some(1u64), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - let raw_message2 = RawMessage { - source: Some(PeerId::random()), - data: vec![1, 2, 3, 4, 5], - sequence_number: Some(2u64), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - let raw_message3 = RawMessage { - source: Some(PeerId::random()), - data: vec![1, 2, 3, 4, 5, 6], - sequence_number: Some(3u64), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - let raw_message4 = RawMessage { - source: Some(PeerId::random()), - data: vec![1, 2, 3, 4, 5, 6, 7], - sequence_number: Some(4u64), - topic: topics[0].clone(), - signature: None, - key: None, - validated: true, - }; - - // Transform the inbound message - let message2 = &gs.data_transform.inbound_transform(raw_message2).unwrap(); - - // Transform the inbound message - let message4 = &gs.data_transform.inbound_transform(raw_message4).unwrap(); - - let subscription = Subscription { - action: SubscriptionAction::Subscribe, - topic_hash: topics[0].clone(), - }; - - let control_action = ControlAction::IHave(IHave { - topic_hash: topics[0].clone(), - message_ids: vec![config.message_id(message2)], - }); - - //clear events - gs.events.clear(); - - //receive from p1 - gs.on_connection_handler_event( - p1, - ConnectionId::new_unchecked(0), - HandlerEvent::Message { - rpc: Rpc { - messages: vec![raw_message1], - subscriptions: vec![subscription.clone()], - control_msgs: vec![control_action], - }, - invalid_messages: Vec::new(), - }, - ); - - //only the subscription event gets processed, the rest is dropped - assert_eq!(gs.events.len(), 1); - assert!(matches!( - gs.events[0], - ToSwarm::GenerateEvent(Event::Subscribed { .. }) - )); - - let control_action = ControlAction::IHave(IHave { - topic_hash: topics[0].clone(), - message_ids: vec![config.message_id(message4)], - }); - - //receive from p2 - gs.on_connection_handler_event( - p2, - ConnectionId::new_unchecked(0), - HandlerEvent::Message { - rpc: Rpc { - messages: vec![raw_message3], - subscriptions: vec![subscription], - control_msgs: vec![control_action], - }, - invalid_messages: Vec::new(), - }, - ); - - //events got processed - assert!(gs.events.len() > 1); -} - -#[test] -fn test_ignore_px_from_peers_below_accept_px_threshold() { - let config = ConfigBuilder::default().prune_peers(16).build().unwrap(); - let peer_score_params = PeerScoreParams::default(); - let peer_score_thresholds = PeerScoreThresholds { - accept_px_threshold: peer_score_params.app_specific_weight, - ..PeerScoreThresholds::default() - }; - // Build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - // Decrease score of first peer to less than accept_px_threshold - gs.set_application_score(&peers[0], 0.99); - - // Increase score of second peer to accept_px_threshold - gs.set_application_score(&peers[1], 1.0); - - // Handle prune from peer peers[0] with px peers - let px = vec![PeerInfo { - peer_id: Some(PeerId::random()), - }]; - gs.handle_prune( - &peers[0], - vec![( - topics[0].clone(), - px, - Some(config.prune_backoff().as_secs()), - )], - ); - - // Assert no dials - assert_eq!( - gs.events - .iter() - .filter(|e| matches!(e, ToSwarm::Dial { .. })) - .count(), - 0 - ); - - //handle prune from peer peers[1] with px peers - let px = vec![PeerInfo { - peer_id: Some(PeerId::random()), - }]; - gs.handle_prune( - &peers[1], - vec![( - topics[0].clone(), - px, - Some(config.prune_backoff().as_secs()), - )], - ); - - //assert there are dials now - assert!( - gs.events - .iter() - .filter(|e| matches!(e, ToSwarm::Dial { .. })) - .count() - > 0 - ); -} - -#[test] -fn test_keep_best_scoring_peers_on_oversubscription() { - let config = ConfigBuilder::default() - .mesh_n_low(15) - .mesh_n(30) - .mesh_n_high(60) - .retain_scores(29) - .build() - .unwrap(); - - //build mesh with more peers than mesh can hold - let n = config.mesh_n_high() + 1; - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(n) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(n) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - // graft all, will be accepted since the are outbound - for peer in &peers { - gs.handle_graft(peer, topics.clone()); - } - - //assign scores to peers equalling their index - - //set random positive scores - for (index, peer) in peers.iter().enumerate() { - gs.set_application_score(peer, index as f64); - } - - assert_eq!(gs.mesh[&topics[0]].len(), n); - - //heartbeat to prune some peers - gs.heartbeat(); - - assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n()); - - //mesh contains retain_scores best peers - assert!(gs.mesh[&topics[0]].is_superset( - &peers[(n - config.retain_scores())..] - .iter() - .cloned() - .collect() - )); -} - -#[test] -fn test_scoring_p1() { - let config = Config::default(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 2.0, - time_in_mesh_quantum: Duration::from_millis(50), - time_in_mesh_cap: 10.0, - topic_weight: 0.7, - ..TopicScoreParams::default() - }; - peer_score_params - .topics - .insert(topic_hash, topic_params.clone()); - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, _) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - //sleep for 2 times the mesh_quantum - sleep(topic_params.time_in_mesh_quantum * 2); - //refresh scores - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - assert!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]) - >= 2.0 * topic_params.time_in_mesh_weight * topic_params.topic_weight, - "score should be at least 2 * time_in_mesh_weight * topic_weight" - ); - assert!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]) - < 3.0 * topic_params.time_in_mesh_weight * topic_params.topic_weight, - "score should be less than 3 * time_in_mesh_weight * topic_weight" - ); - - //sleep again for 2 times the mesh_quantum - sleep(topic_params.time_in_mesh_quantum * 2); - //refresh scores - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - assert!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]) - >= 2.0 * topic_params.time_in_mesh_weight * topic_params.topic_weight, - "score should be at least 4 * time_in_mesh_weight * topic_weight" - ); - - //sleep for enough periods to reach maximum - sleep(topic_params.time_in_mesh_quantum * (topic_params.time_in_mesh_cap - 3.0) as u32); - //refresh scores - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - topic_params.time_in_mesh_cap - * topic_params.time_in_mesh_weight - * topic_params.topic_weight, - "score should be exactly time_in_mesh_cap * time_in_mesh_weight * topic_weight" - ); -} - -fn random_message(seq: &mut u64, topics: &[TopicHash]) -> RawMessage { - let mut rng = rand::thread_rng(); - *seq += 1; - RawMessage { - source: Some(PeerId::random()), - data: (0..rng.gen_range(10..30)).map(|_| rng.gen()).collect(), - sequence_number: Some(*seq), - topic: topics[rng.gen_range(0..topics.len())].clone(), - signature: None, - key: None, - validated: true, - } -} - -#[test] -fn test_scoring_p2() { - let config = Config::default(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 2.0, - first_message_deliveries_cap: 10.0, - first_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..TopicScoreParams::default() - }; - peer_score_params - .topics - .insert(topic_hash, topic_params.clone()); - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - let m1 = random_message(&mut seq, &topics); - //peer 0 delivers message first - deliver_message(&mut gs, 0, m1.clone()); - //peer 1 delivers message second - deliver_message(&mut gs, 1, m1); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 1.0 * topic_params.first_message_deliveries_weight * topic_params.topic_weight, - "score should be exactly first_message_deliveries_weight * topic_weight" - ); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 0.0, - "there should be no score for second message deliveries * topic_weight" - ); - - //peer 2 delivers two new messages - deliver_message(&mut gs, 1, random_message(&mut seq, &topics)); - deliver_message(&mut gs, 1, random_message(&mut seq, &topics)); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 2.0 * topic_params.first_message_deliveries_weight * topic_params.topic_weight, - "score should be exactly 2 * first_message_deliveries_weight * topic_weight" - ); - - //test decaying - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 1.0 * topic_params.first_message_deliveries_decay - * topic_params.first_message_deliveries_weight - * topic_params.topic_weight, - "score should be exactly first_message_deliveries_decay * \ - first_message_deliveries_weight * topic_weight" - ); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 2.0 * topic_params.first_message_deliveries_decay - * topic_params.first_message_deliveries_weight - * topic_params.topic_weight, - "score should be exactly 2 * first_message_deliveries_decay * \ - first_message_deliveries_weight * topic_weight" - ); - - //test cap - for _ in 0..topic_params.first_message_deliveries_cap as u64 { - deliver_message(&mut gs, 1, random_message(&mut seq, &topics)); - } - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - topic_params.first_message_deliveries_cap - * topic_params.first_message_deliveries_weight - * topic_params.topic_weight, - "score should be exactly first_message_deliveries_cap * \ - first_message_deliveries_weight * topic_weight" - ); -} - -#[test] -fn test_scoring_p3() { - let config = Config::default(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: -2.0, - mesh_message_deliveries_decay: 0.9, - mesh_message_deliveries_cap: 10.0, - mesh_message_deliveries_threshold: 5.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(100), - topic_weight: 0.7, - ..TopicScoreParams::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - let mut expected_message_deliveries = 0.0; - - //messages used to test window - let m1 = random_message(&mut seq, &topics); - let m2 = random_message(&mut seq, &topics); - - //peer 1 delivers m1 - deliver_message(&mut gs, 1, m1.clone()); - - //peer 0 delivers two message - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - expected_message_deliveries += 2.0; - - sleep(Duration::from_millis(60)); - - //peer 1 delivers m2 - deliver_message(&mut gs, 1, m2.clone()); - - sleep(Duration::from_millis(70)); - //peer 0 delivers m1 and m2 only m2 gets counted - deliver_message(&mut gs, 0, m1); - deliver_message(&mut gs, 0, m2); - expected_message_deliveries += 1.0; - - sleep(Duration::from_millis(900)); - - //message deliveries penalties get activated, peer 0 has only delivered 3 messages and - // therefore gets a penalty - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - (5f64 - expected_message_deliveries).powi(2) * -2.0 * 0.7 - ); - - // peer 0 delivers a lot of messages => message_deliveries should be capped at 10 - for _ in 0..20 { - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - } - - expected_message_deliveries = 10.0; - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - //apply 10 decays - for _ in 0..10 { - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay - } - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - (5f64 - expected_message_deliveries).powi(2) * -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p3b() { - let config = ConfigBuilder::default() - .prune_backoff(Duration::from_millis(100)) - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: -2.0, - mesh_message_deliveries_decay: 0.9, - mesh_message_deliveries_cap: 10.0, - mesh_message_deliveries_threshold: 5.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(100), - mesh_failure_penalty_weight: -3.0, - mesh_failure_penalty_decay: 0.95, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - let mut expected_message_deliveries = 0.0; - - //add some positive score - gs.peer_score - .as_mut() - .unwrap() - .0 - .set_application_score(&peers[0], 100.0); - - //peer 0 delivers two message - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - expected_message_deliveries += 2.0; - - sleep(Duration::from_millis(1050)); - - //activation kicks in - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay - - //prune peer - gs.handle_prune(&peers[0], vec![(topics[0].clone(), vec![], None)]); - - //wait backoff - sleep(Duration::from_millis(130)); - - //regraft peer - gs.handle_graft(&peers[0], topics.clone()); - - //the score should now consider p3b - let mut expected_b3 = (5f64 - expected_message_deliveries).powi(2); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 100.0 + expected_b3 * -3.0 * 0.7 - ); - - //we can also add a new p3 to the score - - //peer 0 delivers one message - deliver_message(&mut gs, 0, random_message(&mut seq, &topics)); - expected_message_deliveries += 1.0; - - sleep(Duration::from_millis(1050)); - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - expected_message_deliveries *= 0.9; //decay - expected_b3 *= 0.95; - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 100.0 + (expected_b3 * -3.0 + (5f64 - expected_message_deliveries).powi(2) * -2.0) * 0.7 - ); -} - -#[test] -fn test_scoring_p4_valid_message() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers valid message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - //message m1 gets validated - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Accept, - ) - .unwrap(); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); -} - -#[test] -fn test_scoring_p4_invalid_signature() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - - //peer 0 delivers message with invalid signature - let m = random_message(&mut seq, &topics); - - gs.on_connection_handler_event( - peers[0], - ConnectionId::new_unchecked(0), - HandlerEvent::Message { - rpc: Rpc { - messages: vec![], - subscriptions: vec![], - control_msgs: vec![], - }, - invalid_messages: vec![(m, ValidationError::InvalidSignature)], - }, - ); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_message_from_self() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers invalid message from self - let mut m = random_message(&mut seq, &topics); - m.source = Some(*gs.publish_config.get_own_id().unwrap()); - - deliver_message(&mut gs, 0, m); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_ignored_message() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers ignored message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - - //message m1 gets ignored - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Ignore, - ) - .unwrap(); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); -} - -#[test] -fn test_scoring_p4_application_invalidated_message() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers invalid message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - - //message m1 gets rejected - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_application_invalid_message_from_two_peers() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with two peers - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers invalid message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap(); - - //peer 1 delivers same message - deliver_message(&mut gs, 1, m1); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[1]), 0.0); - - //message m1 gets rejected - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_three_application_invalid_messages() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers two invalid message - let m1 = random_message(&mut seq, &topics); - let m2 = random_message(&mut seq, &topics); - let m3 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - deliver_message(&mut gs, 0, m2.clone()); - deliver_message(&mut gs, 0, m3.clone()); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - - // Transform the inbound message - let message2 = &gs.data_transform.inbound_transform(m2).unwrap(); - // Transform the inbound message - let message3 = &gs.data_transform.inbound_transform(m3).unwrap(); - - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - //messages gets rejected - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - gs.report_message_validation_result( - &config.message_id(message2), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - gs.report_message_validation_result( - &config.message_id(message3), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - - //number of invalid messages gets squared - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 9.0 * -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p4_decay() { - let config = ConfigBuilder::default() - .validate_messages() - .build() - .unwrap(); - let mut peer_score_params = PeerScoreParams::default(); - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let topic_params = TopicScoreParams { - time_in_mesh_weight: 0.0, //deactivate time in mesh - first_message_deliveries_weight: 0.0, //deactivate first time deliveries - mesh_message_deliveries_weight: 0.0, //deactivate message deliveries - mesh_failure_penalty_weight: 0.0, //deactivate mesh failure penalties - invalid_message_deliveries_weight: -2.0, - invalid_message_deliveries_decay: 0.9, - topic_weight: 0.7, - ..Default::default() - }; - peer_score_params.topics.insert(topic_hash, topic_params); - peer_score_params.app_specific_weight = 1.0; - let peer_score_thresholds = PeerScoreThresholds::default(); - - //build mesh with one peer - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, peer_score_thresholds))) - .create_network(); - - let mut seq = 0; - let deliver_message = |gs: &mut Behaviour, index: usize, msg: RawMessage| { - gs.handle_received_message(msg, &peers[index]); - }; - - //peer 0 delivers invalid message - let m1 = random_message(&mut seq, &topics); - deliver_message(&mut gs, 0, m1.clone()); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1).unwrap(); - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0); - - //message m1 gets rejected - gs.report_message_validation_result( - &config.message_id(message1), - &peers[0], - MessageAcceptance::Reject, - ) - .unwrap(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - -2.0 * 0.7 - ); - - //we decay - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - - // the number of invalids gets decayed to 0.9 and then squared in the score - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 0.9 * 0.9 * -2.0 * 0.7 - ); -} - -#[test] -fn test_scoring_p5() { - let peer_score_params = PeerScoreParams { - app_specific_weight: 2.0, - ..PeerScoreParams::default() - }; - - //build mesh with one peer - let (mut gs, peers, _, _) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) - .create_network(); - - gs.set_application_score(&peers[0], 1.1); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 1.1 * 2.0 - ); -} - -#[test] -fn test_scoring_p6() { - let peer_score_params = PeerScoreParams { - ip_colocation_factor_threshold: 5.0, - ip_colocation_factor_weight: -2.0, - ..Default::default() - }; - - let (mut gs, _, _, _) = inject_nodes1() - .peer_no(0) - .topics(vec![]) - .to_subscribe(false) - .gs_config(Config::default()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) - .create_network(); - - //create 5 peers with the same ip - let addr = Multiaddr::from(Ipv4Addr::new(10, 1, 2, 3)); - let peers = vec![ - add_peer_with_addr(&mut gs, &[], false, false, addr.clone()).0, - add_peer_with_addr(&mut gs, &[], false, false, addr.clone()).0, - add_peer_with_addr(&mut gs, &[], true, false, addr.clone()).0, - add_peer_with_addr(&mut gs, &[], true, false, addr.clone()).0, - add_peer_with_addr(&mut gs, &[], true, true, addr.clone()).0, - ]; - - //create 4 other peers with other ip - let addr2 = Multiaddr::from(Ipv4Addr::new(10, 1, 2, 4)); - let others = vec![ - add_peer_with_addr(&mut gs, &[], false, false, addr2.clone()).0, - add_peer_with_addr(&mut gs, &[], false, false, addr2.clone()).0, - add_peer_with_addr(&mut gs, &[], true, false, addr2.clone()).0, - add_peer_with_addr(&mut gs, &[], true, false, addr2.clone()).0, - ]; - - //no penalties yet - for peer in peers.iter().chain(others.iter()) { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 0.0); - } - - //add additional connection for 3 others with addr - for id in others.iter().take(3) { - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: *id, - connection_id: ConnectionId::new_unchecked(0), - endpoint: &ConnectedPoint::Dialer { - address: addr.clone(), - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }, - failed_addresses: &[], - other_established: 0, - })); - } - - //penalties apply squared - for peer in peers.iter().chain(others.iter().take(3)) { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0); - } - //fourth other peer still no penalty - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&others[3]), 0.0); - - //add additional connection for 3 of the peers to addr2 - for peer in peers.iter().take(3) { - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: *peer, - connection_id: ConnectionId::new_unchecked(0), - endpoint: &ConnectedPoint::Dialer { - address: addr2.clone(), - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }, - failed_addresses: &[], - other_established: 1, - })); - } - - //double penalties for the first three of each - for peer in peers.iter().take(3).chain(others.iter().take(3)) { - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(peer), - (9.0 + 4.0) * -2.0 - ); - } - - //single penalties for the rest - for peer in peers.iter().skip(3) { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0); - } - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&others[3]), - 4.0 * -2.0 - ); - - //two times same ip doesn't count twice - gs.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished { - peer_id: peers[0], - connection_id: ConnectionId::new_unchecked(0), - endpoint: &ConnectedPoint::Dialer { - address: addr, - role_override: Endpoint::Dialer, - port_use: PortUse::Reuse, - }, - failed_addresses: &[], - other_established: 2, - })); - - //nothing changed - //double penalties for the first three of each - for peer in peers.iter().take(3).chain(others.iter().take(3)) { - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(peer), - (9.0 + 4.0) * -2.0 - ); - } - - //single penalties for the rest - for peer in peers.iter().skip(3) { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0); - } - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&others[3]), - 4.0 * -2.0 - ); -} - -#[test] -fn test_scoring_p7_grafts_before_backoff() { - let config = ConfigBuilder::default() - .prune_backoff(Duration::from_millis(200)) - .graft_flood_threshold(Duration::from_millis(100)) - .build() - .unwrap(); - let peer_score_params = PeerScoreParams { - behaviour_penalty_weight: -2.0, - behaviour_penalty_decay: 0.9, - ..Default::default() - }; - - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) - .create_network(); - - //remove peers from mesh and send prune to them => this adds a backoff for the peers - for peer in peers.iter().take(2) { - gs.mesh.get_mut(&topics[0]).unwrap().remove(peer); - gs.send_graft_prune( - HashMap::new(), - HashMap::from([(*peer, vec![topics[0].clone()])]), - HashSet::new(), - ); - } - - //wait 50 millisecs - sleep(Duration::from_millis(50)); - - //first peer tries to graft - gs.handle_graft(&peers[0], vec![topics[0].clone()]); - - //double behaviour penalty for first peer (squared) - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 4.0 * -2.0 - ); - - //wait 100 millisecs - sleep(Duration::from_millis(100)); - - //second peer tries to graft - gs.handle_graft(&peers[1], vec![topics[0].clone()]); - - //single behaviour penalty for second peer - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 1.0 * -2.0 - ); - - //test decay - gs.peer_score.as_mut().unwrap().0.refresh_scores(); - - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[0]), - 4.0 * 0.9 * 0.9 * -2.0 - ); - assert_eq!( - gs.peer_score.as_ref().unwrap().0.score(&peers[1]), - 1.0 * 0.9 * 0.9 * -2.0 - ); -} - -#[test] -fn test_opportunistic_grafting() { - let config = ConfigBuilder::default() - .mesh_n_low(3) - .mesh_n(5) - .mesh_n_high(7) - .mesh_outbound_min(0) //deactivate outbound handling - .opportunistic_graft_ticks(2) - .opportunistic_graft_peers(2) - .build() - .unwrap(); - let peer_score_params = PeerScoreParams { - app_specific_weight: 1.0, - ..Default::default() - }; - let thresholds = PeerScoreThresholds { - opportunistic_graft_threshold: 2.0, - ..Default::default() - }; - - let (mut gs, peers, _receivers, topics) = inject_nodes1() - .peer_no(5) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, thresholds))) - .create_network(); - - //fill mesh with 5 peers - for peer in &peers { - gs.handle_graft(peer, topics.clone()); - } - - //add additional 5 peers - let others: Vec<_> = (0..5) - .map(|_| add_peer(&mut gs, &topics, false, false)) - .collect(); - - //currently mesh equals peers - assert_eq!(gs.mesh[&topics[0]], peers.iter().cloned().collect()); - - //give others high scores (but the first two have not high enough scores) - for (i, peer) in peers.iter().enumerate().take(5) { - gs.set_application_score(peer, 0.0 + i as f64); - } - - //set scores for peers in the mesh - for (i, (peer, _receiver)) in others.iter().enumerate().take(5) { - gs.set_application_score(peer, 0.0 + i as f64); - } - - //this gives a median of exactly 2.0 => should not apply opportunistic grafting - gs.heartbeat(); - gs.heartbeat(); - - assert_eq!( - gs.mesh[&topics[0]].len(), - 5, - "should not apply opportunistic grafting" - ); - - //reduce middle score to 1.0 giving a median of 1.0 - gs.set_application_score(&peers[2], 1.0); - - //opportunistic grafting after two heartbeats - - gs.heartbeat(); - assert_eq!( - gs.mesh[&topics[0]].len(), - 5, - "should not apply opportunistic grafting after first tick" - ); - - gs.heartbeat(); - - assert_eq!( - gs.mesh[&topics[0]].len(), - 7, - "opportunistic grafting should have added 2 peers" - ); - - assert!( - gs.mesh[&topics[0]].is_superset(&peers.iter().cloned().collect()), - "old peers are still part of the mesh" - ); - - assert!( - gs.mesh[&topics[0]].is_disjoint(&others.iter().map(|(p, _)| p).cloned().take(2).collect()), - "peers below or equal to median should not be added in opportunistic grafting" - ); -} - -#[test] -fn test_ignore_graft_from_unknown_topic() { - //build gossipsub without subscribing to any topics - let (mut gs, peers, receivers, _) = inject_nodes1() - .peer_no(1) - .topics(vec![]) - .to_subscribe(false) - .create_network(); - - //handle an incoming graft for some topic - gs.handle_graft(&peers[0], vec![Topic::new("test").hash()]); - - //assert that no prune got created - let (control_msgs, _) = count_control_msgs(receivers, |_, a| matches!(a, RpcOut::Prune { .. })); - assert_eq!( - control_msgs, 0, - "we should not prune after graft in unknown topic" - ); -} - -#[test] -fn test_ignore_too_many_iwants_from_same_peer_for_same_message() { - let config = Config::default(); - //build gossipsub with full mesh - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(false) - .create_network(); - - //add another peer not in the mesh - let (peer, receiver) = add_peer(&mut gs, &topics, false, false); - receivers.insert(peer, receiver); - - //receive a message - let mut seq = 0; - let m1 = random_message(&mut seq, &topics); - - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap(); - - let id = config.message_id(message1); - - gs.handle_received_message(m1, &PeerId::random()); - - //clear events - let receivers = flush_events(&mut gs, receivers); - - //the first gossip_retransimission many iwants return the valid message, all others are - // ignored. - for _ in 0..(2 * config.gossip_retransimission() + 10) { - gs.handle_iwant(&peer, vec![id.clone()]); - } - - assert_eq!( - receivers.into_values().fold(0, |mut fwds, c| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::Forward { .. }) = non_priority.try_recv() { - fwds += 1; - } - } - fwds - }), - config.gossip_retransimission() as usize, - "not more then gossip_retransmission many messages get sent back" - ); -} - -#[test] -fn test_ignore_too_many_ihaves() { - let config = ConfigBuilder::default() - .max_ihave_messages(10) - .build() - .unwrap(); - //build gossipsub with full mesh - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config.clone()) - .create_network(); - - //add another peer not in the mesh - let (peer, receiver) = add_peer(&mut gs, &topics, false, false); - receivers.insert(peer, receiver); - - //peer has 20 messages - let mut seq = 0; - let messages: Vec<_> = (0..20).map(|_| random_message(&mut seq, &topics)).collect(); - - //peer sends us one ihave for each message in order - for raw_message in &messages { - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), vec![config.message_id(message)])], - ); - } - - let first_ten: HashSet<_> = messages - .iter() - .take(10) - .map(|msg| gs.data_transform.inbound_transform(msg.clone()).unwrap()) - .map(|m| config.message_id(&m)) - .collect(); - - //we send iwant only for the first 10 messages - let (control_msgs, receivers) = count_control_msgs(receivers, |p, action| { - p == &peer - && matches!(action, RpcOut::IWant(IWant { message_ids }) if message_ids.len() == 1 && first_ten.contains(&message_ids[0])) - }); - assert_eq!( - control_msgs, 10, - "exactly the first ten ihaves should be processed and one iwant for each created" - ); - - //after a heartbeat everything is forgotten - gs.heartbeat(); - - for raw_message in messages[10..].iter() { - // Transform the inbound message - let message = &gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), vec![config.message_id(message)])], - ); - } - - //we sent iwant for all 10 messages - let (control_msgs, _) = count_control_msgs(receivers, |p, action| { - p == &peer - && matches!(action, RpcOut::IWant(IWant { message_ids }) if message_ids.len() == 1) - }); - assert_eq!(control_msgs, 10, "all 20 should get sent"); -} - -#[test] -fn test_ignore_too_many_messages_in_ihave() { - let config = ConfigBuilder::default() - .max_ihave_messages(10) - .max_ihave_length(10) - .build() - .unwrap(); - //build gossipsub with full mesh - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config.clone()) - .create_network(); - - //add another peer not in the mesh - let (peer, receiver) = add_peer(&mut gs, &topics, false, false); - receivers.insert(peer, receiver); - - //peer has 30 messages - let mut seq = 0; - let message_ids: Vec<_> = (0..30) - .map(|_| random_message(&mut seq, &topics)) - .map(|msg| gs.data_transform.inbound_transform(msg).unwrap()) - .map(|msg| config.message_id(&msg)) - .collect(); - - //peer sends us three ihaves - gs.handle_ihave(&peer, vec![(topics[0].clone(), message_ids[0..8].to_vec())]); - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), message_ids[0..12].to_vec())], - ); - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), message_ids[0..20].to_vec())], - ); - - let first_twelve: HashSet<_> = message_ids.iter().take(12).collect(); - - //we send iwant only for the first 10 messages - let mut sum = 0; - let (control_msgs, receivers) = count_control_msgs(receivers, |p, rpc| match rpc { - RpcOut::IWant(IWant { message_ids }) => { - p == &peer && { - assert!(first_twelve.is_superset(&message_ids.iter().collect())); - sum += message_ids.len(); - true - } - } - _ => false, - }); - assert_eq!( - control_msgs, 2, - "the third ihave should get ignored and no iwant sent" - ); - - assert_eq!(sum, 10, "exactly the first ten ihaves should be processed"); - - //after a heartbeat everything is forgotten - gs.heartbeat(); - gs.handle_ihave( - &peer, - vec![(topics[0].clone(), message_ids[20..30].to_vec())], - ); - - //we sent 10 iwant messages ids via a IWANT rpc. - let mut sum = 0; - let (control_msgs, _) = count_control_msgs(receivers, |p, rpc| match rpc { - RpcOut::IWant(IWant { message_ids }) => { - p == &peer && { - sum += message_ids.len(); - true - } - } - _ => false, - }); - assert_eq!(control_msgs, 1); - assert_eq!(sum, 10, "exactly 20 iwants should get sent"); -} - -#[test] -fn test_limit_number_of_message_ids_inside_ihave() { - let config = ConfigBuilder::default() - .max_ihave_messages(10) - .max_ihave_length(100) - .build() - .unwrap(); - //build gossipsub with full mesh - let (mut gs, peers, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_high()) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config) - .create_network(); - - //graft to all peers to really fill the mesh with all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - //add two other peers not in the mesh - let (p1, receiver1) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p1, receiver1); - let (p2, receiver2) = add_peer(&mut gs, &topics, false, false); - receivers.insert(p2, receiver2); - - //receive 200 messages from another peer - let mut seq = 0; - for _ in 0..200 { - gs.handle_received_message(random_message(&mut seq, &topics), &PeerId::random()); - } - - //emit gossip - gs.emit_gossip(); - - // both peers should have gotten 100 random ihave messages, to asser the randomness, we - // assert that both have not gotten the same set of messages, but have an intersection - // (which is the case with very high probability, the probabiltity of failure is < 10^-58). - - let mut ihaves1 = HashSet::new(); - let mut ihaves2 = HashSet::new(); - - let (control_msgs, _) = count_control_msgs(receivers, |p, action| match action { - RpcOut::IHave(IHave { message_ids, .. }) => { - if p == &p1 { - ihaves1 = message_ids.iter().cloned().collect(); - true - } else if p == &p2 { - ihaves2 = message_ids.iter().cloned().collect(); - true - } else { - false - } - } - _ => false, - }); - assert_eq!( - control_msgs, 2, - "should have emitted one ihave to p1 and one to p2" - ); - - assert_eq!( - ihaves1.len(), - 100, - "should have sent 100 message ids in ihave to p1" - ); - assert_eq!( - ihaves2.len(), - 100, - "should have sent 100 message ids in ihave to p2" - ); - assert!( - ihaves1 != ihaves2, - "should have sent different random messages to p1 and p2 \ - (this may fail with a probability < 10^-58" - ); - assert!( - ihaves1.intersection(&ihaves2).count() > 0, - "should have sent random messages with some common messages to p1 and p2 \ - (this may fail with a probability < 10^-58" - ); -} - -#[test] -fn test_iwant_penalties() { - /* - use tracing_subscriber::EnvFilter; - let _ = tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); - */ - let config = ConfigBuilder::default() - .iwant_followup_time(Duration::from_secs(4)) - .build() - .unwrap(); - let peer_score_params = PeerScoreParams { - behaviour_penalty_weight: -1.0, - ..Default::default() - }; - - // fill the mesh - let (mut gs, peers, _, topics) = inject_nodes1() - .peer_no(2) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config.clone()) - .explicit(0) - .outbound(0) - .scoring(Some((peer_score_params, PeerScoreThresholds::default()))) - .create_network(); - - // graft to all peers to really fill the mesh with all the peers - for peer in peers { - gs.handle_graft(&peer, topics.clone()); - } - - // add 100 more peers - let other_peers: Vec<_> = (0..100) - .map(|_| add_peer(&mut gs, &topics, false, false)) - .collect(); - - // each peer sends us an ihave containing each two message ids - let mut first_messages = Vec::new(); - let mut second_messages = Vec::new(); - let mut seq = 0; - for (peer, _receiver) in &other_peers { - let msg1 = random_message(&mut seq, &topics); - let msg2 = random_message(&mut seq, &topics); - - // Decompress the raw message and calculate the message id. - // Transform the inbound message - let message1 = &gs.data_transform.inbound_transform(msg1.clone()).unwrap(); - - // Transform the inbound message - let message2 = &gs.data_transform.inbound_transform(msg2.clone()).unwrap(); - - first_messages.push(msg1.clone()); - second_messages.push(msg2.clone()); - gs.handle_ihave( - peer, - vec![( - topics[0].clone(), - vec![config.message_id(message1), config.message_id(message2)], - )], - ); - } - - // the peers send us all the first message ids in time - for (index, (peer, _receiver)) in other_peers.iter().enumerate() { - gs.handle_received_message(first_messages[index].clone(), peer); - } - - // now we do a heartbeat no penalization should have been applied yet - gs.heartbeat(); - - for (peer, _receiver) in &other_peers { - assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 0.0); - } - - // receive the first twenty of the other peers then send their response - for (index, (peer, _receiver)) in other_peers.iter().enumerate().take(20) { - gs.handle_received_message(second_messages[index].clone(), peer); - } - - // sleep for the promise duration - sleep(Duration::from_secs(4)); - - // now we do a heartbeat to apply penalization - gs.heartbeat(); - - // now we get the second messages from the last 80 peers. - for (index, (peer, _receiver)) in other_peers.iter().enumerate() { - if index > 19 { - gs.handle_received_message(second_messages[index].clone(), peer); - } - } - - // no further penalizations should get applied - gs.heartbeat(); - - // Only the last 80 peers should be penalized for not responding in time - let mut not_penalized = 0; - let mut single_penalized = 0; - let mut double_penalized = 0; - - for (i, (peer, _receiver)) in other_peers.iter().enumerate() { - let score = gs.peer_score.as_ref().unwrap().0.score(peer); - if score == 0.0 { - not_penalized += 1; - } else if score == -1.0 { - assert!(i > 9); - single_penalized += 1; - } else if score == -4.0 { - assert!(i > 9); - double_penalized += 1 - } else { - println!("{peer}"); - println!("{score}"); - panic!("Invalid score of peer"); - } - } - - assert_eq!(not_penalized, 20); - assert_eq!(single_penalized, 80); - assert_eq!(double_penalized, 0); -} - -#[test] -fn test_publish_to_floodsub_peers_without_flood_publish() { - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - let (mut gs, _, mut receivers, topics) = inject_nodes1() - .peer_no(config.mesh_n_low() - 1) - .topics(vec!["test".into()]) - .to_subscribe(false) - .gs_config(config) - .create_network(); - - //add two floodsub peer, one explicit, one implicit - let (p1, receiver1) = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - receivers.insert(p1, receiver1); - - let (p2, receiver2) = - add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - receivers.insert(p2, receiver2); - - //p1 and p2 are not in the mesh - assert!(!gs.mesh[&topics[0]].contains(&p1) && !gs.mesh[&topics[0]].contains(&p2)); - - //publish a message - let publish_data = vec![0; 42]; - gs.publish(Topic::new("test"), publish_data).unwrap(); - - // Collect publish messages to floodsub peers - let publishes = receivers - .into_iter() - .fold(0, |mut collected_publish, (peer_id, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if matches!(priority.try_recv(), - Ok(RpcOut::Publish{..}) if peer_id == p1 || peer_id == p2) - { - collected_publish += 1; - } - } - collected_publish - }); - - assert_eq!( - publishes, 2, - "Should send a publish message to all floodsub peers" - ); -} - -#[test] -fn test_do_not_use_floodsub_in_fanout() { - let config = ConfigBuilder::default() - .flood_publish(false) - .build() - .unwrap(); - let (mut gs, _, mut receivers, _) = inject_nodes1() - .peer_no(config.mesh_n_low() - 1) - .topics(Vec::new()) - .to_subscribe(false) - .gs_config(config) - .create_network(); - - let topic = Topic::new("test"); - let topics = vec![topic.hash()]; - - //add two floodsub peer, one explicit, one implicit - let (p1, receiver1) = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - - receivers.insert(p1, receiver1); - let (p2, receiver2) = - add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - - receivers.insert(p2, receiver2); - //publish a message - let publish_data = vec![0; 42]; - gs.publish(Topic::new("test"), publish_data).unwrap(); - - // Collect publish messages to floodsub peers - let publishes = receivers - .into_iter() - .fold(0, |mut collected_publish, (peer_id, c)| { - let priority = c.priority.into_inner(); - while !priority.is_empty() { - if matches!(priority.try_recv(), - Ok(RpcOut::Publish{..}) if peer_id == p1 || peer_id == p2) - { - collected_publish += 1; - } - } - collected_publish - }); - - assert_eq!( - publishes, 2, - "Should send a publish message to all floodsub peers" - ); - - assert!( - !gs.fanout[&topics[0]].contains(&p1) && !gs.fanout[&topics[0]].contains(&p2), - "Floodsub peers are not allowed in fanout" - ); -} - -#[test] -fn test_dont_add_floodsub_peers_to_mesh_on_join() { - let (mut gs, _, _, _) = inject_nodes1() - .peer_no(0) - .topics(Vec::new()) - .to_subscribe(false) - .create_network(); - - let topic = Topic::new("test"); - let topics = vec![topic.hash()]; - - //add two floodsub peer, one explicit, one implicit - let _p1 = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - let _p2 = add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - - gs.join(&topics[0]); - - assert!( - gs.mesh[&topics[0]].is_empty(), - "Floodsub peers should not get added to mesh" - ); -} - -#[test] -fn test_dont_send_px_to_old_gossipsub_peers() { - let (mut gs, _, receivers, topics) = inject_nodes1() - .peer_no(0) - .topics(vec!["test".into()]) - .to_subscribe(false) - .create_network(); - - //add an old gossipsub peer - let (p1, _receiver1) = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Gossipsub), - ); - - //prune the peer - gs.send_graft_prune( - HashMap::new(), - vec![(p1, topics.clone())].into_iter().collect(), - HashSet::new(), - ); - - //check that prune does not contain px - let (control_msgs, _) = count_control_msgs(receivers, |_, m| match m { - RpcOut::Prune(Prune { peers: px, .. }) => !px.is_empty(), - _ => false, - }); - assert_eq!(control_msgs, 0, "Should not send px to floodsub peers"); -} - -#[test] -fn test_dont_send_floodsub_peers_in_px() { - //build mesh with one peer - let (mut gs, peers, receivers, topics) = inject_nodes1() - .peer_no(1) - .topics(vec!["test".into()]) - .to_subscribe(true) - .create_network(); - - //add two floodsub peers - let _p1 = add_peer_with_addr_and_kind( - &mut gs, - &topics, - false, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - let _p2 = add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None); - - //prune only mesh node - gs.send_graft_prune( - HashMap::new(), - vec![(peers[0], topics.clone())].into_iter().collect(), - HashSet::new(), - ); - - //check that px in prune message is empty - let (control_msgs, _) = count_control_msgs(receivers, |_, m| match m { - RpcOut::Prune(Prune { peers: px, .. }) => !px.is_empty(), - _ => false, - }); - assert_eq!(control_msgs, 0, "Should not include floodsub peers in px"); -} - -#[test] -fn test_dont_add_floodsub_peers_to_mesh_in_heartbeat() { - let (mut gs, _, _, topics) = inject_nodes1() - .peer_no(0) - .topics(vec!["test".into()]) - .to_subscribe(false) - .create_network(); - - //add two floodsub peer, one explicit, one implicit - let _p1 = add_peer_with_addr_and_kind( - &mut gs, - &topics, - true, - false, - Multiaddr::empty(), - Some(PeerKind::Floodsub), - ); - let _p2 = add_peer_with_addr_and_kind(&mut gs, &topics, true, false, Multiaddr::empty(), None); - - gs.heartbeat(); - - assert!( - gs.mesh[&topics[0]].is_empty(), - "Floodsub peers should not get added to mesh" - ); -} - -// Some very basic test of public api methods. -#[test] -fn test_public_api() { - let (gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(4) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .create_network(); - let peers = peers.into_iter().collect::>(); - - assert_eq!( - gs.topics().cloned().collect::>(), - topic_hashes, - "Expected topics to match registered topic." - ); - - assert_eq!( - gs.mesh_peers(&TopicHash::from_raw("topic1")) - .cloned() - .collect::>(), - peers, - "Expected peers for a registered topic to contain all peers." - ); - - assert_eq!( - gs.all_mesh_peers().cloned().collect::>(), - peers, - "Expected all_peers to contain all peers." - ); -} - -#[test] -fn test_subscribe_to_invalid_topic() { - let t1 = Topic::new("t1"); - let t2 = Topic::new("t2"); - let (mut gs, _, _, _) = inject_nodes::() - .subscription_filter(WhitelistSubscriptionFilter( - vec![t1.hash()].into_iter().collect(), - )) - .to_subscribe(false) - .create_network(); - - assert!(gs.subscribe(&t1).is_ok()); - assert!(gs.subscribe(&t2).is_err()); -} - -#[test] -fn test_subscribe_and_graft_with_negative_score() { - //simulate a communication between two gossipsub instances - let (mut gs1, _, _, topic_hashes) = inject_nodes1() - .topics(vec!["test".into()]) - .scoring(Some(( - PeerScoreParams::default(), - PeerScoreThresholds::default(), - ))) - .create_network(); - - let (mut gs2, _, receivers, _) = inject_nodes1().create_network(); - - let connection_id = ConnectionId::new_unchecked(0); - - let topic = Topic::new("test"); - - let (p2, _receiver1) = add_peer(&mut gs1, &Vec::new(), true, false); - let (p1, _receiver2) = add_peer(&mut gs2, &topic_hashes, false, false); - - //add penalty to peer p2 - gs1.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1); - - let original_score = gs1.peer_score.as_ref().unwrap().0.score(&p2); - - //subscribe to topic in gs2 - gs2.subscribe(&topic).unwrap(); - - let forward_messages_to_p1 = |gs1: &mut Behaviour<_, _>, - p1: PeerId, - p2: PeerId, - connection_id: ConnectionId, - receivers: HashMap| - -> HashMap { - let new_receivers = HashMap::new(); - for (peer_id, receiver) in receivers.into_iter() { - let non_priority = receiver.non_priority.into_inner(); - match non_priority.try_recv() { - Ok(rpc) if peer_id == p1 => { - gs1.on_connection_handler_event( - p2, - connection_id, - HandlerEvent::Message { - rpc: proto_to_message(&rpc.into_protobuf()), - invalid_messages: vec![], - }, - ); - } - _ => {} - } - } - new_receivers - }; - - //forward the subscribe message - let receivers = forward_messages_to_p1(&mut gs1, p1, p2, connection_id, receivers); - - //heartbeats on both - gs1.heartbeat(); - gs2.heartbeat(); - - //forward messages again - forward_messages_to_p1(&mut gs1, p1, p2, connection_id, receivers); - - //nobody got penalized - assert!(gs1.peer_score.as_ref().unwrap().0.score(&p2) >= original_score); -} - -#[test] -/// Test nodes that send grafts without subscriptions. -fn test_graft_without_subscribe() { - // The node should: - // - Create an empty vector in mesh[topic] - // - Send subscription request to all peers - // - run JOIN(topic) - - let topic = String::from("test_subscribe"); - let subscribe_topic = vec![topic.clone()]; - let subscribe_topic_hash = vec![Topic::new(topic.clone()).hash()]; - let (mut gs, peers, _, topic_hashes) = inject_nodes1() - .peer_no(1) - .topics(subscribe_topic) - .to_subscribe(false) - .create_network(); - - assert!( - gs.mesh.contains_key(&topic_hashes[0]), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // The node sends a graft for the subscribe topic. - gs.handle_graft(&peers[0], subscribe_topic_hash); - - // The node disconnects - disconnect_peer(&mut gs, &peers[0]); - - // We unsubscribe from the topic. - let _ = gs.unsubscribe(&Topic::new(topic)); -} - -/// Test that a node sends IDONTWANT messages to the mesh peers -/// that run Gossipsub v1.2. -#[test] -fn sends_idontwant() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(5) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![12u8; 1024], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - - gs.handle_received_message(message.clone(), &local_id); - assert_eq!( - receivers - .into_iter() - .fold(0, |mut idontwants, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::IDontWant(_)) = non_priority.try_recv() { - assert_ne!(peer_id, peers[1]); - idontwants += 1; - } - } - idontwants - }), - 3, - "IDONTWANT was not sent" - ); -} - -#[test] -fn doesnt_sends_idontwant_for_lower_message_size() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(5) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![12], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - - gs.handle_received_message(message.clone(), &local_id); - assert_eq!( - receivers - .into_iter() - .fold(0, |mut idontwants, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::IDontWant(_)) = non_priority.try_recv() { - assert_ne!(peer_id, peers[1]); - idontwants += 1; - } - } - idontwants - }), - 0, - "IDONTWANT was sent" - ); -} - -/// Test that a node doesn't send IDONTWANT messages to the mesh peers -/// that don't run Gossipsub v1.2. -#[test] -fn doesnt_send_idontwant() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(5) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_1) - .create_network(); - - let local_id = PeerId::random(); - - let message = RawMessage { - source: Some(peers[1]), - data: vec![12], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - - gs.handle_received_message(message.clone(), &local_id); - assert_eq!( - receivers - .into_iter() - .fold(0, |mut idontwants, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if matches!(non_priority.try_recv(), Ok(RpcOut::IDontWant(_)) if peer_id != peers[1]) { - idontwants += 1; - } - } - idontwants - }), - 0, - "IDONTWANT were sent" - ); -} - -/// Test that a node doesn't forward a messages to the mesh peers -/// that sent IDONTWANT. -#[test] -fn doesnt_forward_idontwant() { - let (mut gs, peers, receivers, topic_hashes) = inject_nodes1() - .peer_no(4) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let local_id = PeerId::random(); - - let raw_message = RawMessage { - source: Some(peers[1]), - data: vec![12], - sequence_number: Some(0), - topic: topic_hashes[0].clone(), - signature: None, - key: None, - validated: true, - }; - let message = gs - .data_transform - .inbound_transform(raw_message.clone()) - .unwrap(); - let message_id = gs.config.message_id(&message); - let peer = gs.connected_peers.get_mut(&peers[2]).unwrap(); - peer.dont_send_received.insert(message_id, Instant::now()); - - gs.handle_received_message(raw_message.clone(), &local_id); - assert_eq!( - receivers.into_iter().fold(0, |mut fwds, (peer_id, c)| { - let non_priority = c.non_priority.into_inner(); - while !non_priority.is_empty() { - if let Ok(RpcOut::Forward { .. }) = non_priority.try_recv() { - assert_ne!(peer_id, peers[2]); - fwds += 1; - } - } - fwds - }), - 2, - "IDONTWANT was not sent" - ); -} - -/// Test that a node parses an -/// IDONTWANT message to the respective peer. -#[test] -fn parses_idontwant() { - let (mut gs, peers, _receivers, _topic_hashes) = inject_nodes1() - .peer_no(2) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let message_id = MessageId::new(&[0, 1, 2, 3]); - let rpc = Rpc { - messages: vec![], - subscriptions: vec![], - control_msgs: vec![ControlAction::IDontWant(IDontWant { - message_ids: vec![message_id.clone()], - })], - }; - gs.on_connection_handler_event( - peers[1], - ConnectionId::new_unchecked(0), - HandlerEvent::Message { - rpc, - invalid_messages: vec![], - }, - ); - let peer = gs.connected_peers.get_mut(&peers[1]).unwrap(); - assert!(peer.dont_send_received.get(&message_id).is_some()); -} - -/// Test that a node clears stale IDONTWANT messages. -#[test] -fn clear_stale_idontwant() { - let (mut gs, peers, _receivers, _topic_hashes) = inject_nodes1() - .peer_no(4) - .topics(vec![String::from("topic1")]) - .to_subscribe(true) - .gs_config(Config::default()) - .explicit(1) - .peer_kind(PeerKind::Gossipsubv1_2) - .create_network(); - - let peer = gs.connected_peers.get_mut(&peers[2]).unwrap(); - peer.dont_send_received - .insert(MessageId::new(&[1, 2, 3, 4]), Instant::now()); - std::thread::sleep(Duration::from_secs(3)); - gs.heartbeat(); - let peer = gs.connected_peers.get_mut(&peers[2]).unwrap(); - assert!(peer.dont_send_received.is_empty()); -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/config.rs b/beacon_node/lighthouse_network/gossipsub/src/config.rs deleted file mode 100644 index eb8dd432a3..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/config.rs +++ /dev/null @@ -1,1051 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use std::borrow::Cow; -use std::sync::Arc; -use std::time::Duration; - -use super::error::ConfigBuilderError; -use super::protocol::{ProtocolConfig, ProtocolId, FLOODSUB_PROTOCOL}; -use super::types::{Message, MessageId, PeerKind}; - -use libp2p::identity::PeerId; -use libp2p::swarm::StreamProtocol; - -/// The types of message validation that can be employed by gossipsub. -#[derive(Debug, Clone)] -pub enum ValidationMode { - /// This is the default setting. This requires the message author to be a valid [`PeerId`] and to - /// be present as well as the sequence number. All messages must have valid signatures. - /// - /// NOTE: This setting will reject messages from nodes using - /// [`crate::behaviour::MessageAuthenticity::Anonymous`] and all messages that do not have - /// signatures. - Strict, - /// This setting permits messages that have no author, sequence number or signature. If any of - /// these fields exist in the message these are validated. - Permissive, - /// This setting requires the author, sequence number and signature fields of a message to be - /// empty. Any message that contains these fields is considered invalid. - Anonymous, - /// This setting does not check the author, sequence number or signature fields of incoming - /// messages. If these fields contain data, they are simply ignored. - /// - /// NOTE: This setting will consider messages with invalid signatures as valid messages. - None, -} - -/// Selector for custom Protocol Id -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Version { - V1_0, - V1_1, -} - -/// Configuration parameters that define the performance of the gossipsub network. -#[derive(Clone)] -pub struct Config { - protocol: ProtocolConfig, - history_length: usize, - history_gossip: usize, - mesh_n: usize, - mesh_n_low: usize, - mesh_n_high: usize, - retain_scores: usize, - gossip_lazy: usize, - gossip_factor: f64, - heartbeat_initial_delay: Duration, - heartbeat_interval: Duration, - fanout_ttl: Duration, - check_explicit_peers_ticks: u64, - duplicate_cache_time: Duration, - validate_messages: bool, - message_id_fn: Arc MessageId + Send + Sync + 'static>, - allow_self_origin: bool, - do_px: bool, - prune_peers: usize, - prune_backoff: Duration, - unsubscribe_backoff: Duration, - backoff_slack: u32, - flood_publish: bool, - graft_flood_threshold: Duration, - mesh_outbound_min: usize, - opportunistic_graft_ticks: u64, - opportunistic_graft_peers: usize, - gossip_retransimission: u32, - max_messages_per_rpc: Option, - max_ihave_length: usize, - max_ihave_messages: usize, - iwant_followup_time: Duration, - published_message_ids_cache_time: Duration, - connection_handler_queue_len: usize, - connection_handler_publish_duration: Duration, - connection_handler_forward_duration: Duration, - idontwant_message_size_threshold: usize, -} - -impl Config { - pub(crate) fn protocol_config(&self) -> ProtocolConfig { - self.protocol.clone() - } - - // Overlay network parameters. - /// Number of heartbeats to keep in the `memcache` (default is 5). - pub fn history_length(&self) -> usize { - self.history_length - } - - /// Number of past heartbeats to gossip about (default is 3). - pub fn history_gossip(&self) -> usize { - self.history_gossip - } - - /// Target number of peers for the mesh network (D in the spec, default is 6). - pub fn mesh_n(&self) -> usize { - self.mesh_n - } - - /// Minimum number of peers in mesh network before adding more (D_lo in the spec, default is 5). - pub fn mesh_n_low(&self) -> usize { - self.mesh_n_low - } - - /// Maximum number of peers in mesh network before removing some (D_high in the spec, default - /// is 12). - pub fn mesh_n_high(&self) -> usize { - self.mesh_n_high - } - - /// Affects how peers are selected when pruning a mesh due to over subscription. - /// - /// At least `retain_scores` of the retained peers will be high-scoring, while the remainder are - /// chosen randomly (D_score in the spec, default is 4). - pub fn retain_scores(&self) -> usize { - self.retain_scores - } - - /// Minimum number of peers to emit gossip to during a heartbeat (D_lazy in the spec, - /// default is 6). - pub fn gossip_lazy(&self) -> usize { - self.gossip_lazy - } - - /// Affects how many peers we will emit gossip to at each heartbeat. - /// - /// We will send gossip to `gossip_factor * (total number of non-mesh peers)`, or - /// `gossip_lazy`, whichever is greater. The default is 0.25. - pub fn gossip_factor(&self) -> f64 { - self.gossip_factor - } - - /// Initial delay in each heartbeat (default is 5 seconds). - pub fn heartbeat_initial_delay(&self) -> Duration { - self.heartbeat_initial_delay - } - - /// Time between each heartbeat (default is 1 second). - pub fn heartbeat_interval(&self) -> Duration { - self.heartbeat_interval - } - - /// Time to live for fanout peers (default is 60 seconds). - pub fn fanout_ttl(&self) -> Duration { - self.fanout_ttl - } - - /// The number of heartbeat ticks until we recheck the connection to explicit peers and - /// reconnecting if necessary (default 300). - pub fn check_explicit_peers_ticks(&self) -> u64 { - self.check_explicit_peers_ticks - } - - /// The maximum byte size for each gossipsub RPC (default is 65536 bytes). - /// - /// This represents the maximum size of the entire protobuf payload. It must be at least - /// large enough to support basic control messages. If Peer eXchange is enabled, this - /// must be large enough to transmit the desired peer information on pruning. It must be at - /// least 100 bytes. Default is 65536 bytes. - pub fn max_transmit_size(&self) -> usize { - self.protocol.max_transmit_size - } - - /// Duplicates are prevented by storing message id's of known messages in an LRU time cache. - /// This settings sets the time period that messages are stored in the cache. Duplicates can be - /// received if duplicate messages are sent at a time greater than this setting apart. The - /// default is 1 minute. - pub fn duplicate_cache_time(&self) -> Duration { - self.duplicate_cache_time - } - - /// When set to `true`, prevents automatic forwarding of all received messages. This setting - /// allows a user to validate the messages before propagating them to their peers. If set to - /// true, the user must manually call [`crate::Behaviour::report_message_validation_result()`] - /// on the behaviour to forward message once validated (default is `false`). - /// The default is `false`. - pub fn validate_messages(&self) -> bool { - self.validate_messages - } - - /// Determines the level of validation used when receiving messages. See [`ValidationMode`] - /// for the available types. The default is ValidationMode::Strict. - pub fn validation_mode(&self) -> &ValidationMode { - &self.protocol.validation_mode - } - - /// A user-defined function allowing the user to specify the message id of a gossipsub message. - /// The default value is to concatenate the source peer id with a sequence number. Setting this - /// parameter allows the user to address packets arbitrarily. One example is content based - /// addressing, where this function may be set to `hash(message)`. This would prevent messages - /// of the same content from being duplicated. - /// - /// The function takes a [`Message`] as input and outputs a String to be interpreted as - /// the message id. - pub fn message_id(&self, message: &Message) -> MessageId { - (self.message_id_fn)(message) - } - - /// By default, gossipsub will reject messages that are sent to us that have the same message - /// source as we have specified locally. Enabling this, allows these messages and prevents - /// penalizing the peer that sent us the message. Default is false. - pub fn allow_self_origin(&self) -> bool { - self.allow_self_origin - } - - /// Whether Peer eXchange is enabled; this should be enabled in bootstrappers and other well - /// connected/trusted nodes. The default is false. - /// - /// Note: Peer exchange is not implemented today, see - /// . - pub fn do_px(&self) -> bool { - self.do_px - } - - /// Controls the number of peers to include in prune Peer eXchange. - /// When we prune a peer that's eligible for PX (has a good score, etc), we will try to - /// send them signed peer records for up to `prune_peers` other peers that we - /// know of. It is recommended that this value is larger than `mesh_n_high` so that the pruned - /// peer can reliably form a full mesh. The default is typically 16 however until signed - /// records are spec'd this is disabled and set to 0. - pub fn prune_peers(&self) -> usize { - self.prune_peers - } - - /// Controls the backoff time for pruned peers. This is how long - /// a peer must wait before attempting to graft into our mesh again after being pruned. - /// When pruning a peer, we send them our value of `prune_backoff` so they know - /// the minimum time to wait. Peers running older versions may not send a backoff time, - /// so if we receive a prune message without one, we will wait at least `prune_backoff` - /// before attempting to re-graft. The default is one minute. - pub fn prune_backoff(&self) -> Duration { - self.prune_backoff - } - - /// Controls the backoff time when unsubscribing from a topic. - /// - /// This is how long to wait before resubscribing to the topic. A short backoff period in case - /// of an unsubscribe event allows reaching a healthy mesh in a more timely manner. The default - /// is 10 seconds. - pub fn unsubscribe_backoff(&self) -> Duration { - self.unsubscribe_backoff - } - - /// Number of heartbeat slots considered as slack for backoffs. This gurantees that we wait - /// at least backoff_slack heartbeats after a backoff is over before we try to graft. This - /// solves problems occuring through high latencies. In particular if - /// `backoff_slack * heartbeat_interval` is longer than any latencies between processing - /// prunes on our side and processing prunes on the receiving side this guarantees that we - /// get not punished for too early grafting. The default is 1. - pub fn backoff_slack(&self) -> u32 { - self.backoff_slack - } - - /// Whether to do flood publishing or not. If enabled newly created messages will always be - /// sent to all peers that are subscribed to the topic and have a good enough score. - /// The default is true. - pub fn flood_publish(&self) -> bool { - self.flood_publish - } - - /// If a GRAFT comes before `graft_flood_threshold` has elapsed since the last PRUNE, - /// then there is an extra score penalty applied to the peer through P7. - pub fn graft_flood_threshold(&self) -> Duration { - self.graft_flood_threshold - } - - /// Minimum number of outbound peers in the mesh network before adding more (D_out in the spec). - /// This value must be smaller or equal than `mesh_n / 2` and smaller than `mesh_n_low`. - /// The default is 2. - pub fn mesh_outbound_min(&self) -> usize { - self.mesh_outbound_min - } - - /// Number of heartbeat ticks that specifcy the interval in which opportunistic grafting is - /// applied. Every `opportunistic_graft_ticks` we will attempt to select some high-scoring mesh - /// peers to replace lower-scoring ones, if the median score of our mesh peers falls below a - /// threshold (see ). - /// The default is 60. - pub fn opportunistic_graft_ticks(&self) -> u64 { - self.opportunistic_graft_ticks - } - - /// Controls how many times we will allow a peer to request the same message id through IWANT - /// gossip before we start ignoring them. This is designed to prevent peers from spamming us - /// with requests and wasting our resources. The default is 3. - pub fn gossip_retransimission(&self) -> u32 { - self.gossip_retransimission - } - - /// The maximum number of new peers to graft to during opportunistic grafting. The default is 2. - pub fn opportunistic_graft_peers(&self) -> usize { - self.opportunistic_graft_peers - } - - /// The maximum number of messages we will process in a given RPC. If this is unset, there is - /// no limit. The default is None. - pub fn max_messages_per_rpc(&self) -> Option { - self.max_messages_per_rpc - } - - /// The maximum number of messages to include in an IHAVE message. - /// Also controls the maximum number of IHAVE ids we will accept and request with IWANT from a - /// peer within a heartbeat, to protect from IHAVE floods. You should adjust this value from the - /// default if your system is pushing more than 5000 messages in GossipSubHistoryGossip - /// heartbeats; with the defaults this is 1666 messages/s. The default is 5000. - pub fn max_ihave_length(&self) -> usize { - self.max_ihave_length - } - - /// GossipSubMaxIHaveMessages is the maximum number of IHAVE messages to accept from a peer - /// within a heartbeat. - pub fn max_ihave_messages(&self) -> usize { - self.max_ihave_messages - } - - /// Time to wait for a message requested through IWANT following an IHAVE advertisement. - /// If the message is not received within this window, a broken promise is declared and - /// the router may apply behavioural penalties. The default is 3 seconds. - pub fn iwant_followup_time(&self) -> Duration { - self.iwant_followup_time - } - - /// Enable support for flooodsub peers. Default false. - pub fn support_floodsub(&self) -> bool { - self.protocol.protocol_ids.contains(&FLOODSUB_PROTOCOL) - } - - /// Published message ids time cache duration. The default is 10 seconds. - pub fn published_message_ids_cache_time(&self) -> Duration { - self.published_message_ids_cache_time - } - - /// The max number of messages a `ConnectionHandler` can buffer. The default is 5000. - pub fn connection_handler_queue_len(&self) -> usize { - self.connection_handler_queue_len - } - - /// The duration a message to be published can wait to be sent before it is abandoned. The - /// default is 5 seconds. - pub fn publish_queue_duration(&self) -> Duration { - self.connection_handler_publish_duration - } - - /// The duration a message to be forwarded can wait to be sent before it is abandoned. The - /// default is 1s. - pub fn forward_queue_duration(&self) -> Duration { - self.connection_handler_forward_duration - } - - // The message size threshold for which IDONTWANT messages are sent. - // Sending IDONTWANT messages for small messages can have a negative effect to the overall - // traffic and CPU load. This acts as a lower bound cutoff for the message size to which - // IDONTWANT won't be sent to peers. Only works if the peers support Gossipsub1.2 - // (see https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.2.md#idontwant-message) - // default is 1kB - pub fn idontwant_message_size_threshold(&self) -> usize { - self.idontwant_message_size_threshold - } -} - -impl Default for Config { - fn default() -> Self { - // use ConfigBuilder to also validate defaults - ConfigBuilder::default() - .build() - .expect("Default config parameters should be valid parameters") - } -} - -/// The builder struct for constructing a gossipsub configuration. -pub struct ConfigBuilder { - config: Config, - invalid_protocol: bool, // This is a bit of a hack to only expose one error to the user. -} - -impl Default for ConfigBuilder { - fn default() -> Self { - ConfigBuilder { - config: Config { - protocol: ProtocolConfig::default(), - history_length: 5, - history_gossip: 3, - mesh_n: 6, - mesh_n_low: 5, - mesh_n_high: 12, - retain_scores: 4, - gossip_lazy: 6, // default to mesh_n - gossip_factor: 0.25, - heartbeat_initial_delay: Duration::from_secs(5), - heartbeat_interval: Duration::from_secs(1), - fanout_ttl: Duration::from_secs(60), - check_explicit_peers_ticks: 300, - duplicate_cache_time: Duration::from_secs(60), - validate_messages: false, - message_id_fn: Arc::new(|message| { - // default message id is: source + sequence number - // NOTE: If either the peer_id or source is not provided, we set to 0; - let mut source_string = if let Some(peer_id) = message.source.as_ref() { - peer_id.to_base58() - } else { - PeerId::from_bytes(&[0, 1, 0]) - .expect("Valid peer id") - .to_base58() - }; - source_string - .push_str(&message.sequence_number.unwrap_or_default().to_string()); - MessageId::from(source_string) - }), - allow_self_origin: false, - do_px: false, - prune_peers: 0, // NOTE: Increasing this currently has little effect until Signed records are implemented. - prune_backoff: Duration::from_secs(60), - unsubscribe_backoff: Duration::from_secs(10), - backoff_slack: 1, - flood_publish: true, - graft_flood_threshold: Duration::from_secs(10), - mesh_outbound_min: 2, - opportunistic_graft_ticks: 60, - opportunistic_graft_peers: 2, - gossip_retransimission: 3, - max_messages_per_rpc: None, - max_ihave_length: 5000, - max_ihave_messages: 10, - iwant_followup_time: Duration::from_secs(3), - published_message_ids_cache_time: Duration::from_secs(10), - connection_handler_queue_len: 5000, - connection_handler_publish_duration: Duration::from_secs(5), - connection_handler_forward_duration: Duration::from_millis(1000), - idontwant_message_size_threshold: 1000, - }, - invalid_protocol: false, - } - } -} - -impl From for ConfigBuilder { - fn from(config: Config) -> Self { - ConfigBuilder { - config, - invalid_protocol: false, - } - } -} - -impl ConfigBuilder { - /// The protocol id prefix to negotiate this protocol (default is `/meshsub/1.1.0` and `/meshsub/1.0.0`). - pub fn protocol_id_prefix( - &mut self, - protocol_id_prefix: impl Into>, - ) -> &mut Self { - let cow = protocol_id_prefix.into(); - - match ( - StreamProtocol::try_from_owned(format!("{}/1.1.0", cow)), - StreamProtocol::try_from_owned(format!("{}/1.0.0", cow)), - ) { - (Ok(p1), Ok(p2)) => { - self.config.protocol.protocol_ids = vec![ - ProtocolId { - protocol: p1, - kind: PeerKind::Gossipsubv1_1, - }, - ProtocolId { - protocol: p2, - kind: PeerKind::Gossipsub, - }, - ] - } - _ => { - self.invalid_protocol = true; - } - } - - self - } - - /// The full protocol id to negotiate this protocol (does not append `/1.0.0` or `/1.1.0`). - pub fn protocol_id( - &mut self, - protocol_id: impl Into>, - custom_id_version: Version, - ) -> &mut Self { - let cow = protocol_id.into(); - - match StreamProtocol::try_from_owned(cow.to_string()) { - Ok(protocol) => { - self.config.protocol.protocol_ids = vec![ProtocolId { - protocol, - kind: match custom_id_version { - Version::V1_1 => PeerKind::Gossipsubv1_1, - Version::V1_0 => PeerKind::Gossipsub, - }, - }] - } - _ => { - self.invalid_protocol = true; - } - } - - self - } - - /// Number of heartbeats to keep in the `memcache` (default is 5). - pub fn history_length(&mut self, history_length: usize) -> &mut Self { - self.config.history_length = history_length; - self - } - - /// Number of past heartbeats to gossip about (default is 3). - pub fn history_gossip(&mut self, history_gossip: usize) -> &mut Self { - self.config.history_gossip = history_gossip; - self - } - - /// Target number of peers for the mesh network (D in the spec, default is 6). - pub fn mesh_n(&mut self, mesh_n: usize) -> &mut Self { - self.config.mesh_n = mesh_n; - self - } - - /// Minimum number of peers in mesh network before adding more (D_lo in the spec, default is 4). - pub fn mesh_n_low(&mut self, mesh_n_low: usize) -> &mut Self { - self.config.mesh_n_low = mesh_n_low; - self - } - - /// Maximum number of peers in mesh network before removing some (D_high in the spec, default - /// is 12). - pub fn mesh_n_high(&mut self, mesh_n_high: usize) -> &mut Self { - self.config.mesh_n_high = mesh_n_high; - self - } - - /// Affects how peers are selected when pruning a mesh due to over subscription. - /// - /// At least [`Self::retain_scores`] of the retained peers will be high-scoring, while the remainder are - /// chosen randomly (D_score in the spec, default is 4). - pub fn retain_scores(&mut self, retain_scores: usize) -> &mut Self { - self.config.retain_scores = retain_scores; - self - } - - /// Minimum number of peers to emit gossip to during a heartbeat (D_lazy in the spec, - /// default is 6). - pub fn gossip_lazy(&mut self, gossip_lazy: usize) -> &mut Self { - self.config.gossip_lazy = gossip_lazy; - self - } - - /// Affects how many peers we will emit gossip to at each heartbeat. - /// - /// We will send gossip to `gossip_factor * (total number of non-mesh peers)`, or - /// `gossip_lazy`, whichever is greater. The default is 0.25. - pub fn gossip_factor(&mut self, gossip_factor: f64) -> &mut Self { - self.config.gossip_factor = gossip_factor; - self - } - - /// Initial delay in each heartbeat (default is 5 seconds). - pub fn heartbeat_initial_delay(&mut self, heartbeat_initial_delay: Duration) -> &mut Self { - self.config.heartbeat_initial_delay = heartbeat_initial_delay; - self - } - - /// Time between each heartbeat (default is 1 second). - pub fn heartbeat_interval(&mut self, heartbeat_interval: Duration) -> &mut Self { - self.config.heartbeat_interval = heartbeat_interval; - self - } - - /// The number of heartbeat ticks until we recheck the connection to explicit peers and - /// reconnecting if necessary (default 300). - pub fn check_explicit_peers_ticks(&mut self, check_explicit_peers_ticks: u64) -> &mut Self { - self.config.check_explicit_peers_ticks = check_explicit_peers_ticks; - self - } - - /// Time to live for fanout peers (default is 60 seconds). - pub fn fanout_ttl(&mut self, fanout_ttl: Duration) -> &mut Self { - self.config.fanout_ttl = fanout_ttl; - self - } - - /// The maximum byte size for each gossip (default is 2048 bytes). - pub fn max_transmit_size(&mut self, max_transmit_size: usize) -> &mut Self { - self.config.protocol.max_transmit_size = max_transmit_size; - self - } - - /// Duplicates are prevented by storing message id's of known messages in an LRU time cache. - /// This settings sets the time period that messages are stored in the cache. Duplicates can be - /// received if duplicate messages are sent at a time greater than this setting apart. The - /// default is 1 minute. - pub fn duplicate_cache_time(&mut self, cache_size: Duration) -> &mut Self { - self.config.duplicate_cache_time = cache_size; - self - } - - /// When set, prevents automatic forwarding of all received messages. This setting - /// allows a user to validate the messages before propagating them to their peers. If set, - /// the user must manually call [`crate::Behaviour::report_message_validation_result()`] on the - /// behaviour to forward a message once validated. - pub fn validate_messages(&mut self) -> &mut Self { - self.config.validate_messages = true; - self - } - - /// Determines the level of validation used when receiving messages. See [`ValidationMode`] - /// for the available types. The default is ValidationMode::Strict. - pub fn validation_mode(&mut self, validation_mode: ValidationMode) -> &mut Self { - self.config.protocol.validation_mode = validation_mode; - self - } - - /// A user-defined function allowing the user to specify the message id of a gossipsub message. - /// The default value is to concatenate the source peer id with a sequence number. Setting this - /// parameter allows the user to address packets arbitrarily. One example is content based - /// addressing, where this function may be set to `hash(message)`. This would prevent messages - /// of the same content from being duplicated. - /// - /// The function takes a [`Message`] as input and outputs a String to be - /// interpreted as the message id. - pub fn message_id_fn(&mut self, id_fn: F) -> &mut Self - where - F: Fn(&Message) -> MessageId + Send + Sync + 'static, - { - self.config.message_id_fn = Arc::new(id_fn); - self - } - - /// Enables Peer eXchange. This should be enabled in bootstrappers and other well - /// connected/trusted nodes. The default is false. - /// - /// Note: Peer exchange is not implemented today, see - /// . - pub fn do_px(&mut self) -> &mut Self { - self.config.do_px = true; - self - } - - /// Controls the number of peers to include in prune Peer eXchange. - /// - /// When we prune a peer that's eligible for PX (has a good score, etc), we will try to - /// send them signed peer records for up to [`Self::prune_peers] other peers that we - /// know of. It is recommended that this value is larger than [`Self::mesh_n_high`] so that the - /// pruned peer can reliably form a full mesh. The default is 16. - pub fn prune_peers(&mut self, prune_peers: usize) -> &mut Self { - self.config.prune_peers = prune_peers; - self - } - - /// Controls the backoff time for pruned peers. This is how long - /// a peer must wait before attempting to graft into our mesh again after being pruned. - /// When pruning a peer, we send them our value of [`Self::prune_backoff`] so they know - /// the minimum time to wait. Peers running older versions may not send a backoff time, - /// so if we receive a prune message without one, we will wait at least [`Self::prune_backoff`] - /// before attempting to re-graft. The default is one minute. - pub fn prune_backoff(&mut self, prune_backoff: Duration) -> &mut Self { - self.config.prune_backoff = prune_backoff; - self - } - - /// Controls the backoff time when unsubscribing from a topic. - /// - /// This is how long to wait before resubscribing to the topic. A short backoff period in case - /// of an unsubscribe event allows reaching a healthy mesh in a more timely manner. The default - /// is 10 seconds. - pub fn unsubscribe_backoff(&mut self, unsubscribe_backoff: u64) -> &mut Self { - self.config.unsubscribe_backoff = Duration::from_secs(unsubscribe_backoff); - self - } - - /// Number of heartbeat slots considered as slack for backoffs. This gurantees that we wait - /// at least backoff_slack heartbeats after a backoff is over before we try to graft. This - /// solves problems occuring through high latencies. In particular if - /// `backoff_slack * heartbeat_interval` is longer than any latencies between processing - /// prunes on our side and processing prunes on the receiving side this guarantees that we - /// get not punished for too early grafting. The default is 1. - pub fn backoff_slack(&mut self, backoff_slack: u32) -> &mut Self { - self.config.backoff_slack = backoff_slack; - self - } - - /// Whether to do flood publishing or not. If enabled newly created messages will always be - /// sent to all peers that are subscribed to the topic and have a good enough score. - /// The default is true. - pub fn flood_publish(&mut self, flood_publish: bool) -> &mut Self { - self.config.flood_publish = flood_publish; - self - } - - /// If a GRAFT comes before `graft_flood_threshold` has elapsed since the last PRUNE, - /// then there is an extra score penalty applied to the peer through P7. - pub fn graft_flood_threshold(&mut self, graft_flood_threshold: Duration) -> &mut Self { - self.config.graft_flood_threshold = graft_flood_threshold; - self - } - - /// Minimum number of outbound peers in the mesh network before adding more (D_out in the spec). - /// This value must be smaller or equal than `mesh_n / 2` and smaller than `mesh_n_low`. - /// The default is 2. - pub fn mesh_outbound_min(&mut self, mesh_outbound_min: usize) -> &mut Self { - self.config.mesh_outbound_min = mesh_outbound_min; - self - } - - /// Number of heartbeat ticks that specifcy the interval in which opportunistic grafting is - /// applied. Every `opportunistic_graft_ticks` we will attempt to select some high-scoring mesh - /// peers to replace lower-scoring ones, if the median score of our mesh peers falls below a - /// threshold (see ). - /// The default is 60. - pub fn opportunistic_graft_ticks(&mut self, opportunistic_graft_ticks: u64) -> &mut Self { - self.config.opportunistic_graft_ticks = opportunistic_graft_ticks; - self - } - - /// Controls how many times we will allow a peer to request the same message id through IWANT - /// gossip before we start ignoring them. This is designed to prevent peers from spamming us - /// with requests and wasting our resources. - pub fn gossip_retransimission(&mut self, gossip_retransimission: u32) -> &mut Self { - self.config.gossip_retransimission = gossip_retransimission; - self - } - - /// The maximum number of new peers to graft to during opportunistic grafting. The default is 2. - pub fn opportunistic_graft_peers(&mut self, opportunistic_graft_peers: usize) -> &mut Self { - self.config.opportunistic_graft_peers = opportunistic_graft_peers; - self - } - - /// The maximum number of messages we will process in a given RPC. If this is unset, there is - /// no limit. The default is None. - pub fn max_messages_per_rpc(&mut self, max: Option) -> &mut Self { - self.config.max_messages_per_rpc = max; - self - } - - /// The maximum number of messages to include in an IHAVE message. - /// Also controls the maximum number of IHAVE ids we will accept and request with IWANT from a - /// peer within a heartbeat, to protect from IHAVE floods. You should adjust this value from the - /// default if your system is pushing more than 5000 messages in GossipSubHistoryGossip - /// heartbeats; with the defaults this is 1666 messages/s. The default is 5000. - pub fn max_ihave_length(&mut self, max_ihave_length: usize) -> &mut Self { - self.config.max_ihave_length = max_ihave_length; - self - } - - /// GossipSubMaxIHaveMessages is the maximum number of IHAVE messages to accept from a peer - /// within a heartbeat. - pub fn max_ihave_messages(&mut self, max_ihave_messages: usize) -> &mut Self { - self.config.max_ihave_messages = max_ihave_messages; - self - } - - /// By default, gossipsub will reject messages that are sent to us that has the same message - /// source as we have specified locally. Enabling this, allows these messages and prevents - /// penalizing the peer that sent us the message. Default is false. - pub fn allow_self_origin(&mut self, allow_self_origin: bool) -> &mut Self { - self.config.allow_self_origin = allow_self_origin; - self - } - - /// Time to wait for a message requested through IWANT following an IHAVE advertisement. - /// If the message is not received within this window, a broken promise is declared and - /// the router may apply behavioural penalties. The default is 3 seconds. - pub fn iwant_followup_time(&mut self, iwant_followup_time: Duration) -> &mut Self { - self.config.iwant_followup_time = iwant_followup_time; - self - } - - /// Enable support for flooodsub peers. - pub fn support_floodsub(&mut self) -> &mut Self { - if self - .config - .protocol - .protocol_ids - .contains(&FLOODSUB_PROTOCOL) - { - return self; - } - - self.config.protocol.protocol_ids.push(FLOODSUB_PROTOCOL); - self - } - - /// Published message ids time cache duration. The default is 10 seconds. - pub fn published_message_ids_cache_time( - &mut self, - published_message_ids_cache_time: Duration, - ) -> &mut Self { - self.config.published_message_ids_cache_time = published_message_ids_cache_time; - self - } - - /// The max number of messages a `ConnectionHandler` can buffer. The default is 5000. - pub fn connection_handler_queue_len(&mut self, len: usize) -> &mut Self { - self.config.connection_handler_queue_len = len; - self - } - - /// The duration a message to be published can wait to be sent before it is abandoned. The - /// default is 5 seconds. - pub fn publish_queue_duration(&mut self, duration: Duration) -> &mut Self { - self.config.connection_handler_publish_duration = duration; - self - } - - /// The duration a message to be forwarded can wait to be sent before it is abandoned. The - /// default is 1s. - pub fn forward_queue_duration(&mut self, duration: Duration) -> &mut Self { - self.config.connection_handler_forward_duration = duration; - self - } - - // The message size threshold for which IDONTWANT messages are sent. - // Sending IDONTWANT messages for small messages can have a negative effect to the overall - // traffic and CPU load. This acts as a lower bound cutoff for the message size to which - // IDONTWANT won't be sent to peers. Only works if the peers support Gossipsub1.2 - // (see https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.2.md#idontwant-message) - // default is 1kB - pub fn idontwant_message_size_threshold(&mut self, size: usize) -> &mut Self { - self.config.idontwant_message_size_threshold = size; - self - } - - /// Constructs a [`Config`] from the given configuration and validates the settings. - pub fn build(&self) -> Result { - // check all constraints on config - - if self.config.protocol.max_transmit_size < 100 { - return Err(ConfigBuilderError::MaxTransmissionSizeTooSmall); - } - - if self.config.history_length < self.config.history_gossip { - return Err(ConfigBuilderError::HistoryLengthTooSmall); - } - - if !(self.config.mesh_outbound_min <= self.config.mesh_n_low - && self.config.mesh_n_low <= self.config.mesh_n - && self.config.mesh_n <= self.config.mesh_n_high) - { - return Err(ConfigBuilderError::MeshParametersInvalid); - } - - if self.config.mesh_outbound_min * 2 > self.config.mesh_n { - return Err(ConfigBuilderError::MeshOutboundInvalid); - } - - if self.config.unsubscribe_backoff.as_millis() == 0 { - return Err(ConfigBuilderError::UnsubscribeBackoffIsZero); - } - - if self.invalid_protocol { - return Err(ConfigBuilderError::InvalidProtocol); - } - - Ok(self.config.clone()) - } -} - -impl std::fmt::Debug for Config { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut builder = f.debug_struct("GossipsubConfig"); - let _ = builder.field("protocol", &self.protocol); - let _ = builder.field("history_length", &self.history_length); - let _ = builder.field("history_gossip", &self.history_gossip); - let _ = builder.field("mesh_n", &self.mesh_n); - let _ = builder.field("mesh_n_low", &self.mesh_n_low); - let _ = builder.field("mesh_n_high", &self.mesh_n_high); - let _ = builder.field("retain_scores", &self.retain_scores); - let _ = builder.field("gossip_lazy", &self.gossip_lazy); - let _ = builder.field("gossip_factor", &self.gossip_factor); - let _ = builder.field("heartbeat_initial_delay", &self.heartbeat_initial_delay); - let _ = builder.field("heartbeat_interval", &self.heartbeat_interval); - let _ = builder.field("fanout_ttl", &self.fanout_ttl); - let _ = builder.field("duplicate_cache_time", &self.duplicate_cache_time); - let _ = builder.field("validate_messages", &self.validate_messages); - let _ = builder.field("allow_self_origin", &self.allow_self_origin); - let _ = builder.field("do_px", &self.do_px); - let _ = builder.field("prune_peers", &self.prune_peers); - let _ = builder.field("prune_backoff", &self.prune_backoff); - let _ = builder.field("backoff_slack", &self.backoff_slack); - let _ = builder.field("flood_publish", &self.flood_publish); - let _ = builder.field("graft_flood_threshold", &self.graft_flood_threshold); - let _ = builder.field("mesh_outbound_min", &self.mesh_outbound_min); - let _ = builder.field("opportunistic_graft_ticks", &self.opportunistic_graft_ticks); - let _ = builder.field("opportunistic_graft_peers", &self.opportunistic_graft_peers); - let _ = builder.field("max_messages_per_rpc", &self.max_messages_per_rpc); - let _ = builder.field("max_ihave_length", &self.max_ihave_length); - let _ = builder.field("max_ihave_messages", &self.max_ihave_messages); - let _ = builder.field("iwant_followup_time", &self.iwant_followup_time); - let _ = builder.field( - "published_message_ids_cache_time", - &self.published_message_ids_cache_time, - ); - let _ = builder.field( - "idontwant_message_size_threhold", - &self.idontwant_message_size_threshold, - ); - builder.finish() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::topic::IdentityHash; - use crate::Topic; - use libp2p::core::UpgradeInfo; - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - - #[test] - fn create_config_with_message_id_as_plain_function() { - let config = ConfigBuilder::default() - .message_id_fn(message_id_plain_function) - .build() - .unwrap(); - - let result = config.message_id(&get_gossipsub_message()); - - assert_eq!(result, get_expected_message_id()); - } - - #[test] - fn create_config_with_message_id_as_closure() { - let config = ConfigBuilder::default() - .message_id_fn(|message: &Message| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - let mut v = s.finish().to_string(); - v.push('e'); - MessageId::from(v) - }) - .build() - .unwrap(); - - let result = config.message_id(&get_gossipsub_message()); - - assert_eq!(result, get_expected_message_id()); - } - - #[test] - fn create_config_with_message_id_as_closure_with_variable_capture() { - let captured: char = 'e'; - - let config = ConfigBuilder::default() - .message_id_fn(move |message: &Message| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - let mut v = s.finish().to_string(); - v.push(captured); - MessageId::from(v) - }) - .build() - .unwrap(); - - let result = config.message_id(&get_gossipsub_message()); - - assert_eq!(result, get_expected_message_id()); - } - - #[test] - fn create_config_with_protocol_id_prefix() { - let protocol_config = ConfigBuilder::default() - .protocol_id_prefix("/purple") - .build() - .unwrap() - .protocol_config(); - - let protocol_ids = protocol_config.protocol_info(); - - assert_eq!(protocol_ids.len(), 2); - - assert_eq!( - protocol_ids[0].protocol, - StreamProtocol::new("/purple/1.1.0") - ); - assert_eq!(protocol_ids[0].kind, PeerKind::Gossipsubv1_1); - - assert_eq!( - protocol_ids[1].protocol, - StreamProtocol::new("/purple/1.0.0") - ); - assert_eq!(protocol_ids[1].kind, PeerKind::Gossipsub); - } - - #[test] - fn create_config_with_custom_protocol_id() { - let protocol_config = ConfigBuilder::default() - .protocol_id("/purple", Version::V1_0) - .build() - .unwrap() - .protocol_config(); - - let protocol_ids = protocol_config.protocol_info(); - - assert_eq!(protocol_ids.len(), 1); - - assert_eq!(protocol_ids[0].protocol, "/purple"); - assert_eq!(protocol_ids[0].kind, PeerKind::Gossipsub); - } - - fn get_gossipsub_message() -> Message { - Message { - source: None, - data: vec![12, 34, 56], - sequence_number: None, - topic: Topic::::new("test").hash(), - } - } - - fn get_expected_message_id() -> MessageId { - MessageId::from([ - 49, 55, 56, 51, 56, 52, 49, 51, 52, 51, 52, 55, 51, 51, 53, 52, 54, 54, 52, 49, 101, - ]) - } - - fn message_id_plain_function(message: &Message) -> MessageId { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - let mut v = s.finish().to_string(); - v.push('e'); - MessageId::from(v) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/error.rs b/beacon_node/lighthouse_network/gossipsub/src/error.rs deleted file mode 100644 index df3332bc92..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/error.rs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Error types that can result from gossipsub. - -use libp2p::identity::SigningError; - -/// Error associated with publishing a gossipsub message. -#[derive(Debug)] -pub enum PublishError { - /// This message has already been published. - Duplicate, - /// An error occurred whilst signing the message. - SigningError(SigningError), - /// There were no peers to send this message to. - InsufficientPeers, - /// The overall message was too large. This could be due to excessive topics or an excessive - /// message size. - MessageTooLarge, - /// The compression algorithm failed. - TransformFailed(std::io::Error), - /// Messages could not be sent because all queues for peers were full. The usize represents the - /// number of peers that have full queues. - AllQueuesFull(usize), -} - -impl std::fmt::Display for PublishError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for PublishError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::SigningError(err) => Some(err), - Self::TransformFailed(err) => Some(err), - _ => None, - } - } -} - -/// Error associated with subscribing to a topic. -#[derive(Debug)] -pub enum SubscriptionError { - /// Couldn't publish our subscription - PublishError(PublishError), - /// We are not allowed to subscribe to this topic by the subscription filter - NotAllowed, -} - -impl std::fmt::Display for SubscriptionError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for SubscriptionError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::PublishError(err) => Some(err), - _ => None, - } - } -} - -impl From for PublishError { - fn from(error: SigningError) -> Self { - PublishError::SigningError(error) - } -} - -#[derive(Debug, Clone, Copy)] -pub enum ValidationError { - /// The message has an invalid signature, - InvalidSignature, - /// The sequence number was empty, expected a value. - EmptySequenceNumber, - /// The sequence number was the incorrect size - InvalidSequenceNumber, - /// The PeerId was invalid - InvalidPeerId, - /// Signature existed when validation has been sent to - /// [`crate::behaviour::MessageAuthenticity::Anonymous`]. - SignaturePresent, - /// Sequence number existed when validation has been sent to - /// [`crate::behaviour::MessageAuthenticity::Anonymous`]. - SequenceNumberPresent, - /// Message source existed when validation has been sent to - /// [`crate::behaviour::MessageAuthenticity::Anonymous`]. - MessageSourcePresent, - /// The data transformation failed. - TransformFailed, -} - -impl std::fmt::Display for ValidationError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for ValidationError {} - -impl From for PublishError { - fn from(error: std::io::Error) -> PublishError { - PublishError::TransformFailed(error) - } -} - -/// Error associated with Config building. -#[derive(Debug)] -pub enum ConfigBuilderError { - /// Maximum transmission size is too small. - MaxTransmissionSizeTooSmall, - /// Histroy length less than history gossip length. - HistoryLengthTooSmall, - /// The ineauality doesn't hold mesh_outbound_min <= mesh_n_low <= mesh_n <= mesh_n_high - MeshParametersInvalid, - /// The inequality doesn't hold mesh_outbound_min <= self.config.mesh_n / 2 - MeshOutboundInvalid, - /// unsubscribe_backoff is zero - UnsubscribeBackoffIsZero, - /// Invalid protocol - InvalidProtocol, -} - -impl std::error::Error for ConfigBuilderError {} - -impl std::fmt::Display for ConfigBuilderError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::MaxTransmissionSizeTooSmall => { - write!(f, "Maximum transmission size is too small") - } - Self::HistoryLengthTooSmall => write!(f, "Histroy length less than history gossip length"), - Self::MeshParametersInvalid => write!(f, "The ineauality doesn't hold mesh_outbound_min <= mesh_n_low <= mesh_n <= mesh_n_high"), - Self::MeshOutboundInvalid => write!(f, "The inequality doesn't hold mesh_outbound_min <= self.config.mesh_n / 2"), - Self::UnsubscribeBackoffIsZero => write!(f, "unsubscribe_backoff is zero"), - Self::InvalidProtocol => write!(f, "Invalid protocol"), - } - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/compat.proto b/beacon_node/lighthouse_network/gossipsub/src/generated/compat.proto deleted file mode 100644 index b2753bf7e4..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/compat.proto +++ /dev/null @@ -1,12 +0,0 @@ -syntax = "proto2"; - -package compat.pb; - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - repeated string topic_ids = 4; - optional bytes signature = 5; - optional bytes key = 6; -} \ No newline at end of file diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/compat/mod.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/compat/mod.rs deleted file mode 100644 index aec6164c7e..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/compat/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Automatically generated mod.rs -pub mod pb; diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/compat/pb.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/compat/pb.rs deleted file mode 100644 index fd59c38e2b..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/compat/pb.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Automatically generated rust module for 'compat.proto' file - -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(unused_imports)] -#![allow(unknown_lints)] -#![allow(clippy::all)] -#![cfg_attr(rustfmt, rustfmt_skip)] - - -use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; -use quick_protobuf::sizeofs::*; -use super::super::*; - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct Message { - pub from: Option>, - pub data: Option>, - pub seqno: Option>, - pub topic_ids: Vec, - pub signature: Option>, - pub key: Option>, -} - -impl<'a> MessageRead<'a> for Message { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.from = Some(r.read_bytes(bytes)?.to_owned()), - Ok(18) => msg.data = Some(r.read_bytes(bytes)?.to_owned()), - Ok(26) => msg.seqno = Some(r.read_bytes(bytes)?.to_owned()), - Ok(34) => msg.topic_ids.push(r.read_string(bytes)?.to_owned()), - Ok(42) => msg.signature = Some(r.read_bytes(bytes)?.to_owned()), - Ok(50) => msg.key = Some(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for Message { - fn get_size(&self) -> usize { - 0 - + self.from.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.data.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.seqno.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.topic_ids.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - + self.signature.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.key.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.from { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.data { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.seqno { w.write_with_tag(26, |w| w.write_bytes(&**s))?; } - for s in &self.topic_ids { w.write_with_tag(34, |w| w.write_string(&**s))?; } - if let Some(ref s) = self.signature { w.write_with_tag(42, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.key { w.write_with_tag(50, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/mod.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/mod.rs deleted file mode 100644 index aec6164c7e..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Automatically generated mod.rs -pub mod pb; diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/pb.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/pb.rs deleted file mode 100644 index 24ac80d275..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/gossipsub/pb.rs +++ /dev/null @@ -1,603 +0,0 @@ -// Automatically generated rust module for 'rpc.proto' file - -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(unused_imports)] -#![allow(unknown_lints)] -#![allow(clippy::all)] -#![cfg_attr(rustfmt, rustfmt_skip)] - - -use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; -use quick_protobuf::sizeofs::*; -use super::super::*; - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct RPC { - pub subscriptions: Vec, - pub publish: Vec, - pub control: Option, -} - -impl<'a> MessageRead<'a> for RPC { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.subscriptions.push(r.read_message::(bytes)?), - Ok(18) => msg.publish.push(r.read_message::(bytes)?), - Ok(26) => msg.control = Some(r.read_message::(bytes)?), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for RPC { - fn get_size(&self) -> usize { - 0 - + self.subscriptions.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.publish.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.control.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - for s in &self.subscriptions { w.write_with_tag(10, |w| w.write_message(s))?; } - for s in &self.publish { w.write_with_tag(18, |w| w.write_message(s))?; } - if let Some(ref s) = self.control { w.write_with_tag(26, |w| w.write_message(s))?; } - Ok(()) - } -} - -pub mod mod_RPC { - -use super::*; - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct SubOpts { - pub subscribe: Option, - pub topic_id: Option, -} - -impl<'a> MessageRead<'a> for SubOpts { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(8) => msg.subscribe = Some(r.read_bool(bytes)?), - Ok(18) => msg.topic_id = Some(r.read_string(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for SubOpts { - fn get_size(&self) -> usize { - 0 - + self.subscribe.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) - + self.topic_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.subscribe { w.write_with_tag(8, |w| w.write_bool(*s))?; } - if let Some(ref s) = self.topic_id { w.write_with_tag(18, |w| w.write_string(&**s))?; } - Ok(()) - } -} - -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct Message { - pub from: Option>, - pub data: Option>, - pub seqno: Option>, - pub topic: String, - pub signature: Option>, - pub key: Option>, -} - -impl<'a> MessageRead<'a> for Message { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.from = Some(r.read_bytes(bytes)?.to_owned()), - Ok(18) => msg.data = Some(r.read_bytes(bytes)?.to_owned()), - Ok(26) => msg.seqno = Some(r.read_bytes(bytes)?.to_owned()), - Ok(34) => msg.topic = r.read_string(bytes)?.to_owned(), - Ok(42) => msg.signature = Some(r.read_bytes(bytes)?.to_owned()), - Ok(50) => msg.key = Some(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for Message { - fn get_size(&self) -> usize { - 0 - + self.from.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.data.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.seqno.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + 1 + sizeof_len((&self.topic).len()) - + self.signature.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.key.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.from { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.data { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.seqno { w.write_with_tag(26, |w| w.write_bytes(&**s))?; } - w.write_with_tag(34, |w| w.write_string(&**&self.topic))?; - if let Some(ref s) = self.signature { w.write_with_tag(42, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.key { w.write_with_tag(50, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlMessage { - pub ihave: Vec, - pub iwant: Vec, - pub graft: Vec, - pub prune: Vec, - pub idontwant: Vec, -} - -impl<'a> MessageRead<'a> for ControlMessage { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.ihave.push(r.read_message::(bytes)?), - Ok(18) => msg.iwant.push(r.read_message::(bytes)?), - Ok(26) => msg.graft.push(r.read_message::(bytes)?), - Ok(34) => msg.prune.push(r.read_message::(bytes)?), - Ok(42) => msg.idontwant.push(r.read_message::(bytes)?), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlMessage { - fn get_size(&self) -> usize { - 0 - + self.ihave.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.iwant.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.graft.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.prune.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.idontwant.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - for s in &self.ihave { w.write_with_tag(10, |w| w.write_message(s))?; } - for s in &self.iwant { w.write_with_tag(18, |w| w.write_message(s))?; } - for s in &self.graft { w.write_with_tag(26, |w| w.write_message(s))?; } - for s in &self.prune { w.write_with_tag(34, |w| w.write_message(s))?; } - for s in &self.idontwant { w.write_with_tag(42, |w| w.write_message(s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlIHave { - pub topic_id: Option, - pub message_ids: Vec>, -} - -impl<'a> MessageRead<'a> for ControlIHave { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.topic_id = Some(r.read_string(bytes)?.to_owned()), - Ok(18) => msg.message_ids.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlIHave { - fn get_size(&self) -> usize { - 0 - + self.topic_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.message_ids.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.topic_id { w.write_with_tag(10, |w| w.write_string(&**s))?; } - for s in &self.message_ids { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlIWant { - pub message_ids: Vec>, -} - -impl<'a> MessageRead<'a> for ControlIWant { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.message_ids.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlIWant { - fn get_size(&self) -> usize { - 0 - + self.message_ids.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - for s in &self.message_ids { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlGraft { - pub topic_id: Option, -} - -impl<'a> MessageRead<'a> for ControlGraft { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.topic_id = Some(r.read_string(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlGraft { - fn get_size(&self) -> usize { - 0 - + self.topic_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.topic_id { w.write_with_tag(10, |w| w.write_string(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlPrune { - pub topic_id: Option, - pub peers: Vec, - pub backoff: Option, -} - -impl<'a> MessageRead<'a> for ControlPrune { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.topic_id = Some(r.read_string(bytes)?.to_owned()), - Ok(18) => msg.peers.push(r.read_message::(bytes)?), - Ok(24) => msg.backoff = Some(r.read_uint64(bytes)?), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlPrune { - fn get_size(&self) -> usize { - 0 - + self.topic_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.peers.iter().map(|s| 1 + sizeof_len((s).get_size())).sum::() - + self.backoff.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.topic_id { w.write_with_tag(10, |w| w.write_string(&**s))?; } - for s in &self.peers { w.write_with_tag(18, |w| w.write_message(s))?; } - if let Some(ref s) = self.backoff { w.write_with_tag(24, |w| w.write_uint64(*s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct ControlIDontWant { - pub message_ids: Vec>, -} - -impl<'a> MessageRead<'a> for ControlIDontWant { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.message_ids.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for ControlIDontWant { - fn get_size(&self) -> usize { - 0 - + self.message_ids.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - for s in &self.message_ids { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct PeerInfo { - pub peer_id: Option>, - pub signed_peer_record: Option>, -} - -impl<'a> MessageRead<'a> for PeerInfo { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.peer_id = Some(r.read_bytes(bytes)?.to_owned()), - Ok(18) => msg.signed_peer_record = Some(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for PeerInfo { - fn get_size(&self) -> usize { - 0 - + self.peer_id.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.signed_peer_record.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.peer_id { w.write_with_tag(10, |w| w.write_bytes(&**s))?; } - if let Some(ref s) = self.signed_peer_record { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct TopicDescriptor { - pub name: Option, - pub auth: Option, - pub enc: Option, -} - -impl<'a> MessageRead<'a> for TopicDescriptor { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(10) => msg.name = Some(r.read_string(bytes)?.to_owned()), - Ok(18) => msg.auth = Some(r.read_message::(bytes)?), - Ok(26) => msg.enc = Some(r.read_message::(bytes)?), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for TopicDescriptor { - fn get_size(&self) -> usize { - 0 - + self.name.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) - + self.auth.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size())) - + self.enc.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size())) - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.name { w.write_with_tag(10, |w| w.write_string(&**s))?; } - if let Some(ref s) = self.auth { w.write_with_tag(18, |w| w.write_message(s))?; } - if let Some(ref s) = self.enc { w.write_with_tag(26, |w| w.write_message(s))?; } - Ok(()) - } -} - -pub mod mod_TopicDescriptor { - -use super::*; - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct AuthOpts { - pub mode: Option, - pub keys: Vec>, -} - -impl<'a> MessageRead<'a> for AuthOpts { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(8) => msg.mode = Some(r.read_enum(bytes)?), - Ok(18) => msg.keys.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for AuthOpts { - fn get_size(&self) -> usize { - 0 - + self.mode.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) - + self.keys.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.mode { w.write_with_tag(8, |w| w.write_enum(*s as i32))?; } - for s in &self.keys { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -pub mod mod_AuthOpts { - - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum AuthMode { - NONE = 0, - KEY = 1, - WOT = 2, -} - -impl Default for AuthMode { - fn default() -> Self { - AuthMode::NONE - } -} - -impl From for AuthMode { - fn from(i: i32) -> Self { - match i { - 0 => AuthMode::NONE, - 1 => AuthMode::KEY, - 2 => AuthMode::WOT, - _ => Self::default(), - } - } -} - -impl<'a> From<&'a str> for AuthMode { - fn from(s: &'a str) -> Self { - match s { - "NONE" => AuthMode::NONE, - "KEY" => AuthMode::KEY, - "WOT" => AuthMode::WOT, - _ => Self::default(), - } - } -} - -} - -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Debug, Default, PartialEq, Clone)] -pub struct EncOpts { - pub mode: Option, - pub key_hashes: Vec>, -} - -impl<'a> MessageRead<'a> for EncOpts { - fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { - let mut msg = Self::default(); - while !r.is_eof() { - match r.next_tag(bytes) { - Ok(8) => msg.mode = Some(r.read_enum(bytes)?), - Ok(18) => msg.key_hashes.push(r.read_bytes(bytes)?.to_owned()), - Ok(t) => { r.read_unknown(bytes, t)?; } - Err(e) => return Err(e), - } - } - Ok(msg) - } -} - -impl MessageWrite for EncOpts { - fn get_size(&self) -> usize { - 0 - + self.mode.as_ref().map_or(0, |m| 1 + sizeof_varint(*(m) as u64)) - + self.key_hashes.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - } - - fn write_message(&self, w: &mut Writer) -> Result<()> { - if let Some(ref s) = self.mode { w.write_with_tag(8, |w| w.write_enum(*s as i32))?; } - for s in &self.key_hashes { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } - Ok(()) - } -} - -pub mod mod_EncOpts { - - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum EncMode { - NONE = 0, - SHAREDKEY = 1, - WOT = 2, -} - -impl Default for EncMode { - fn default() -> Self { - EncMode::NONE - } -} - -impl From for EncMode { - fn from(i: i32) -> Self { - match i { - 0 => EncMode::NONE, - 1 => EncMode::SHAREDKEY, - 2 => EncMode::WOT, - _ => Self::default(), - } - } -} - -impl<'a> From<&'a str> for EncMode { - fn from(s: &'a str) -> Self { - match s { - "NONE" => EncMode::NONE, - "SHAREDKEY" => EncMode::SHAREDKEY, - "WOT" => EncMode::WOT, - _ => Self::default(), - } - } -} - -} - -} - diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/mod.rs b/beacon_node/lighthouse_network/gossipsub/src/generated/mod.rs deleted file mode 100644 index 7ac564f3c3..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -// Automatically generated mod.rs -pub mod compat; -pub mod gossipsub; diff --git a/beacon_node/lighthouse_network/gossipsub/src/generated/rpc.proto b/beacon_node/lighthouse_network/gossipsub/src/generated/rpc.proto deleted file mode 100644 index e3b5888d2c..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/generated/rpc.proto +++ /dev/null @@ -1,89 +0,0 @@ -syntax = "proto2"; - -package gossipsub.pb; - -message RPC { - repeated SubOpts subscriptions = 1; - repeated Message publish = 2; - - message SubOpts { - optional bool subscribe = 1; // subscribe or unsubscribe - optional string topic_id = 2; - } - - optional ControlMessage control = 3; -} - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - required string topic = 4; - optional bytes signature = 5; - optional bytes key = 6; -} - -message ControlMessage { - repeated ControlIHave ihave = 1; - repeated ControlIWant iwant = 2; - repeated ControlGraft graft = 3; - repeated ControlPrune prune = 4; - repeated ControlIDontWant idontwant = 5; -} - -message ControlIHave { - optional string topic_id = 1; - repeated bytes message_ids = 2; -} - -message ControlIWant { - repeated bytes message_ids= 1; -} - -message ControlGraft { - optional string topic_id = 1; -} - -message ControlPrune { - optional string topic_id = 1; - repeated PeerInfo peers = 2; // gossipsub v1.1 PX - optional uint64 backoff = 3; // gossipsub v1.1 backoff time (in seconds) -} - -message ControlIDontWant { - repeated bytes message_ids = 1; -} - -message PeerInfo { - optional bytes peer_id = 1; - optional bytes signed_peer_record = 2; -} - -// topicID = hash(topicDescriptor); (not the topic.name) -message TopicDescriptor { - optional string name = 1; - optional AuthOpts auth = 2; - optional EncOpts enc = 3; - - message AuthOpts { - optional AuthMode mode = 1; - repeated bytes keys = 2; // root keys to trust - - enum AuthMode { - NONE = 0; // no authentication, anyone can publish - KEY = 1; // only messages signed by keys in the topic descriptor are accepted - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } - - message EncOpts { - optional EncMode mode = 1; - repeated bytes key_hashes = 2; // the hashes of the shared keys used (salted) - - enum EncMode { - NONE = 0; // no encryption, anyone can read - SHAREDKEY = 1; // messages are encrypted with shared key - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/gossip_promises.rs b/beacon_node/lighthouse_network/gossipsub/src/gossip_promises.rs deleted file mode 100644 index ce1dee2a72..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/gossip_promises.rs +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::peer_score::RejectReason; -use super::MessageId; -use super::ValidationError; -use libp2p::identity::PeerId; -use std::collections::HashMap; -use web_time::Instant; - -/// Tracks recently sent `IWANT` messages and checks if peers respond to them. -#[derive(Default)] -pub(crate) struct GossipPromises { - /// Stores for each tracked message id and peer the instant when this promise expires. - /// - /// If the peer didn't respond until then we consider the promise as broken and penalize the - /// peer. - promises: HashMap>, -} - -impl GossipPromises { - /// Returns true if the message id exists in the promises. - pub(crate) fn contains(&self, message: &MessageId) -> bool { - self.promises.contains_key(message) - } - - /// Returns true if the message id exists in the promises and contains the given peer. - pub(crate) fn contains_peer(&self, message: &MessageId, peer: &PeerId) -> bool { - self.promises - .get(message) - .is_some_and(|peers| peers.contains_key(peer)) - } - - ///Get the peers we sent IWANT the input message id. - pub(crate) fn peers_for_message(&self, message_id: &MessageId) -> Vec { - self.promises - .get(message_id) - .map(|peers| peers.keys().copied().collect()) - .unwrap_or_default() - } - - /// Track a promise to deliver a message from a list of [`MessageId`]s we are requesting. - pub(crate) fn add_promise(&mut self, peer: PeerId, messages: &[MessageId], expires: Instant) { - for message_id in messages { - // If a promise for this message id and peer already exists we don't update the expiry! - self.promises - .entry(message_id.clone()) - .or_default() - .entry(peer) - .or_insert(expires); - } - } - - pub(crate) fn message_delivered(&mut self, message_id: &MessageId) { - // Someone delivered a message, we can stop tracking all promises for it. - self.promises.remove(message_id); - } - - pub(crate) fn reject_message(&mut self, message_id: &MessageId, reason: &RejectReason) { - // A message got rejected, so we can stop tracking promises and let the score penalty apply - // from invalid message delivery. - // We do take exception and apply promise penalty regardless in the following cases, where - // the peer delivered an obviously invalid message. - match reason { - RejectReason::ValidationError(ValidationError::InvalidSignature) => (), - RejectReason::SelfOrigin => (), - _ => { - self.promises.remove(message_id); - } - }; - } - - /// Returns the number of broken promises for each peer who didn't follow up on an IWANT - /// request. - /// This should be called not too often relative to the expire times, since it iterates over - /// the whole stored data. - pub(crate) fn get_broken_promises(&mut self) -> HashMap { - let now = Instant::now(); - let mut result = HashMap::new(); - self.promises.retain(|msg, peers| { - peers.retain(|peer_id, expires| { - if *expires < now { - let count = result.entry(*peer_id).or_insert(0); - *count += 1; - tracing::debug!( - peer=%peer_id, - message=%msg, - "[Penalty] The peer broke the promise to deliver message in time!" - ); - false - } else { - true - } - }); - !peers.is_empty() - }); - result - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/handler.rs b/beacon_node/lighthouse_network/gossipsub/src/handler.rs deleted file mode 100644 index 0f25db6e3d..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/handler.rs +++ /dev/null @@ -1,558 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::protocol::{GossipsubCodec, ProtocolConfig}; -use super::rpc_proto::proto; -use super::types::{PeerKind, RawMessage, Rpc, RpcOut, RpcReceiver}; -use super::ValidationError; -use asynchronous_codec::Framed; -use futures::future::Either; -use futures::prelude::*; -use futures::StreamExt; -use libp2p::core::upgrade::DeniedUpgrade; -use libp2p::swarm::handler::{ - ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, - FullyNegotiatedInbound, FullyNegotiatedOutbound, StreamUpgradeError, SubstreamProtocol, -}; -use libp2p::swarm::Stream; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; -use web_time::Instant; - -/// The event emitted by the Handler. This informs the behaviour of various events created -/// by the handler. -#[derive(Debug)] -pub enum HandlerEvent { - /// A GossipsubRPC message has been received. This also contains a list of invalid messages (if - /// any) that were received. - Message { - /// The GossipsubRPC message excluding any invalid messages. - rpc: Rpc, - /// Any invalid messages that were received in the RPC, along with the associated - /// validation error. - invalid_messages: Vec<(RawMessage, ValidationError)>, - }, - /// An inbound or outbound substream has been established with the peer and this informs over - /// which protocol. This message only occurs once per connection. - PeerKind(PeerKind), - /// A message to be published was dropped because it could not be sent in time. - MessageDropped(RpcOut), -} - -/// A message sent from the behaviour to the handler. -#[allow(clippy::large_enum_variant)] -#[derive(Debug)] -pub enum HandlerIn { - /// The peer has joined the mesh. - JoinedMesh, - /// The peer has left the mesh. - LeftMesh, -} - -/// The maximum number of inbound or outbound substreams attempts we allow. -/// -/// Gossipsub is supposed to have a single long-lived inbound and outbound substream. On failure we -/// attempt to recreate these. This imposes an upper bound of new substreams before we consider the -/// connection faulty and disable the handler. This also prevents against potential substream -/// creation loops. -const MAX_SUBSTREAM_ATTEMPTS: usize = 5; - -#[allow(clippy::large_enum_variant)] -pub enum Handler { - Enabled(EnabledHandler), - Disabled(DisabledHandler), -} - -/// Protocol Handler that manages a single long-lived substream with a peer. -pub struct EnabledHandler { - /// Upgrade configuration for the gossipsub protocol. - listen_protocol: ProtocolConfig, - - /// The single long-lived outbound substream. - outbound_substream: Option, - - /// The single long-lived inbound substream. - inbound_substream: Option, - - /// Queue of values that we want to send to the remote - send_queue: RpcReceiver, - - /// Flag indicating that an outbound substream is being established to prevent duplicate - /// requests. - outbound_substream_establishing: bool, - - /// The number of outbound substreams we have requested. - outbound_substream_attempts: usize, - - /// The number of inbound substreams that have been created by the peer. - inbound_substream_attempts: usize, - - /// The type of peer this handler is associated to. - peer_kind: Option, - - /// Keeps track on whether we have sent the peer kind to the behaviour. - // - // NOTE: Use this flag rather than checking the substream count each poll. - peer_kind_sent: bool, - - last_io_activity: Instant, - - /// Keeps track of whether this connection is for a peer in the mesh. This is used to make - /// decisions about the keep alive state for this connection. - in_mesh: bool, -} - -pub enum DisabledHandler { - /// If the peer doesn't support the gossipsub protocol we do not immediately disconnect. - /// Rather, we disable the handler and prevent any incoming or outgoing substreams from being - /// established. - ProtocolUnsupported { - /// Keeps track on whether we have sent the peer kind to the behaviour. - peer_kind_sent: bool, - }, - /// The maximum number of inbound or outbound substream attempts have happened and thereby the - /// handler has been disabled. - MaxSubstreamAttempts, -} - -/// State of the inbound substream, opened either by us or by the remote. -enum InboundSubstreamState { - /// Waiting for a message from the remote. The idle state for an inbound substream. - WaitingInput(Framed), - /// The substream is being closed. - Closing(Framed), - /// An error occurred during processing. - Poisoned, -} - -/// State of the outbound substream, opened either by us or by the remote. -enum OutboundSubstreamState { - /// Waiting for the user to send a message. The idle state for an outbound substream. - WaitingOutput(Framed), - /// Waiting to send a message to the remote. - PendingSend(Framed, proto::RPC), - /// Waiting to flush the substream so that the data arrives to the remote. - PendingFlush(Framed), - /// An error occurred during processing. - Poisoned, -} - -impl Handler { - /// Builds a new [`Handler`]. - pub fn new(protocol_config: ProtocolConfig, message_queue: RpcReceiver) -> Self { - Handler::Enabled(EnabledHandler { - listen_protocol: protocol_config, - inbound_substream: None, - outbound_substream: None, - outbound_substream_establishing: false, - outbound_substream_attempts: 0, - inbound_substream_attempts: 0, - peer_kind: None, - peer_kind_sent: false, - last_io_activity: Instant::now(), - in_mesh: false, - send_queue: message_queue, - }) - } -} - -impl EnabledHandler { - fn on_fully_negotiated_inbound( - &mut self, - (substream, peer_kind): (Framed, PeerKind), - ) { - // update the known kind of peer - if self.peer_kind.is_none() { - self.peer_kind = Some(peer_kind); - } - - // new inbound substream. Replace the current one, if it exists. - tracing::trace!("New inbound substream request"); - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - } - - fn on_fully_negotiated_outbound( - &mut self, - FullyNegotiatedOutbound { protocol, .. }: FullyNegotiatedOutbound< - ::OutboundProtocol, - >, - ) { - let (substream, peer_kind) = protocol; - - // update the known kind of peer - if self.peer_kind.is_none() { - self.peer_kind = Some(peer_kind); - } - - assert!( - self.outbound_substream.is_none(), - "Established an outbound substream with one already available" - ); - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)); - } - - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll< - ConnectionHandlerEvent< - ::OutboundProtocol, - (), - ::ToBehaviour, - >, - > { - if !self.peer_kind_sent { - if let Some(peer_kind) = self.peer_kind.as_ref() { - self.peer_kind_sent = true; - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - HandlerEvent::PeerKind(peer_kind.clone()), - )); - } - } - - // determine if we need to create the outbound stream - if !self.send_queue.poll_is_empty(cx) - && self.outbound_substream.is_none() - && !self.outbound_substream_establishing - { - self.outbound_substream_establishing = true; - return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new(self.listen_protocol.clone(), ()), - }); - } - - // process outbound stream - loop { - match std::mem::replace( - &mut self.outbound_substream, - Some(OutboundSubstreamState::Poisoned), - ) { - // outbound idle state - Some(OutboundSubstreamState::WaitingOutput(substream)) => { - if let Poll::Ready(Some(mut message)) = self.send_queue.poll_next_unpin(cx) { - match message { - RpcOut::Publish { - message: _, - ref mut timeout, - } - | RpcOut::Forward { - message: _, - ref mut timeout, - } => { - if Pin::new(timeout).poll(cx).is_ready() { - // Inform the behaviour and end the poll. - self.outbound_substream = - Some(OutboundSubstreamState::WaitingOutput(substream)); - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - HandlerEvent::MessageDropped(message), - )); - } - } - _ => {} // All other messages are not time-bound. - } - self.outbound_substream = Some(OutboundSubstreamState::PendingSend( - substream, - message.into_protobuf(), - )); - continue; - } - - self.outbound_substream = - Some(OutboundSubstreamState::WaitingOutput(substream)); - break; - } - Some(OutboundSubstreamState::PendingSend(mut substream, message)) => { - match Sink::poll_ready(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - match Sink::start_send(Pin::new(&mut substream), message) { - Ok(()) => { - self.outbound_substream = - Some(OutboundSubstreamState::PendingFlush(substream)) - } - Err(e) => { - tracing::debug!( - "Failed to send message on outbound stream: {e}" - ); - self.outbound_substream = None; - break; - } - } - } - Poll::Ready(Err(e)) => { - tracing::debug!("Failed to send message on outbound stream: {e}"); - self.outbound_substream = None; - break; - } - Poll::Pending => { - self.outbound_substream = - Some(OutboundSubstreamState::PendingSend(substream, message)); - break; - } - } - } - Some(OutboundSubstreamState::PendingFlush(mut substream)) => { - match Sink::poll_flush(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - self.last_io_activity = Instant::now(); - self.outbound_substream = - Some(OutboundSubstreamState::WaitingOutput(substream)) - } - Poll::Ready(Err(e)) => { - tracing::debug!("Failed to flush outbound stream: {e}"); - self.outbound_substream = None; - break; - } - Poll::Pending => { - self.outbound_substream = - Some(OutboundSubstreamState::PendingFlush(substream)); - break; - } - } - } - None => { - self.outbound_substream = None; - break; - } - Some(OutboundSubstreamState::Poisoned) => { - unreachable!("Error occurred during outbound stream processing") - } - } - } - - // Handle inbound messages. - loop { - match std::mem::replace( - &mut self.inbound_substream, - Some(InboundSubstreamState::Poisoned), - ) { - // inbound idle state - Some(InboundSubstreamState::WaitingInput(mut substream)) => { - match substream.poll_next_unpin(cx) { - Poll::Ready(Some(Ok(message))) => { - self.last_io_activity = Instant::now(); - self.inbound_substream = - Some(InboundSubstreamState::WaitingInput(substream)); - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(message)); - } - Poll::Ready(Some(Err(error))) => { - tracing::debug!("Failed to read from inbound stream: {error}"); - // Close this side of the stream. If the - // peer is still around, they will re-establish their - // outbound stream i.e. our inbound stream. - self.inbound_substream = - Some(InboundSubstreamState::Closing(substream)); - } - // peer closed the stream - Poll::Ready(None) => { - tracing::debug!("Inbound stream closed by remote"); - self.inbound_substream = - Some(InboundSubstreamState::Closing(substream)); - } - Poll::Pending => { - self.inbound_substream = - Some(InboundSubstreamState::WaitingInput(substream)); - break; - } - } - } - Some(InboundSubstreamState::Closing(mut substream)) => { - match Sink::poll_close(Pin::new(&mut substream), cx) { - Poll::Ready(res) => { - if let Err(e) = res { - // Don't close the connection but just drop the inbound substream. - // In case the remote has more to send, they will open up a new - // substream. - tracing::debug!("Inbound substream error while closing: {e}"); - } - self.inbound_substream = None; - break; - } - Poll::Pending => { - self.inbound_substream = - Some(InboundSubstreamState::Closing(substream)); - break; - } - } - } - None => { - self.inbound_substream = None; - break; - } - Some(InboundSubstreamState::Poisoned) => { - unreachable!("Error occurred during inbound stream processing") - } - } - } - - // Drop the next message in queue if it's stale. - if let Poll::Ready(Some(rpc)) = self.send_queue.poll_stale(cx) { - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - HandlerEvent::MessageDropped(rpc), - )); - } - - Poll::Pending - } -} - -impl ConnectionHandler for Handler { - type FromBehaviour = HandlerIn; - type ToBehaviour = HandlerEvent; - type InboundOpenInfo = (); - type InboundProtocol = either::Either; - type OutboundOpenInfo = (); - type OutboundProtocol = ProtocolConfig; - - fn listen_protocol(&self) -> SubstreamProtocol { - match self { - Handler::Enabled(handler) => { - SubstreamProtocol::new(either::Either::Left(handler.listen_protocol.clone()), ()) - } - Handler::Disabled(_) => { - SubstreamProtocol::new(either::Either::Right(DeniedUpgrade), ()) - } - } - } - - fn on_behaviour_event(&mut self, message: HandlerIn) { - match self { - Handler::Enabled(handler) => match message { - HandlerIn::JoinedMesh => { - handler.in_mesh = true; - } - HandlerIn::LeftMesh => { - handler.in_mesh = false; - } - }, - Handler::Disabled(_) => { - tracing::debug!(?message, "Handler is disabled. Dropping message"); - } - } - } - - fn connection_keep_alive(&self) -> bool { - matches!(self, Handler::Enabled(h) if h.in_mesh) - } - - #[tracing::instrument(level = "trace", name = "ConnectionHandler::poll", skip(self, cx))] - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll> { - match self { - Handler::Enabled(handler) => handler.poll(cx), - Handler::Disabled(DisabledHandler::ProtocolUnsupported { peer_kind_sent }) => { - if !*peer_kind_sent { - *peer_kind_sent = true; - return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( - HandlerEvent::PeerKind(PeerKind::NotSupported), - )); - } - - Poll::Pending - } - Handler::Disabled(DisabledHandler::MaxSubstreamAttempts) => Poll::Pending, - } - } - - fn on_connection_event( - &mut self, - event: ConnectionEvent, - ) { - match self { - Handler::Enabled(handler) => { - if event.is_inbound() { - handler.inbound_substream_attempts += 1; - - if handler.inbound_substream_attempts == MAX_SUBSTREAM_ATTEMPTS { - tracing::warn!( - "The maximum number of inbound substreams attempts has been exceeded" - ); - *self = Handler::Disabled(DisabledHandler::MaxSubstreamAttempts); - return; - } - } - - if event.is_outbound() { - handler.outbound_substream_establishing = false; - - handler.outbound_substream_attempts += 1; - - if handler.outbound_substream_attempts == MAX_SUBSTREAM_ATTEMPTS { - tracing::warn!( - "The maximum number of outbound substream attempts has been exceeded" - ); - *self = Handler::Disabled(DisabledHandler::MaxSubstreamAttempts); - return; - } - } - - match event { - ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound { - protocol, - .. - }) => match protocol { - Either::Left(protocol) => handler.on_fully_negotiated_inbound(protocol), - #[allow(unreachable_patterns)] - Either::Right(v) => libp2p::core::util::unreachable(v), - }, - ConnectionEvent::FullyNegotiatedOutbound(fully_negotiated_outbound) => { - handler.on_fully_negotiated_outbound(fully_negotiated_outbound) - } - ConnectionEvent::DialUpgradeError(DialUpgradeError { - error: StreamUpgradeError::Timeout, - .. - }) => { - tracing::debug!("Dial upgrade error: Protocol negotiation timeout"); - } - // This pattern is unreachable as of Rust 1.82, we can remove it once the - // MSRV is increased past that version. - #[allow(unreachable_patterns)] - ConnectionEvent::DialUpgradeError(DialUpgradeError { - error: StreamUpgradeError::Apply(e), - .. - }) => void::unreachable(e), - ConnectionEvent::DialUpgradeError(DialUpgradeError { - error: StreamUpgradeError::NegotiationFailed, - .. - }) => { - // The protocol is not supported - tracing::debug!( - "The remote peer does not support gossipsub on this connection" - ); - *self = Handler::Disabled(DisabledHandler::ProtocolUnsupported { - peer_kind_sent: false, - }); - } - ConnectionEvent::DialUpgradeError(DialUpgradeError { - error: StreamUpgradeError::Io(e), - .. - }) => { - tracing::debug!("Protocol negotiation failed: {e}") - } - _ => {} - } - } - Handler::Disabled(_) => {} - } - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/lib.rs b/beacon_node/lighthouse_network/gossipsub/src/lib.rs deleted file mode 100644 index 1d29aaa759..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/lib.rs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Implementation of the [Gossipsub](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/README.md) protocol. -//! -//! Gossipsub is a P2P pubsub (publish/subscription) routing layer designed to extend upon -//! floodsub and meshsub routing protocols. -//! -//! # Overview -//! -//! *Note: The gossipsub protocol specifications -//! () provide an outline for the -//! routing protocol. They should be consulted for further detail.* -//! -//! Gossipsub is a blend of meshsub for data and randomsub for mesh metadata. It provides bounded -//! degree and amplification factor with the meshsub construction and augments it using gossip -//! propagation of metadata with the randomsub technique. -//! -//! The router maintains an overlay mesh network of peers on which to efficiently send messages and -//! metadata. Peers use control messages to broadcast and request known messages and -//! subscribe/unsubscribe from topics in the mesh network. -//! -//! # Important Discrepancies -//! -//! This section outlines the current implementation's potential discrepancies from that of other -//! implementations, due to undefined elements in the current specification. -//! -//! - **Topics** - In gossipsub, topics configurable by the `hash_topics` configuration parameter. -//! Topics are of type [`TopicHash`]. The current go implementation uses raw utf-8 strings, and this -//! is default configuration in rust-libp2p. Topics can be hashed (SHA256 hashed then base64 -//! encoded) by setting the `hash_topics` configuration parameter to true. -//! -//! - **Sequence Numbers** - A message on the gossipsub network is identified by the source -//! [`PeerId`](libp2p_identity::PeerId) and a nonce (sequence number) of the message. The sequence numbers in -//! this implementation are sent as raw bytes across the wire. They are 64-bit big-endian unsigned -//! integers. When messages are signed, they are monotonically increasing integers starting from a -//! random value and wrapping around u64::MAX. When messages are unsigned, they are chosen at random. -//! NOTE: These numbers are sequential in the current go implementation. -//! -//! # Peer Discovery -//! -//! Gossipsub does not provide peer discovery by itself. Peer discovery is the process by which -//! peers in a p2p network exchange information about each other among other reasons to become resistant -//! against the failure or replacement of the -//! [boot nodes](https://docs.libp2p.io/reference/glossary/#boot-node) of the network. -//! -//! Peer -//! discovery can e.g. be implemented with the help of the [Kademlia](https://github.com/libp2p/specs/blob/master/kad-dht/README.md) protocol -//! in combination with the [Identify](https://github.com/libp2p/specs/tree/master/identify) protocol. See the -//! Kademlia implementation documentation for more information. -//! -//! # Using Gossipsub -//! -//! ## Gossipsub Config -//! -//! The [`Config`] struct specifies various network performance/tuning configuration -//! parameters. Specifically it specifies: -//! -//! [`Config`]: struct.Config.html -//! -//! This struct implements the [`Default`] trait and can be initialised via -//! [`Config::default()`]. -//! -//! -//! ## Behaviour -//! -//! The [`Behaviour`] struct implements the [`libp2p_swarm::NetworkBehaviour`] trait allowing it to -//! act as the routing behaviour in a [`libp2p_swarm::Swarm`]. This struct requires an instance of -//! [`PeerId`](libp2p_identity::PeerId) and [`Config`]. -//! -//! [`Behaviour`]: struct.Behaviour.html - -//! ## Example -//! -//! For an example on how to use gossipsub, see the [chat-example](https://github.com/libp2p/rust-libp2p/tree/master/examples/chat). - -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -mod backoff; -mod behaviour; -mod config; -mod error; -mod gossip_promises; -mod handler; -mod mcache; -mod metrics; -mod peer_score; -mod protocol; -mod rpc_proto; -mod subscription_filter; -mod time_cache; -mod topic; -mod transform; -mod types; - -pub use self::behaviour::{Behaviour, Event, MessageAuthenticity}; -pub use self::config::{Config, ConfigBuilder, ValidationMode, Version}; -pub use self::error::{ConfigBuilderError, PublishError, SubscriptionError, ValidationError}; -pub use self::metrics::Config as MetricsConfig; -pub use self::peer_score::{ - score_parameter_decay, score_parameter_decay_with_base, PeerScoreParams, PeerScoreThresholds, - TopicScoreParams, -}; -pub use self::subscription_filter::{ - AllowAllSubscriptionFilter, CallbackSubscriptionFilter, CombinedSubscriptionFilters, - MaxCountSubscriptionFilter, RegexSubscriptionFilter, TopicSubscriptionFilter, - WhitelistSubscriptionFilter, -}; -pub use self::topic::{Hasher, Topic, TopicHash}; -pub use self::transform::{DataTransform, IdentityTransform}; -pub use self::types::{FailedMessages, Message, MessageAcceptance, MessageId, RawMessage}; - -#[deprecated(note = "Will be removed from the public API.")] -pub type Rpc = self::types::Rpc; - -pub type IdentTopic = Topic; -pub type Sha256Topic = Topic; diff --git a/beacon_node/lighthouse_network/gossipsub/src/mcache.rs b/beacon_node/lighthouse_network/gossipsub/src/mcache.rs deleted file mode 100644 index eced0456d6..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/mcache.rs +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::topic::TopicHash; -use super::types::{MessageId, RawMessage}; -use libp2p::identity::PeerId; -use std::collections::hash_map::Entry; -use std::fmt::Debug; -use std::{ - collections::{HashMap, HashSet}, - fmt, -}; - -/// CacheEntry stored in the history. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct CacheEntry { - mid: MessageId, - topic: TopicHash, -} - -/// MessageCache struct holding history of messages. -#[derive(Clone)] -pub(crate) struct MessageCache { - msgs: HashMap)>, - /// For every message and peer the number of times this peer asked for the message - iwant_counts: HashMap>, - history: Vec>, - /// The number of indices in the cache history used for gossiping. That means that a message - /// won't get gossiped anymore when shift got called `gossip` many times after inserting the - /// message in the cache. - gossip: usize, -} - -impl fmt::Debug for MessageCache { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("MessageCache") - .field("msgs", &self.msgs) - .field("history", &self.history) - .field("gossip", &self.gossip) - .finish() - } -} - -/// Implementation of the MessageCache. -impl MessageCache { - pub(crate) fn new(gossip: usize, history_capacity: usize) -> Self { - MessageCache { - gossip, - msgs: HashMap::default(), - iwant_counts: HashMap::default(), - history: vec![Vec::new(); history_capacity], - } - } - - /// Put a message into the memory cache. - /// - /// Returns true if the message didn't already exist in the cache. - pub(crate) fn put(&mut self, message_id: &MessageId, msg: RawMessage) -> bool { - match self.msgs.entry(message_id.clone()) { - Entry::Occupied(_) => { - // Don't add duplicate entries to the cache. - false - } - Entry::Vacant(entry) => { - let cache_entry = CacheEntry { - mid: message_id.clone(), - topic: msg.topic.clone(), - }; - entry.insert((msg, HashSet::default())); - self.history[0].push(cache_entry); - - tracing::trace!(message=?message_id, "Put message in mcache"); - true - } - } - } - - /// Keeps track of peers we know have received the message to prevent forwarding to said peers. - pub(crate) fn observe_duplicate(&mut self, message_id: &MessageId, source: &PeerId) { - if let Some((message, originating_peers)) = self.msgs.get_mut(message_id) { - // if the message is already validated, we don't need to store extra peers sending us - // duplicates as the message has already been forwarded - if message.validated { - return; - } - - originating_peers.insert(*source); - } - } - - /// Get a message with `message_id` - #[cfg(test)] - pub(crate) fn get(&self, message_id: &MessageId) -> Option<&RawMessage> { - self.msgs.get(message_id).map(|(message, _)| message) - } - - /// Increases the iwant count for the given message by one and returns the message together - /// with the iwant if the message exists. - pub(crate) fn get_with_iwant_counts( - &mut self, - message_id: &MessageId, - peer: &PeerId, - ) -> Option<(&RawMessage, u32)> { - let iwant_counts = &mut self.iwant_counts; - self.msgs.get(message_id).and_then(|(message, _)| { - if !message.validated { - None - } else { - Some((message, { - let count = iwant_counts - .entry(message_id.clone()) - .or_default() - .entry(*peer) - .or_default(); - *count += 1; - *count - })) - } - }) - } - - /// Gets a message with [`MessageId`] and tags it as validated. - /// This function also returns the known peers that have sent us this message. This is used to - /// prevent us sending redundant messages to peers who have already propagated it. - pub(crate) fn validate( - &mut self, - message_id: &MessageId, - ) -> Option<(&RawMessage, HashSet)> { - self.msgs.get_mut(message_id).map(|(message, known_peers)| { - message.validated = true; - // Clear the known peers list (after a message is validated, it is forwarded and we no - // longer need to store the originating peers). - let originating_peers = std::mem::take(known_peers); - (&*message, originating_peers) - }) - } - - /// Get a list of [`MessageId`]s for a given topic. - pub(crate) fn get_gossip_message_ids(&self, topic: &TopicHash) -> Vec { - self.history[..self.gossip] - .iter() - .fold(vec![], |mut current_entries, entries| { - // search for entries with desired topic - let mut found_entries: Vec = entries - .iter() - .filter_map(|entry| { - if &entry.topic == topic { - let mid = &entry.mid; - // Only gossip validated messages - if let Some(true) = self.msgs.get(mid).map(|(msg, _)| msg.validated) { - Some(mid.clone()) - } else { - None - } - } else { - None - } - }) - .collect(); - - // generate the list - current_entries.append(&mut found_entries); - current_entries - }) - } - - /// Shift the history array down one and delete messages associated with the - /// last entry. - pub(crate) fn shift(&mut self) { - for entry in self.history.pop().expect("history is always > 1") { - if let Some((msg, _)) = self.msgs.remove(&entry.mid) { - if !msg.validated { - // If GossipsubConfig::validate_messages is true, the implementing - // application has to ensure that Gossipsub::validate_message gets called for - // each received message within the cache timeout time." - tracing::debug!( - message=%&entry.mid, - "The message got removed from the cache without being validated." - ); - } - } - tracing::trace!(message=%&entry.mid, "Remove message from the cache"); - - self.iwant_counts.remove(&entry.mid); - } - - // Insert an empty vec in position 0 - self.history.insert(0, Vec::new()); - } - - /// Removes a message from the cache and returns it if existent - pub(crate) fn remove( - &mut self, - message_id: &MessageId, - ) -> Option<(RawMessage, HashSet)> { - //We only remove the message from msgs and iwant_count and keep the message_id in the - // history vector. Zhe id in the history vector will simply be ignored on popping. - - self.iwant_counts.remove(message_id); - self.msgs.remove(message_id) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::IdentTopic as Topic; - - fn gen_testm(x: u64, topic: TopicHash) -> (MessageId, RawMessage) { - let default_id = |message: &RawMessage| { - // default message id is: source + sequence number - let mut source_string = message.source.as_ref().unwrap().to_base58(); - source_string.push_str(&message.sequence_number.unwrap().to_string()); - MessageId::from(source_string) - }; - let u8x: u8 = x as u8; - let source = Some(PeerId::random()); - let data: Vec = vec![u8x]; - let sequence_number = Some(x); - - let m = RawMessage { - source, - data, - sequence_number, - topic, - signature: None, - key: None, - validated: false, - }; - - let id = default_id(&m); - (id, m) - } - - fn new_cache(gossip_size: usize, history: usize) -> MessageCache { - MessageCache::new(gossip_size, history) - } - - #[test] - /// Test that the message cache can be created. - fn test_new_cache() { - let x: usize = 3; - let mc = new_cache(x, 5); - - assert_eq!(mc.gossip, x); - } - - #[test] - /// Test you can put one message and get one. - fn test_put_get_one() { - let mut mc = new_cache(10, 15); - - let topic1_hash = Topic::new("topic1").hash(); - let (id, m) = gen_testm(10, topic1_hash); - - mc.put(&id, m.clone()); - - assert_eq!(mc.history[0].len(), 1); - - let fetched = mc.get(&id); - - assert_eq!(fetched.unwrap(), &m); - } - - #[test] - /// Test attempting to 'get' with a wrong id. - fn test_get_wrong() { - let mut mc = new_cache(10, 15); - - let topic1_hash = Topic::new("topic1").hash(); - let (id, m) = gen_testm(10, topic1_hash); - - mc.put(&id, m); - - // Try to get an incorrect ID - let wrong_id = MessageId::new(b"wrongid"); - let fetched = mc.get(&wrong_id); - assert!(fetched.is_none()); - } - - #[test] - /// Test attempting to 'get' empty message cache. - fn test_get_empty() { - let mc = new_cache(10, 15); - - // Try to get an incorrect ID - let wrong_string = MessageId::new(b"imempty"); - let fetched = mc.get(&wrong_string); - assert!(fetched.is_none()); - } - - #[test] - /// Test shift mechanism. - fn test_shift() { - let mut mc = new_cache(1, 5); - - let topic1_hash = Topic::new("topic1").hash(); - - // Build the message - for i in 0..10 { - let (id, m) = gen_testm(i, topic1_hash.clone()); - mc.put(&id, m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - // Make sure no messages deleted - assert!(mc.msgs.len() == 10); - } - - #[test] - /// Test Shift with no additions. - fn test_empty_shift() { - let mut mc = new_cache(1, 5); - - let topic1_hash = Topic::new("topic1").hash(); - - // Build the message - for i in 0..10 { - let (id, m) = gen_testm(i, topic1_hash.clone()); - mc.put(&id, m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - mc.shift(); - - assert!(mc.history[2].len() == 10); - assert!(mc.history[1].is_empty()); - assert!(mc.history[0].is_empty()); - } - - #[test] - /// Test shift to see if the last history messages are removed. - fn test_remove_last_from_shift() { - let mut mc = new_cache(4, 5); - - let topic1_hash = Topic::new("topic1").hash(); - - // Build the message - for i in 0..10 { - let (id, m) = gen_testm(i, topic1_hash.clone()); - mc.put(&id, m.clone()); - } - - // Shift right until deleting messages - mc.shift(); - mc.shift(); - mc.shift(); - mc.shift(); - - assert_eq!(mc.history[mc.history.len() - 1].len(), 10); - - // Shift and delete the messages - mc.shift(); - assert_eq!(mc.history[mc.history.len() - 1].len(), 0); - assert_eq!(mc.history[0].len(), 0); - assert_eq!(mc.msgs.len(), 0); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/metrics.rs b/beacon_node/lighthouse_network/gossipsub/src/metrics.rs deleted file mode 100644 index 2989f95a26..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/metrics.rs +++ /dev/null @@ -1,800 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! A set of metrics used to help track and diagnose the network behaviour of the gossipsub -//! protocol. - -use std::collections::HashMap; - -use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue}; -use prometheus_client::metrics::counter::Counter; -use prometheus_client::metrics::family::{Family, MetricConstructor}; -use prometheus_client::metrics::gauge::Gauge; -use prometheus_client::metrics::histogram::{linear_buckets, Histogram}; -use prometheus_client::registry::Registry; - -use super::topic::TopicHash; -use super::types::{MessageAcceptance, PeerKind}; - -// Default value that limits for how many topics do we store metrics. -const DEFAULT_MAX_TOPICS: usize = 300; - -// Default value that limits how many topics for which there has never been a subscription do we -// store metrics. -const DEFAULT_MAX_NEVER_SUBSCRIBED_TOPICS: usize = 100; - -#[derive(Debug, Clone)] -pub struct Config { - /// This provides an upper bound to the number of mesh topics we create metrics for. It - /// prevents unbounded labels being created in the metrics. - pub max_topics: usize, - /// Mesh topics are controlled by the user via subscriptions whereas non-mesh topics are - /// determined by users on the network. This limit permits a fixed amount of topics to allow, - /// in-addition to the mesh topics. - pub max_never_subscribed_topics: usize, - /// Buckets used for the score histograms. - pub score_buckets: Vec, -} - -impl Config { - /// Create buckets for the score histograms based on score thresholds. - pub fn buckets_using_scoring_thresholds(&mut self, params: &super::PeerScoreThresholds) { - self.score_buckets = vec![ - params.graylist_threshold, - params.publish_threshold, - params.gossip_threshold, - params.gossip_threshold / 2.0, - params.gossip_threshold / 4.0, - 0.0, - 1.0, - 10.0, - 100.0, - ]; - } -} - -impl Default for Config { - fn default() -> Self { - // Some sensible defaults - let gossip_threshold = -4000.0; - let publish_threshold = -8000.0; - let graylist_threshold = -16000.0; - let score_buckets: Vec = vec![ - graylist_threshold, - publish_threshold, - gossip_threshold, - gossip_threshold / 2.0, - gossip_threshold / 4.0, - 0.0, - 1.0, - 10.0, - 100.0, - ]; - Config { - max_topics: DEFAULT_MAX_TOPICS, - max_never_subscribed_topics: DEFAULT_MAX_NEVER_SUBSCRIBED_TOPICS, - score_buckets, - } - } -} - -/// Whether we have ever been subscribed to this topic. -type EverSubscribed = bool; - -/// A collection of metrics used throughout the Gossipsub behaviour. -pub(crate) struct Metrics { - /* Configuration parameters */ - /// Maximum number of topics for which we store metrics. This helps keep the metrics bounded. - max_topics: usize, - /// Maximum number of topics for which we store metrics, where the topic in not one to which we - /// have subscribed at some point. This helps keep the metrics bounded, since these topics come - /// from received messages and not explicit application subscriptions. - max_never_subscribed_topics: usize, - - /* Auxiliary variables */ - /// Information needed to decide if a topic is allowed or not. - topic_info: HashMap, - - /* Metrics per known topic */ - /// Status of our subscription to this topic. This metric allows analyzing other topic metrics - /// filtered by our current subscription status. - topic_subscription_status: Family, - /// Number of peers subscribed to each topic. This allows us to analyze a topic's behaviour - /// regardless of our subscription status. - topic_peers_count: Family, - /// The number of invalid messages received for a given topic. - invalid_messages: Family, - /// The number of messages accepted by the application (validation result). - accepted_messages: Family, - /// The number of messages ignored by the application (validation result). - ignored_messages: Family, - /// The number of messages rejected by the application (validation result). - rejected_messages: Family, - /// The number of publish messages dropped by the sender. - publish_messages_dropped: Family, - /// The number of forward messages dropped by the sender. - forward_messages_dropped: Family, - - /* Metrics regarding mesh state */ - /// Number of peers in our mesh. This metric should be updated with the count of peers for a - /// topic in the mesh regardless of inclusion and churn events. - mesh_peer_counts: Family, - /// Number of times we include peers in a topic mesh for different reasons. - mesh_peer_inclusion_events: Family, - /// Number of times we remove peers in a topic mesh for different reasons. - mesh_peer_churn_events: Family, - - /* Metrics regarding messages sent/received */ - /// Number of gossip messages sent to each topic. - topic_msg_sent_counts: Family, - /// Bytes from gossip messages sent to each topic. - topic_msg_sent_bytes: Family, - /// Number of gossipsub messages published to each topic. - topic_msg_published: Family, - - /// Number of gossipsub messages received on each topic (without filtering duplicates). - topic_msg_recv_counts_unfiltered: Family, - /// Number of gossipsub messages received on each topic (after filtering duplicates). - topic_msg_recv_counts: Family, - /// Bytes received from gossip messages for each topic. - topic_msg_recv_bytes: Family, - - /* Metrics related to scoring */ - /// Histogram of the scores for each mesh topic. - score_per_mesh: Family, - /// A counter of the kind of penalties being applied to peers. - scoring_penalties: Family, - - /* General Metrics */ - /// Gossipsub supports floodsub, gossipsub v1.0 and gossipsub v1.1. Peers are classified based - /// on which protocol they support. This metric keeps track of the number of peers that are - /// connected of each type. - peers_per_protocol: Family, - /// The time it takes to complete one iteration of the heartbeat. - heartbeat_duration: Histogram, - - /* Performance metrics */ - /// When the user validates a message, it tries to re propagate it to its mesh peers. If the - /// message expires from the memcache before it can be validated, we count this a cache miss - /// and it is an indicator that the memcache size should be increased. - memcache_misses: Counter, - /// The number of times we have decided that an IWANT control message is required for this - /// topic. A very high metric might indicate an underperforming network. - topic_iwant_msgs: Family, - - /// The number of times we have received an IDONTWANT control message. - idontwant_msgs: Counter, - - /// The number of msg_id's we have received in every IDONTWANT control message. - idontwant_msgs_ids: Counter, - - /// The number of bytes we have received in every IDONTWANT control message. - idontwant_bytes: Counter, - - /// Number of IDONTWANT messages sent per topic. - idontwant_messages_sent_per_topic: Family, - - /// Number of full messages we received that we previously sent a IDONTWANT for. - idontwant_messages_ignored_per_topic: Family, - - /// Count of duplicate messages we have received from mesh peers for a given topic. - mesh_duplicates: Family, - - /// Count of duplicate messages we have received from by requesting them over iwant for a given topic. - iwant_duplicates: Family, - - /// The size of the priority queue. - priority_queue_size: Histogram, - /// The size of the non-priority queue. - non_priority_queue_size: Histogram, -} - -impl Metrics { - pub(crate) fn new(registry: &mut Registry, config: Config) -> Self { - // Destructure the config to be sure everything is used. - let Config { - max_topics, - max_never_subscribed_topics, - score_buckets, - } = config; - - macro_rules! register_family { - ($name:expr, $help:expr) => {{ - let fam = Family::default(); - registry.register($name, $help, fam.clone()); - fam - }}; - } - - let topic_subscription_status = register_family!( - "topic_subscription_status", - "Subscription status per known topic" - ); - let topic_peers_count = register_family!( - "topic_peers_counts", - "Number of peers subscribed to each topic" - ); - - let invalid_messages = register_family!( - "invalid_messages_per_topic", - "Number of invalid messages received for each topic" - ); - - let accepted_messages = register_family!( - "accepted_messages_per_topic", - "Number of accepted messages received for each topic" - ); - - let ignored_messages = register_family!( - "ignored_messages_per_topic", - "Number of ignored messages received for each topic" - ); - - let rejected_messages = register_family!( - "rejected_messages_per_topic", - "Number of rejected messages received for each topic" - ); - - let publish_messages_dropped = register_family!( - "publish_messages_dropped_per_topic", - "Number of publish messages dropped per topic" - ); - - let forward_messages_dropped = register_family!( - "forward_messages_dropped_per_topic", - "Number of forward messages dropped per topic" - ); - - let mesh_peer_counts = register_family!( - "mesh_peer_counts", - "Number of peers in each topic in our mesh" - ); - let mesh_peer_inclusion_events = register_family!( - "mesh_peer_inclusion_events", - "Number of times a peer gets added to our mesh for different reasons" - ); - let mesh_peer_churn_events = register_family!( - "mesh_peer_churn_events", - "Number of times a peer gets removed from our mesh for different reasons" - ); - let topic_msg_sent_counts = register_family!( - "topic_msg_sent_counts", - "Number of gossip messages sent to each topic" - ); - let topic_msg_published = register_family!( - "topic_msg_published", - "Number of gossip messages published to each topic" - ); - let topic_msg_sent_bytes = register_family!( - "topic_msg_sent_bytes", - "Bytes from gossip messages sent to each topic" - ); - - let topic_msg_recv_counts_unfiltered = register_family!( - "topic_msg_recv_counts_unfiltered", - "Number of gossip messages received on each topic (without duplicates being filtered)" - ); - - let topic_msg_recv_counts = register_family!( - "topic_msg_recv_counts", - "Number of gossip messages received on each topic (after duplicates have been filtered)" - ); - let topic_msg_recv_bytes = register_family!( - "topic_msg_recv_bytes", - "Bytes received from gossip messages for each topic" - ); - - let hist_builder = HistBuilder { - buckets: score_buckets, - }; - - let score_per_mesh: Family<_, _, HistBuilder> = Family::new_with_constructor(hist_builder); - registry.register( - "score_per_mesh", - "Histogram of scores per mesh topic", - score_per_mesh.clone(), - ); - - let scoring_penalties = register_family!( - "scoring_penalties", - "Counter of types of scoring penalties given to peers" - ); - let peers_per_protocol = register_family!( - "peers_per_protocol", - "Number of connected peers by protocol type" - ); - - let heartbeat_duration = Histogram::new(linear_buckets(0.0, 50.0, 10)); - registry.register( - "heartbeat_duration", - "Histogram of observed heartbeat durations", - heartbeat_duration.clone(), - ); - - let topic_iwant_msgs = register_family!( - "topic_iwant_msgs", - "Number of times we have decided an IWANT is required for this topic" - ); - - let idontwant_msgs = { - let metric = Counter::default(); - registry.register( - "idontwant_msgs", - "The number of times we have received an IDONTWANT control message", - metric.clone(), - ); - metric - }; - - let idontwant_msgs_ids = { - let metric = Counter::default(); - registry.register( - "idontwant_msgs_ids", - "The number of msg_id's we have received in every IDONTWANT control message.", - metric.clone(), - ); - metric - }; - - // IDONTWANT messages sent per topic - let idontwant_messages_sent_per_topic = register_family!( - "idonttwant_messages_sent_per_topic", - "Number of IDONTWANT messages sent per topic" - ); - - // IDONTWANTs which were ignored, and we still received the message per topic - let idontwant_messages_ignored_per_topic = register_family!( - "idontwant_messages_ignored_per_topic", - "IDONTWANT messages that were sent but we received the full message regardless" - ); - - let mesh_duplicates = register_family!( - "mesh_duplicates_per_topic", - "Count of duplicate messages received from mesh peers per topic" - ); - - let iwant_duplicates = register_family!( - "iwant_duplicates_per_topic", - "Count of duplicate messages received from non-mesh peers that we sent iwants for" - ); - - let idontwant_bytes = { - let metric = Counter::default(); - registry.register( - "idontwant_bytes", - "The total bytes we have received an IDONTWANT control messages", - metric.clone(), - ); - metric - }; - - let memcache_misses = { - let metric = Counter::default(); - registry.register( - "memcache_misses", - "Number of times a message is not found in the duplicate cache when validating", - metric.clone(), - ); - metric - }; - - let priority_queue_size = Histogram::new(linear_buckets(0.0, 25.0, 100)); - registry.register( - "priority_queue_size", - "Histogram of observed priority queue sizes", - priority_queue_size.clone(), - ); - - let non_priority_queue_size = Histogram::new(linear_buckets(0.0, 25.0, 100)); - registry.register( - "non_priority_queue_size", - "Histogram of observed non-priority queue sizes", - non_priority_queue_size.clone(), - ); - - Self { - max_topics, - max_never_subscribed_topics, - topic_info: HashMap::default(), - topic_subscription_status, - topic_peers_count, - invalid_messages, - accepted_messages, - ignored_messages, - rejected_messages, - publish_messages_dropped, - forward_messages_dropped, - mesh_peer_counts, - mesh_peer_inclusion_events, - mesh_peer_churn_events, - topic_msg_sent_counts, - topic_msg_sent_bytes, - topic_msg_published, - topic_msg_recv_counts_unfiltered, - topic_msg_recv_counts, - topic_msg_recv_bytes, - score_per_mesh, - scoring_penalties, - peers_per_protocol, - heartbeat_duration, - memcache_misses, - topic_iwant_msgs, - idontwant_msgs, - idontwant_bytes, - idontwant_msgs_ids, - idontwant_messages_sent_per_topic, - idontwant_messages_ignored_per_topic, - mesh_duplicates, - iwant_duplicates, - priority_queue_size, - non_priority_queue_size, - } - } - - fn non_subscription_topics_count(&self) -> usize { - self.topic_info - .values() - .filter(|&ever_subscribed| !ever_subscribed) - .count() - } - - /// Registers a topic if not already known and if the bounds allow it. - fn register_topic(&mut self, topic: &TopicHash) -> Result<(), ()> { - if self.topic_info.contains_key(topic) { - Ok(()) - } else if self.topic_info.len() < self.max_topics - && self.non_subscription_topics_count() < self.max_never_subscribed_topics - { - // This is a topic without an explicit subscription and we register it if we are within - // the configured bounds. - self.topic_info.entry(topic.clone()).or_insert(false); - self.topic_subscription_status.get_or_create(topic).set(0); - Ok(()) - } else { - // We don't know this topic and there is no space left to store it - Err(()) - } - } - - /// Registers a set of topics that we want to store calculate metrics for. - pub(crate) fn register_allowed_topics(&mut self, topics: Vec) { - for topic_hash in topics { - self.topic_info.insert(topic_hash, true); - } - } - - /// Increase the number of peers that are subscribed to this topic. - pub(crate) fn inc_topic_peers(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_peers_count.get_or_create(topic).inc(); - } - } - - /// Decrease the number of peers that are subscribed to this topic. - pub(crate) fn dec_topic_peers(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_peers_count.get_or_create(topic).dec(); - } - } - - /* Mesh related methods */ - - /// Registers the subscription to a topic if the configured limits allow it. - /// Sets the registered number of peers in the mesh to 0. - pub(crate) fn joined(&mut self, topic: &TopicHash) { - if self.topic_info.contains_key(topic) || self.topic_info.len() < self.max_topics { - self.topic_info.insert(topic.clone(), true); - let was_subscribed = self.topic_subscription_status.get_or_create(topic).set(1); - debug_assert_eq!(was_subscribed, 0); - self.mesh_peer_counts.get_or_create(topic).set(0); - } - } - - /// Registers the unsubscription to a topic if the topic was previously allowed. - /// Sets the registered number of peers in the mesh to 0. - pub(crate) fn left(&mut self, topic: &TopicHash) { - if self.topic_info.contains_key(topic) { - // Depending on the configured topic bounds we could miss a mesh topic. - // So, check first if the topic was previously allowed. - let was_subscribed = self.topic_subscription_status.get_or_create(topic).set(0); - debug_assert_eq!(was_subscribed, 1); - self.mesh_peer_counts.get_or_create(topic).set(0); - } - } - - /// Register the inclusion of peers in our mesh due to some reason. - pub(crate) fn peers_included(&mut self, topic: &TopicHash, reason: Inclusion, count: usize) { - if self.register_topic(topic).is_ok() { - self.mesh_peer_inclusion_events - .get_or_create(&InclusionLabel { - hash: topic.to_string(), - reason, - }) - .inc_by(count as u64); - } - } - - /// Register the removal of peers in our mesh due to some reason. - pub(crate) fn peers_removed(&mut self, topic: &TopicHash, reason: Churn, count: usize) { - if self.register_topic(topic).is_ok() { - self.mesh_peer_churn_events - .get_or_create(&ChurnLabel { - hash: topic.to_string(), - reason, - }) - .inc_by(count as u64); - } - } - - /// Register the current number of peers in our mesh for this topic. - pub(crate) fn set_mesh_peers(&mut self, topic: &TopicHash, count: usize) { - if self.register_topic(topic).is_ok() { - // Due to limits, this topic could have not been allowed, so we check. - self.mesh_peer_counts.get_or_create(topic).set(count as i64); - } - } - - /// Register that an invalid message was received on a specific topic. - pub(crate) fn register_invalid_message(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.invalid_messages.get_or_create(topic).inc(); - } - } - - /// Register a score penalty. - pub(crate) fn register_score_penalty(&mut self, penalty: Penalty) { - self.scoring_penalties - .get_or_create(&PenaltyLabel { penalty }) - .inc(); - } - - /// Registers that a message was published on a specific topic. - pub(crate) fn register_published_message(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_msg_published.get_or_create(topic).inc(); - } - } - - /// Register sending a message over a topic. - pub(crate) fn msg_sent(&mut self, topic: &TopicHash, bytes: usize) { - if self.register_topic(topic).is_ok() { - self.topic_msg_sent_counts.get_or_create(topic).inc(); - self.topic_msg_sent_bytes - .get_or_create(topic) - .inc_by(bytes as u64); - } - } - - /// Register sending a message over a topic. - pub(crate) fn publish_msg_dropped(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.publish_messages_dropped.get_or_create(topic).inc(); - } - } - - /// Register dropping a message over a topic. - pub(crate) fn forward_msg_dropped(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.forward_messages_dropped.get_or_create(topic).inc(); - } - } - - /// Register that a message was received (and was not a duplicate). - pub(crate) fn msg_recvd(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_msg_recv_counts.get_or_create(topic).inc(); - } - } - - /// Register that a message was received (could have been a duplicate). - pub(crate) fn msg_recvd_unfiltered(&mut self, topic: &TopicHash, bytes: usize) { - if self.register_topic(topic).is_ok() { - self.topic_msg_recv_counts_unfiltered - .get_or_create(topic) - .inc(); - self.topic_msg_recv_bytes - .get_or_create(topic) - .inc_by(bytes as u64); - } - } - - /// Register a duplicate message received from a mesh peer. - pub(crate) fn mesh_duplicates(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.mesh_duplicates.get_or_create(topic).inc(); - } - } - - /// Register a duplicate message received from a non-mesh peer on an iwant request. - pub(crate) fn iwant_duplicates(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.iwant_duplicates.get_or_create(topic).inc(); - } - } - - pub(crate) fn register_msg_validation( - &mut self, - topic: &TopicHash, - validation: &MessageAcceptance, - ) { - if self.register_topic(topic).is_ok() { - match validation { - MessageAcceptance::Accept => self.accepted_messages.get_or_create(topic).inc(), - MessageAcceptance::Ignore => self.ignored_messages.get_or_create(topic).inc(), - MessageAcceptance::Reject => self.rejected_messages.get_or_create(topic).inc(), - }; - } - } - - /// Register a memcache miss. - pub(crate) fn memcache_miss(&mut self) { - self.memcache_misses.inc(); - } - - /// Register sending an IWANT msg for this topic. - pub(crate) fn register_iwant(&mut self, topic: &TopicHash) { - if self.register_topic(topic).is_ok() { - self.topic_iwant_msgs.get_or_create(topic).inc(); - } - } - - /// Register receiving the total bytes of an IDONTWANT control message. - pub(crate) fn register_idontwant_bytes(&mut self, bytes: usize) { - self.idontwant_bytes.inc_by(bytes as u64); - } - - /// Register receiving an IDONTWANT control message for a given topic. - pub(crate) fn register_idontwant_messages_sent_per_topic(&mut self, topic: &TopicHash) { - self.idontwant_messages_sent_per_topic - .get_or_create(topic) - .inc(); - } - - /// Register receiving a message for an already sent IDONTWANT. - pub(crate) fn register_idontwant_messages_ignored_per_topic(&mut self, topic: &TopicHash) { - self.idontwant_messages_ignored_per_topic - .get_or_create(topic) - .inc(); - } - - /// Register receiving an IDONTWANT msg for this topic. - pub(crate) fn register_idontwant(&mut self, msgs: usize) { - self.idontwant_msgs.inc(); - self.idontwant_msgs_ids.inc_by(msgs as u64); - } - - /// Observes a heartbeat duration. - pub(crate) fn observe_heartbeat_duration(&mut self, millis: u64) { - self.heartbeat_duration.observe(millis as f64); - } - - /// Observes a priority queue size. - pub(crate) fn observe_priority_queue_size(&mut self, len: usize) { - self.priority_queue_size.observe(len as f64); - } - - /// Observes a non-priority queue size. - pub(crate) fn observe_non_priority_queue_size(&mut self, len: usize) { - self.non_priority_queue_size.observe(len as f64); - } - - /// Observe a score of a mesh peer. - pub(crate) fn observe_mesh_peers_score(&mut self, topic: &TopicHash, score: f64) { - if self.register_topic(topic).is_ok() { - self.score_per_mesh.get_or_create(topic).observe(score); - } - } - - /// Register a new peers connection based on its protocol. - pub(crate) fn peer_protocol_connected(&mut self, kind: PeerKind) { - self.peers_per_protocol - .get_or_create(&ProtocolLabel { protocol: kind }) - .inc(); - } - - /// Removes a peer from the counter based on its protocol when it disconnects. - pub(crate) fn peer_protocol_disconnected(&mut self, kind: PeerKind) { - let metric = self - .peers_per_protocol - .get_or_create(&ProtocolLabel { protocol: kind }); - if metric.get() != 0 { - // decrement the counter - metric.set(metric.get() - 1); - } - } -} - -/// Reasons why a peer was included in the mesh. -#[derive(PartialEq, Eq, Hash, EncodeLabelValue, Clone, Debug)] -pub(crate) enum Inclusion { - /// Peer was a fanaout peer. - Fanout, - /// Included from random selection. - Random, - /// Peer subscribed. - Subscribed, - /// Peer was included to fill the outbound quota. - Outbound, -} - -/// Reasons why a peer was removed from the mesh. -#[derive(PartialEq, Eq, Hash, EncodeLabelValue, Clone, Debug)] -pub(crate) enum Churn { - /// Peer disconnected. - Dc, - /// Peer had a bad score. - BadScore, - /// Peer sent a PRUNE. - Prune, - /// Peer unsubscribed. - Unsub, - /// Too many peers. - Excess, -} - -/// Kinds of reasons a peer's score has been penalized -#[derive(PartialEq, Eq, Hash, EncodeLabelValue, Clone, Debug)] -pub(crate) enum Penalty { - /// A peer grafted before waiting the back-off time. - GraftBackoff, - /// A Peer did not respond to an IWANT request in time. - BrokenPromise, - /// A Peer did not send enough messages as expected. - MessageDeficit, - /// Too many peers under one IP address. - IPColocation, -} - -/// Label for the mesh inclusion event metrics. -#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)] -struct InclusionLabel { - hash: String, - reason: Inclusion, -} - -/// Label for the mesh churn event metrics. -#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)] -struct ChurnLabel { - hash: String, - reason: Churn, -} - -/// Label for the kinds of protocols peers can connect as. -#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)] -struct ProtocolLabel { - protocol: PeerKind, -} - -/// Label for the kinds of scoring penalties that can occur -#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)] -struct PenaltyLabel { - penalty: Penalty, -} - -#[derive(Clone)] -struct HistBuilder { - buckets: Vec, -} - -impl MetricConstructor for HistBuilder { - fn new_metric(&self) -> Histogram { - Histogram::new(self.buckets.clone().into_iter()) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/mod.rs b/beacon_node/lighthouse_network/gossipsub/src/mod.rs deleted file mode 100644 index 8ccdc32cdd..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/mod.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Implementation of the [Gossipsub](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/README.md) protocol. -//! -//! Gossipsub is a P2P pubsub (publish/subscription) routing layer designed to extend upon -//! floodsub and meshsub routing protocols. -//! -//! # Overview -//! -//! *Note: The gossipsub protocol specifications -//! () provide an outline for the -//! routing protocol. They should be consulted for further detail.* -//! -//! Gossipsub is a blend of meshsub for data and randomsub for mesh metadata. It provides bounded -//! degree and amplification factor with the meshsub construction and augments it using gossip -//! propagation of metadata with the randomsub technique. -//! -//! The router maintains an overlay mesh network of peers on which to efficiently send messages and -//! metadata. Peers use control messages to broadcast and request known messages and -//! subscribe/unsubscribe from topics in the mesh network. -//! -//! # Important Discrepancies -//! -//! This section outlines the current implementation's potential discrepancies from that of other -//! implementations, due to undefined elements in the current specification. -//! -//! - **Topics** - In gossipsub, topics configurable by the `hash_topics` configuration parameter. -//! Topics are of type [`TopicHash`]. The current go implementation uses raw utf-8 strings, and this -//! is default configuration in rust-libp2p. Topics can be hashed (SHA256 hashed then base64 -//! encoded) by setting the `hash_topics` configuration parameter to true. -//! -//! - **Sequence Numbers** - A message on the gossipsub network is identified by the source -//! [`PeerId`](libp2p_identity::PeerId) and a nonce (sequence number) of the message. The sequence numbers in -//! this implementation are sent as raw bytes across the wire. They are 64-bit big-endian unsigned -//! integers. When messages are signed, they are monotonically increasing integers starting from a -//! random value and wrapping around u64::MAX. When messages are unsigned, they are chosen at random. -//! NOTE: These numbers are sequential in the current go implementation. -//! -//! # Peer Discovery -//! -//! Gossipsub does not provide peer discovery by itself. Peer discovery is the process by which -//! peers in a p2p network exchange information about each other among other reasons to become resistant -//! against the failure or replacement of the -//! [boot nodes](https://docs.libp2p.io/reference/glossary/#boot-node) of the network. -//! -//! Peer -//! discovery can e.g. be implemented with the help of the [Kademlia](https://github.com/libp2p/specs/blob/master/kad-dht/README.md) protocol -//! in combination with the [Identify](https://github.com/libp2p/specs/tree/master/identify) protocol. See the -//! Kademlia implementation documentation for more information. -//! -//! # Using Gossipsub -//! -//! ## Gossipsub Config -//! -//! The [`Config`] struct specifies various network performance/tuning configuration -//! parameters. Specifically it specifies: -//! -//! [`Config`]: struct.Config.html -//! -//! This struct implements the [`Default`] trait and can be initialised via -//! [`Config::default()`]. -//! -//! -//! ## Behaviour -//! -//! The [`Behaviour`] struct implements the [`libp2p_swarm::NetworkBehaviour`] trait allowing it to -//! act as the routing behaviour in a [`libp2p_swarm::Swarm`]. This struct requires an instance of -//! [`PeerId`](libp2p_identity::PeerId) and [`Config`]. -//! -//! [`Behaviour`]: struct.Behaviour.html - -//! ## Example -//! -//! For an example on how to use gossipsub, see the [chat-example](https://github.com/libp2p/rust-libp2p/tree/master/examples/chat). - -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -mod backoff; -mod behaviour; -mod config; -mod error; -mod gossip_promises; -mod handler; -mod mcache; -mod metrics; -mod peer_score; -mod protocol; -mod rpc_proto; -mod subscription_filter; -mod time_cache; -mod topic; -mod transform; -mod types; - -pub use self::behaviour::{Behaviour, Event, MessageAuthenticity}; -pub use self::config::{Config, ConfigBuilder, ValidationMode, Version}; -pub use self::error::{ConfigBuilderError, PublishError, SubscriptionError, ValidationError}; -pub use self::metrics::Config as MetricsConfig; -pub use self::peer_score::{ - score_parameter_decay, score_parameter_decay_with_base, PeerScoreParams, PeerScoreThresholds, - TopicScoreParams, -}; -pub use self::subscription_filter::{ - AllowAllSubscriptionFilter, CallbackSubscriptionFilter, CombinedSubscriptionFilters, - MaxCountSubscriptionFilter, RegexSubscriptionFilter, TopicSubscriptionFilter, - WhitelistSubscriptionFilter, -}; -pub use self::topic::{Hasher, Topic, TopicHash}; -pub use self::transform::{DataTransform, IdentityTransform}; -pub use self::types::{Message, MessageAcceptance, MessageId, RawMessage}; -pub type IdentTopic = Topic; -pub type Sha256Topic = Topic; -pub use self::types::FailedMessages; diff --git a/beacon_node/lighthouse_network/gossipsub/src/peer_score.rs b/beacon_node/lighthouse_network/gossipsub/src/peer_score.rs deleted file mode 100644 index ec6fe7bdb6..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/peer_score.rs +++ /dev/null @@ -1,937 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! -//! Manages and stores the Scoring logic of a particular peer on the gossipsub behaviour. - -use super::metrics::{Metrics, Penalty}; -use super::time_cache::TimeCache; -use super::{MessageId, TopicHash}; -use libp2p::identity::PeerId; -use std::collections::{hash_map, HashMap, HashSet}; -use std::net::IpAddr; -use std::time::Duration; -use web_time::Instant; - -mod params; -use super::ValidationError; -pub use params::{ - score_parameter_decay, score_parameter_decay_with_base, PeerScoreParams, PeerScoreThresholds, - TopicScoreParams, -}; - -#[cfg(test)] -mod tests; - -/// The number of seconds delivery messages are stored in the cache. -const TIME_CACHE_DURATION: u64 = 120; - -pub(crate) struct PeerScore { - pub(crate) params: PeerScoreParams, - /// The score parameters. - peer_stats: HashMap, - /// Tracking peers per IP. - peer_ips: HashMap>, - /// Message delivery tracking. This is a time-cache of [`DeliveryRecord`]s. - deliveries: TimeCache, - /// callback for monitoring message delivery times - message_delivery_time_callback: Option, -} - -/// General statistics for a given gossipsub peer. -struct PeerStats { - /// Connection status of the peer. - status: ConnectionStatus, - /// Stats per topic. - topics: HashMap, - /// IP tracking for individual peers. - known_ips: HashSet, - /// Behaviour penalty that is applied to the peer, assigned by the behaviour. - behaviour_penalty: f64, - /// Application specific score. Can be manipulated by calling PeerScore::set_application_score - application_score: f64, - /// Scoring based on how whether this peer consumes messages fast enough or not. - slow_peer_penalty: f64, -} - -enum ConnectionStatus { - /// The peer is connected. - Connected, - /// The peer is disconnected - Disconnected { - /// Expiration time of the score state for disconnected peers. - expire: Instant, - }, -} - -impl Default for PeerStats { - fn default() -> Self { - PeerStats { - status: ConnectionStatus::Connected, - topics: HashMap::new(), - known_ips: HashSet::new(), - behaviour_penalty: 0f64, - application_score: 0f64, - slow_peer_penalty: 0f64, - } - } -} - -impl PeerStats { - /// Returns a mutable reference to topic stats if they exist, otherwise if the supplied parameters score the - /// topic, inserts the default stats and returns a reference to those. If neither apply, returns None. - pub(crate) fn stats_or_default_mut( - &mut self, - topic_hash: TopicHash, - params: &PeerScoreParams, - ) -> Option<&mut TopicStats> { - if params.topics.contains_key(&topic_hash) { - Some(self.topics.entry(topic_hash).or_default()) - } else { - self.topics.get_mut(&topic_hash) - } - } -} - -/// Stats assigned to peer for each topic. -struct TopicStats { - mesh_status: MeshStatus, - /// Number of first message deliveries. - first_message_deliveries: f64, - /// True if the peer has been in the mesh for enough time to activate mesh message deliveries. - mesh_message_deliveries_active: bool, - /// Number of message deliveries from the mesh. - mesh_message_deliveries: f64, - /// Mesh rate failure penalty. - mesh_failure_penalty: f64, - /// Invalid message counter. - invalid_message_deliveries: f64, -} - -impl TopicStats { - /// Returns true if the peer is in the `mesh`. - pub(crate) fn in_mesh(&self) -> bool { - matches!(self.mesh_status, MeshStatus::Active { .. }) - } -} - -/// Status defining a peer's inclusion in the mesh and associated parameters. -enum MeshStatus { - Active { - /// The time the peer was last GRAFTed; - graft_time: Instant, - /// The time the peer has been in the mesh. - mesh_time: Duration, - }, - InActive, -} - -impl MeshStatus { - /// Initialises a new [`MeshStatus::Active`] mesh status. - pub(crate) fn new_active() -> Self { - MeshStatus::Active { - graft_time: Instant::now(), - mesh_time: Duration::from_secs(0), - } - } -} - -impl Default for TopicStats { - fn default() -> Self { - TopicStats { - mesh_status: MeshStatus::InActive, - first_message_deliveries: Default::default(), - mesh_message_deliveries_active: Default::default(), - mesh_message_deliveries: Default::default(), - mesh_failure_penalty: Default::default(), - invalid_message_deliveries: Default::default(), - } - } -} - -#[derive(PartialEq, Debug)] -struct DeliveryRecord { - status: DeliveryStatus, - first_seen: Instant, - peers: HashSet, -} - -#[derive(PartialEq, Debug)] -enum DeliveryStatus { - /// Don't know (yet) if the message is valid. - Unknown, - /// The message is valid together with the validated time. - Valid(Instant), - /// The message is invalid. - Invalid, - /// Instructed by the validator to ignore the message. - Ignored, -} - -impl Default for DeliveryRecord { - fn default() -> Self { - DeliveryRecord { - status: DeliveryStatus::Unknown, - first_seen: Instant::now(), - peers: HashSet::new(), - } - } -} - -impl PeerScore { - /// Creates a new [`PeerScore`] using a given set of peer scoring parameters. - #[allow(dead_code)] - pub(crate) fn new(params: PeerScoreParams) -> Self { - Self::new_with_message_delivery_time_callback(params, None) - } - - pub(crate) fn new_with_message_delivery_time_callback( - params: PeerScoreParams, - callback: Option, - ) -> Self { - PeerScore { - params, - peer_stats: HashMap::new(), - peer_ips: HashMap::new(), - deliveries: TimeCache::new(Duration::from_secs(TIME_CACHE_DURATION)), - message_delivery_time_callback: callback, - } - } - - /// Returns the score for a peer - pub(crate) fn score(&self, peer_id: &PeerId) -> f64 { - self.metric_score(peer_id, None) - } - - /// Returns the score for a peer, logging metrics. This is called from the heartbeat and - /// increments the metric counts for penalties. - pub(crate) fn metric_score(&self, peer_id: &PeerId, mut metrics: Option<&mut Metrics>) -> f64 { - let Some(peer_stats) = self.peer_stats.get(peer_id) else { - return 0.0; - }; - let mut score = 0.0; - - // topic scores - for (topic, topic_stats) in peer_stats.topics.iter() { - // topic parameters - if let Some(topic_params) = self.params.topics.get(topic) { - // we are tracking the topic - - // the topic score - let mut topic_score = 0.0; - - // P1: time in mesh - if let MeshStatus::Active { mesh_time, .. } = topic_stats.mesh_status { - let p1 = { - let v = mesh_time.as_secs_f64() - / topic_params.time_in_mesh_quantum.as_secs_f64(); - if v < topic_params.time_in_mesh_cap { - v - } else { - topic_params.time_in_mesh_cap - } - }; - topic_score += p1 * topic_params.time_in_mesh_weight; - } - - // P2: first message deliveries - let p2 = { - let v = topic_stats.first_message_deliveries; - if v < topic_params.first_message_deliveries_cap { - v - } else { - topic_params.first_message_deliveries_cap - } - }; - topic_score += p2 * topic_params.first_message_deliveries_weight; - - // P3: mesh message deliveries - if topic_stats.mesh_message_deliveries_active - && topic_stats.mesh_message_deliveries - < topic_params.mesh_message_deliveries_threshold - { - let deficit = topic_params.mesh_message_deliveries_threshold - - topic_stats.mesh_message_deliveries; - let p3 = deficit * deficit; - topic_score += p3 * topic_params.mesh_message_deliveries_weight; - if let Some(metrics) = metrics.as_mut() { - metrics.register_score_penalty(Penalty::MessageDeficit); - } - tracing::debug!( - peer=%peer_id, - %topic, - %deficit, - penalty=%topic_score, - "[Penalty] The peer has a mesh deliveries deficit and will be penalized" - ); - } - - // P3b: - // NOTE: the weight of P3b is negative (validated in TopicScoreParams.validate), so this detracts. - let p3b = topic_stats.mesh_failure_penalty; - topic_score += p3b * topic_params.mesh_failure_penalty_weight; - - // P4: invalid messages - // NOTE: the weight of P4 is negative (validated in TopicScoreParams.validate), so this detracts. - let p4 = - topic_stats.invalid_message_deliveries * topic_stats.invalid_message_deliveries; - topic_score += p4 * topic_params.invalid_message_deliveries_weight; - - // update score, mixing with topic weight - score += topic_score * topic_params.topic_weight; - } - } - - // apply the topic score cap, if any - if self.params.topic_score_cap > 0f64 && score > self.params.topic_score_cap { - score = self.params.topic_score_cap; - } - - // P5: application-specific score - let p5 = peer_stats.application_score; - score += p5 * self.params.app_specific_weight; - - // P6: IP collocation factor - for ip in peer_stats.known_ips.iter() { - if self.params.ip_colocation_factor_whitelist.contains(ip) { - continue; - } - - // P6 has a cliff (ip_colocation_factor_threshold); it's only applied iff - // at least that many peers are connected to us from that source IP - // addr. It is quadratic, and the weight is negative (validated by - // peer_score_params.validate()). - if let Some(peers_in_ip) = self.peer_ips.get(ip).map(|peers| peers.len()) { - if (peers_in_ip as f64) > self.params.ip_colocation_factor_threshold { - let surplus = (peers_in_ip as f64) - self.params.ip_colocation_factor_threshold; - let p6 = surplus * surplus; - if let Some(metrics) = metrics.as_mut() { - metrics.register_score_penalty(Penalty::IPColocation); - } - tracing::debug!( - peer=%peer_id, - surplus_ip=%ip, - surplus=%surplus, - "[Penalty] The peer gets penalized because of too many peers with the same ip" - ); - score += p6 * self.params.ip_colocation_factor_weight; - } - } - } - - // P7: behavioural pattern penalty - if peer_stats.behaviour_penalty > self.params.behaviour_penalty_threshold { - let excess = peer_stats.behaviour_penalty - self.params.behaviour_penalty_threshold; - let p7 = excess * excess; - score += p7 * self.params.behaviour_penalty_weight; - } - - // Slow peer weighting - if peer_stats.slow_peer_penalty > self.params.slow_peer_threshold { - let excess = peer_stats.slow_peer_penalty - self.params.slow_peer_threshold; - score += excess * self.params.slow_peer_weight; - } - - score - } - - pub(crate) fn add_penalty(&mut self, peer_id: &PeerId, count: usize) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - tracing::debug!( - peer=%peer_id, - %count, - "[Penalty] Behavioral penalty for peer" - ); - peer_stats.behaviour_penalty += count as f64; - } - } - - fn remove_ips_for_peer( - peer_stats: &PeerStats, - peer_ips: &mut HashMap>, - peer_id: &PeerId, - ) { - for ip in peer_stats.known_ips.iter() { - if let Some(peer_set) = peer_ips.get_mut(ip) { - peer_set.remove(peer_id); - } - } - } - - pub(crate) fn refresh_scores(&mut self) { - let now = Instant::now(); - let params_ref = &self.params; - let peer_ips_ref = &mut self.peer_ips; - self.peer_stats.retain(|peer_id, peer_stats| { - if let ConnectionStatus::Disconnected { expire } = peer_stats.status { - // has the retention period expired? - if now > expire { - // yes, throw it away (but clean up the IP tracking first) - Self::remove_ips_for_peer(peer_stats, peer_ips_ref, peer_id); - // re address this, use retain or entry - return false; - } - - // we don't decay retained scores, as the peer is not active. - // this way the peer cannot reset a negative score by simply disconnecting and reconnecting, - // unless the retention period has elapsed. - // similarly, a well behaved peer does not lose its score by getting disconnected. - return true; - } - - for (topic, topic_stats) in peer_stats.topics.iter_mut() { - // the topic parameters - if let Some(topic_params) = params_ref.topics.get(topic) { - // decay counters - topic_stats.first_message_deliveries *= - topic_params.first_message_deliveries_decay; - if topic_stats.first_message_deliveries < params_ref.decay_to_zero { - topic_stats.first_message_deliveries = 0.0; - } - topic_stats.mesh_message_deliveries *= - topic_params.mesh_message_deliveries_decay; - if topic_stats.mesh_message_deliveries < params_ref.decay_to_zero { - topic_stats.mesh_message_deliveries = 0.0; - } - topic_stats.mesh_failure_penalty *= topic_params.mesh_failure_penalty_decay; - if topic_stats.mesh_failure_penalty < params_ref.decay_to_zero { - topic_stats.mesh_failure_penalty = 0.0; - } - topic_stats.invalid_message_deliveries *= - topic_params.invalid_message_deliveries_decay; - if topic_stats.invalid_message_deliveries < params_ref.decay_to_zero { - topic_stats.invalid_message_deliveries = 0.0; - } - // update mesh time and activate mesh message delivery parameter if need be - if let MeshStatus::Active { - ref mut mesh_time, - ref mut graft_time, - } = topic_stats.mesh_status - { - *mesh_time = now.duration_since(*graft_time); - if *mesh_time > topic_params.mesh_message_deliveries_activation { - topic_stats.mesh_message_deliveries_active = true; - } - } - } - } - - // decay P7 counter - peer_stats.behaviour_penalty *= params_ref.behaviour_penalty_decay; - if peer_stats.behaviour_penalty < params_ref.decay_to_zero { - peer_stats.behaviour_penalty = 0.0; - } - - // decay slow peer score - peer_stats.slow_peer_penalty *= params_ref.slow_peer_decay; - if peer_stats.slow_peer_penalty < params_ref.decay_to_zero { - peer_stats.slow_peer_penalty = 0.0; - } - - true - }); - } - - /// Adds a connected peer to [`PeerScore`], initialising with empty ips (ips get added later - /// through add_ip. - pub(crate) fn add_peer(&mut self, peer_id: PeerId) { - let peer_stats = self.peer_stats.entry(peer_id).or_default(); - - // mark the peer as connected - peer_stats.status = ConnectionStatus::Connected; - } - - /// Adds a new ip to a peer, if the peer is not yet known creates a new peer_stats entry for it - pub(crate) fn add_ip(&mut self, peer_id: &PeerId, ip: IpAddr) { - tracing::trace!(peer=%peer_id, %ip, "Add ip for peer"); - let peer_stats = self.peer_stats.entry(*peer_id).or_default(); - - // Mark the peer as connected (currently the default is connected, but we don't want to - // rely on the default). - peer_stats.status = ConnectionStatus::Connected; - - // Insert the ip - peer_stats.known_ips.insert(ip); - self.peer_ips.entry(ip).or_default().insert(*peer_id); - } - - /// Indicate that a peer has been too slow to consume a message. - pub(crate) fn failed_message_slow_peer(&mut self, peer_id: &PeerId) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - peer_stats.slow_peer_penalty += 1.0; - tracing::debug!(peer=%peer_id, %peer_stats.slow_peer_penalty, "[Penalty] Expired message penalty."); - } - } - - /// Removes an ip from a peer - pub(crate) fn remove_ip(&mut self, peer_id: &PeerId, ip: &IpAddr) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - peer_stats.known_ips.remove(ip); - if let Some(peer_ids) = self.peer_ips.get_mut(ip) { - tracing::trace!(peer=%peer_id, %ip, "Remove ip for peer"); - peer_ids.remove(peer_id); - } else { - tracing::trace!( - peer=%peer_id, - %ip, - "No entry in peer_ips for ip which should get removed for peer" - ); - } - } else { - tracing::trace!( - peer=%peer_id, - %ip, - "No peer_stats for peer which should remove the ip" - ); - } - } - - /// Removes a peer from the score table. This retains peer statistics if their score is - /// non-positive. - pub(crate) fn remove_peer(&mut self, peer_id: &PeerId) { - // we only retain non-positive scores of peers - if self.score(peer_id) > 0f64 { - if let hash_map::Entry::Occupied(entry) = self.peer_stats.entry(*peer_id) { - Self::remove_ips_for_peer(entry.get(), &mut self.peer_ips, peer_id); - entry.remove(); - } - return; - } - - // if the peer is retained (including it's score) the `first_message_delivery` counters - // are reset to 0 and mesh delivery penalties applied. - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - for (topic, topic_stats) in peer_stats.topics.iter_mut() { - topic_stats.first_message_deliveries = 0f64; - - if let Some(threshold) = self - .params - .topics - .get(topic) - .map(|param| param.mesh_message_deliveries_threshold) - { - if topic_stats.in_mesh() - && topic_stats.mesh_message_deliveries_active - && topic_stats.mesh_message_deliveries < threshold - { - let deficit = threshold - topic_stats.mesh_message_deliveries; - topic_stats.mesh_failure_penalty += deficit * deficit; - } - } - - topic_stats.mesh_status = MeshStatus::InActive; - topic_stats.mesh_message_deliveries_active = false; - } - - peer_stats.status = ConnectionStatus::Disconnected { - expire: Instant::now() + self.params.retain_score, - }; - } - } - - /// Handles scoring functionality as a peer GRAFTs to a topic. - pub(crate) fn graft(&mut self, peer_id: &PeerId, topic: impl Into) { - let topic = topic.into(); - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - // if we are scoring the topic, update the mesh status. - if let Some(topic_stats) = peer_stats.stats_or_default_mut(topic, &self.params) { - topic_stats.mesh_status = MeshStatus::new_active(); - topic_stats.mesh_message_deliveries_active = false; - } - } - } - - /// Handles scoring functionality as a peer PRUNEs from a topic. - pub(crate) fn prune(&mut self, peer_id: &PeerId, topic: TopicHash) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - // if we are scoring the topic, update the mesh status. - if let Some(topic_stats) = peer_stats.stats_or_default_mut(topic.clone(), &self.params) - { - // sticky mesh delivery rate failure penalty - let threshold = self - .params - .topics - .get(&topic) - .expect("Topic must exist in order for there to be topic stats") - .mesh_message_deliveries_threshold; - if topic_stats.mesh_message_deliveries_active - && topic_stats.mesh_message_deliveries < threshold - { - let deficit = threshold - topic_stats.mesh_message_deliveries; - topic_stats.mesh_failure_penalty += deficit * deficit; - } - topic_stats.mesh_message_deliveries_active = false; - topic_stats.mesh_status = MeshStatus::InActive; - } - } - } - - pub(crate) fn validate_message( - &mut self, - from: &PeerId, - msg_id: &MessageId, - topic_hash: &TopicHash, - ) { - // adds an empty record with the message id - self.deliveries.entry(msg_id.clone()).or_default(); - - if let Some(callback) = self.message_delivery_time_callback { - if self - .peer_stats - .get(from) - .and_then(|s| s.topics.get(topic_hash)) - .map(|ts| ts.in_mesh()) - .unwrap_or(false) - { - callback(from, topic_hash, 0.0); - } - } - } - - pub(crate) fn deliver_message( - &mut self, - from: &PeerId, - msg_id: &MessageId, - topic_hash: &TopicHash, - ) { - self.mark_first_message_delivery(from, topic_hash); - - let record = self.deliveries.entry(msg_id.clone()).or_default(); - - // this should be the first delivery trace - if record.status != DeliveryStatus::Unknown { - tracing::warn!( - peer=%from, - status=?record.status, - first_seen=?record.first_seen.elapsed().as_secs(), - "Unexpected delivery trace" - ); - return; - } - - // mark the message as valid and reward mesh peers that have already forwarded it to us - record.status = DeliveryStatus::Valid(Instant::now()); - for peer in record.peers.iter().cloned().collect::>() { - // this check is to make sure a peer can't send us a message twice and get a double - // count if it is a first delivery - if &peer != from { - self.mark_duplicate_message_delivery(&peer, topic_hash, None); - } - } - } - - /// Similar to `reject_message` except does not require the message id or reason for an invalid message. - pub(crate) fn reject_invalid_message(&mut self, from: &PeerId, topic_hash: &TopicHash) { - tracing::debug!( - peer=%from, - "[Penalty] Message from peer rejected because of ValidationError or SelfOrigin" - ); - - self.mark_invalid_message_delivery(from, topic_hash); - } - - // Reject a message. - pub(crate) fn reject_message( - &mut self, - from: &PeerId, - msg_id: &MessageId, - topic_hash: &TopicHash, - reason: RejectReason, - ) { - match reason { - // these messages are not tracked, but the peer is penalized as they are invalid - RejectReason::ValidationError(_) | RejectReason::SelfOrigin => { - self.reject_invalid_message(from, topic_hash); - return; - } - // we ignore those messages, so do nothing. - RejectReason::BlackListedPeer | RejectReason::BlackListedSource => { - return; - } - _ => {} // the rest are handled after record creation - } - - let peers: Vec<_> = { - let record = self.deliveries.entry(msg_id.clone()).or_default(); - - // Multiple peers can now reject the same message as we track which peers send us the - // message. If we have already updated the status, return. - if record.status != DeliveryStatus::Unknown { - return; - } - - if let RejectReason::ValidationIgnored = reason { - // we were explicitly instructed by the validator to ignore the message but not penalize - // the peer - record.status = DeliveryStatus::Ignored; - record.peers.clear(); - return; - } - - // mark the message as invalid and penalize peers that have already forwarded it. - record.status = DeliveryStatus::Invalid; - // release the delivery time tracking map to free some memory early - record.peers.drain().collect() - }; - - self.mark_invalid_message_delivery(from, topic_hash); - for peer_id in peers.iter() { - self.mark_invalid_message_delivery(peer_id, topic_hash) - } - } - - pub(crate) fn duplicated_message( - &mut self, - from: &PeerId, - msg_id: &MessageId, - topic_hash: &TopicHash, - ) { - let record = self.deliveries.entry(msg_id.clone()).or_default(); - - if record.peers.contains(from) { - // we have already seen this duplicate! - return; - } - - if let Some(callback) = self.message_delivery_time_callback { - let time = if let DeliveryStatus::Valid(validated) = record.status { - validated.elapsed().as_secs_f64() - } else { - 0.0 - }; - if self - .peer_stats - .get(from) - .and_then(|s| s.topics.get(topic_hash)) - .map(|ts| ts.in_mesh()) - .unwrap_or(false) - { - callback(from, topic_hash, time); - } - } - - match record.status { - DeliveryStatus::Unknown => { - // the message is being validated; track the peer delivery and wait for - // the Deliver/Reject notification. - record.peers.insert(*from); - } - DeliveryStatus::Valid(validated) => { - // mark the peer delivery time to only count a duplicate delivery once. - record.peers.insert(*from); - self.mark_duplicate_message_delivery(from, topic_hash, Some(validated)); - } - DeliveryStatus::Invalid => { - // we no longer track delivery time - self.mark_invalid_message_delivery(from, topic_hash); - } - DeliveryStatus::Ignored => { - // the message was ignored; do nothing (we don't know if it was valid) - } - } - } - - /// Sets the application specific score for a peer. Returns true if the peer is the peer is - /// connected or if the score of the peer is not yet expired and false otherwise. - pub(crate) fn set_application_score(&mut self, peer_id: &PeerId, new_score: f64) -> bool { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - peer_stats.application_score = new_score; - true - } else { - false - } - } - - /// Sets scoring parameters for a topic. - pub(crate) fn set_topic_params(&mut self, topic_hash: TopicHash, params: TopicScoreParams) { - use hash_map::Entry::*; - match self.params.topics.entry(topic_hash.clone()) { - Occupied(mut entry) => { - let first_message_deliveries_cap = params.first_message_deliveries_cap; - let mesh_message_deliveries_cap = params.mesh_message_deliveries_cap; - let old_params = entry.insert(params); - - if old_params.first_message_deliveries_cap > first_message_deliveries_cap { - for stats in &mut self.peer_stats.values_mut() { - if let Some(tstats) = stats.topics.get_mut(&topic_hash) { - if tstats.first_message_deliveries > first_message_deliveries_cap { - tstats.first_message_deliveries = first_message_deliveries_cap; - } - } - } - } - - if old_params.mesh_message_deliveries_cap > mesh_message_deliveries_cap { - for stats in self.peer_stats.values_mut() { - if let Some(tstats) = stats.topics.get_mut(&topic_hash) { - if tstats.mesh_message_deliveries > mesh_message_deliveries_cap { - tstats.mesh_message_deliveries = mesh_message_deliveries_cap; - } - } - } - } - } - Vacant(entry) => { - entry.insert(params); - } - } - } - - /// Returns a scoring parameters for a topic if existent. - pub(crate) fn get_topic_params(&self, topic_hash: &TopicHash) -> Option<&TopicScoreParams> { - self.params.topics.get(topic_hash) - } - - /// Increments the "invalid message deliveries" counter for all scored topics the message - /// is published in. - fn mark_invalid_message_delivery(&mut self, peer_id: &PeerId, topic_hash: &TopicHash) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - if let Some(topic_stats) = - peer_stats.stats_or_default_mut(topic_hash.clone(), &self.params) - { - tracing::debug!( - peer=%peer_id, - topic=%topic_hash, - "[Penalty] Peer delivered an invalid message in topic and gets penalized \ - for it", - ); - topic_stats.invalid_message_deliveries += 1f64; - } - } - } - - /// Increments the "first message deliveries" counter for all scored topics the message is - /// published in, as well as the "mesh message deliveries" counter, if the peer is in the - /// mesh for the topic. - fn mark_first_message_delivery(&mut self, peer_id: &PeerId, topic_hash: &TopicHash) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - if let Some(topic_stats) = - peer_stats.stats_or_default_mut(topic_hash.clone(), &self.params) - { - let cap = self - .params - .topics - .get(topic_hash) - .expect("Topic must exist if there are known topic_stats") - .first_message_deliveries_cap; - topic_stats.first_message_deliveries = - if topic_stats.first_message_deliveries + 1f64 > cap { - cap - } else { - topic_stats.first_message_deliveries + 1f64 - }; - - if let MeshStatus::Active { .. } = topic_stats.mesh_status { - let cap = self - .params - .topics - .get(topic_hash) - .expect("Topic must exist if there are known topic_stats") - .mesh_message_deliveries_cap; - - topic_stats.mesh_message_deliveries = - if topic_stats.mesh_message_deliveries + 1f64 > cap { - cap - } else { - topic_stats.mesh_message_deliveries + 1f64 - }; - } - } - } - } - - /// Increments the "mesh message deliveries" counter for messages we've seen before, as long the - /// message was received within the P3 window. - fn mark_duplicate_message_delivery( - &mut self, - peer_id: &PeerId, - topic_hash: &TopicHash, - validated_time: Option, - ) { - if let Some(peer_stats) = self.peer_stats.get_mut(peer_id) { - let now = if validated_time.is_some() { - Some(Instant::now()) - } else { - None - }; - if let Some(topic_stats) = - peer_stats.stats_or_default_mut(topic_hash.clone(), &self.params) - { - if let MeshStatus::Active { .. } = topic_stats.mesh_status { - let topic_params = self - .params - .topics - .get(topic_hash) - .expect("Topic must exist if there are known topic_stats"); - - // check against the mesh delivery window -- if the validated time is passed as 0, then - // the message was received before we finished validation and thus falls within the mesh - // delivery window. - let mut falls_in_mesh_deliver_window = true; - if let Some(validated_time) = validated_time { - if let Some(now) = &now { - //should always be true - let window_time = validated_time - .checked_add(topic_params.mesh_message_deliveries_window) - .unwrap_or(*now); - if now > &window_time { - falls_in_mesh_deliver_window = false; - } - } - } - - if falls_in_mesh_deliver_window { - let cap = topic_params.mesh_message_deliveries_cap; - topic_stats.mesh_message_deliveries = - if topic_stats.mesh_message_deliveries + 1f64 > cap { - cap - } else { - topic_stats.mesh_message_deliveries + 1f64 - }; - } - } - } - } - } - - pub(crate) fn mesh_message_deliveries(&self, peer: &PeerId, topic: &TopicHash) -> Option { - self.peer_stats - .get(peer) - .and_then(|s| s.topics.get(topic)) - .map(|t| t.mesh_message_deliveries) - } -} - -/// The reason a Gossipsub message has been rejected. -#[derive(Clone, Copy)] -pub(crate) enum RejectReason { - /// The message failed the configured validation during decoding. - ValidationError(ValidationError), - /// The message source is us. - SelfOrigin, - /// The peer that sent the message was blacklisted. - BlackListedPeer, - /// The source (from field) of the message was blacklisted. - BlackListedSource, - /// The validation was ignored. - ValidationIgnored, - /// The validation failed. - ValidationFailed, -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/peer_score/params.rs b/beacon_node/lighthouse_network/gossipsub/src/peer_score/params.rs deleted file mode 100644 index a5ac1b63b5..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/peer_score/params.rs +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::TopicHash; -use std::collections::{HashMap, HashSet}; -use std::net::IpAddr; -use std::time::Duration; - -/// The default number of seconds for a decay interval. -const DEFAULT_DECAY_INTERVAL: u64 = 1; -/// The default rate to decay to 0. -const DEFAULT_DECAY_TO_ZERO: f64 = 0.1; - -/// Computes the decay factor for a parameter, assuming the `decay_interval` is 1s -/// and that the value decays to zero if it drops below 0.01. -pub fn score_parameter_decay(decay: Duration) -> f64 { - score_parameter_decay_with_base( - decay, - Duration::from_secs(DEFAULT_DECAY_INTERVAL), - DEFAULT_DECAY_TO_ZERO, - ) -} - -/// Computes the decay factor for a parameter using base as the `decay_interval`. -pub fn score_parameter_decay_with_base(decay: Duration, base: Duration, decay_to_zero: f64) -> f64 { - // the decay is linear, so after n ticks the value is factor^n - // so factor^n = decay_to_zero => factor = decay_to_zero^(1/n) - let ticks = decay.as_secs_f64() / base.as_secs_f64(); - decay_to_zero.powf(1f64 / ticks) -} - -#[derive(Debug, Clone)] -pub struct PeerScoreThresholds { - /// The score threshold below which gossip propagation is suppressed; - /// should be negative. - pub gossip_threshold: f64, - - /// The score threshold below which we shouldn't publish when using flood - /// publishing (also applies to fanout peers); should be negative and <= `gossip_threshold`. - pub publish_threshold: f64, - - /// The score threshold below which message processing is suppressed altogether, - /// implementing an effective graylist according to peer score; should be negative and - /// <= `publish_threshold`. - pub graylist_threshold: f64, - - /// The score threshold below which px will be ignored; this should be positive - /// and limited to scores attainable by bootstrappers and other trusted nodes. - pub accept_px_threshold: f64, - - /// The median mesh score threshold before triggering opportunistic - /// grafting; this should have a small positive value. - pub opportunistic_graft_threshold: f64, -} - -impl Default for PeerScoreThresholds { - fn default() -> Self { - PeerScoreThresholds { - gossip_threshold: -10.0, - publish_threshold: -50.0, - graylist_threshold: -80.0, - accept_px_threshold: 10.0, - opportunistic_graft_threshold: 20.0, - } - } -} - -impl PeerScoreThresholds { - pub fn validate(&self) -> Result<(), &'static str> { - if self.gossip_threshold > 0f64 { - return Err("invalid gossip threshold; it must be <= 0"); - } - if self.publish_threshold > 0f64 || self.publish_threshold > self.gossip_threshold { - return Err("Invalid publish threshold; it must be <= 0 and <= gossip threshold"); - } - if self.graylist_threshold > 0f64 || self.graylist_threshold > self.publish_threshold { - return Err("Invalid graylist threshold; it must be <= 0 and <= publish threshold"); - } - if self.accept_px_threshold < 0f64 { - return Err("Invalid accept px threshold; it must be >= 0"); - } - if self.opportunistic_graft_threshold < 0f64 { - return Err("Invalid opportunistic grafting threshold; it must be >= 0"); - } - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub struct PeerScoreParams { - /// Score parameters per topic. - pub topics: HashMap, - - /// Aggregate topic score cap; this limits the total contribution of topics towards a positive - /// score. It must be positive (or 0 for no cap). - pub topic_score_cap: f64, - - /// P5: Application-specific peer scoring - pub app_specific_weight: f64, - - /// P6: IP-colocation factor. - /// The parameter has an associated counter which counts the number of peers with the same IP. - /// If the number of peers in the same IP exceeds `ip_colocation_factor_threshold, then the value - /// is the square of the difference, ie `(peers_in_same_ip - ip_colocation_threshold)^2`. - /// If the number of peers in the same IP is less than the threshold, then the value is 0. - /// The weight of the parameter MUST be negative, unless you want to disable for testing. - /// Note: In order to simulate many IPs in a manageable manner when testing, you can set the weight to 0 - /// thus disabling the IP colocation penalty. - pub ip_colocation_factor_weight: f64, - pub ip_colocation_factor_threshold: f64, - pub ip_colocation_factor_whitelist: HashSet, - - /// P7: behavioural pattern penalties. - /// This parameter has an associated counter which tracks misbehaviour as detected by the - /// router. The router currently applies penalties for the following behaviors: - /// - attempting to re-graft before the prune backoff time has elapsed. - /// - not following up in IWANT requests for messages advertised with IHAVE. - /// - /// The value of the parameter is the square of the counter over the threshold, which decays - /// with BehaviourPenaltyDecay. - /// The weight of the parameter MUST be negative (or zero to disable). - pub behaviour_penalty_weight: f64, - pub behaviour_penalty_threshold: f64, - pub behaviour_penalty_decay: f64, - - /// The decay interval for parameter counters. - pub decay_interval: Duration, - - /// Counter value below which it is considered 0. - pub decay_to_zero: f64, - - /// Time to remember counters for a disconnected peer. - pub retain_score: Duration, - - /// Slow peer penalty conditions - pub slow_peer_weight: f64, - pub slow_peer_threshold: f64, - pub slow_peer_decay: f64, -} - -impl Default for PeerScoreParams { - fn default() -> Self { - PeerScoreParams { - topics: HashMap::new(), - topic_score_cap: 3600.0, - app_specific_weight: 10.0, - ip_colocation_factor_weight: -5.0, - ip_colocation_factor_threshold: 10.0, - ip_colocation_factor_whitelist: HashSet::new(), - behaviour_penalty_weight: -10.0, - behaviour_penalty_threshold: 0.0, - behaviour_penalty_decay: 0.2, - decay_interval: Duration::from_secs(DEFAULT_DECAY_INTERVAL), - decay_to_zero: DEFAULT_DECAY_TO_ZERO, - retain_score: Duration::from_secs(3600), - slow_peer_weight: -0.2, - slow_peer_threshold: 0.0, - slow_peer_decay: 0.2, - } - } -} - -/// Peer score parameter validation -impl PeerScoreParams { - pub fn validate(&self) -> Result<(), String> { - for (topic, params) in self.topics.iter() { - if let Err(e) = params.validate() { - return Err(format!("Invalid score parameters for topic {topic}: {e}")); - } - } - - // check that the topic score is 0 or something positive - if self.topic_score_cap < 0f64 { - return Err("Invalid topic score cap; must be positive (or 0 for no cap)".into()); - } - - // check the IP colocation factor - if self.ip_colocation_factor_weight > 0f64 { - return Err( - "Invalid ip_colocation_factor_weight; must be negative (or 0 to disable)".into(), - ); - } - if self.ip_colocation_factor_weight != 0f64 && self.ip_colocation_factor_threshold < 1f64 { - return Err("Invalid ip_colocation_factor_threshold; must be at least 1".into()); - } - - // check the behaviour penalty - if self.behaviour_penalty_weight > 0f64 { - return Err( - "Invalid behaviour_penalty_weight; must be negative (or 0 to disable)".into(), - ); - } - if self.behaviour_penalty_weight != 0f64 - && (self.behaviour_penalty_decay <= 0f64 || self.behaviour_penalty_decay >= 1f64) - { - return Err("invalid behaviour_penalty_decay; must be between 0 and 1".into()); - } - - if self.behaviour_penalty_threshold < 0f64 { - return Err("invalid behaviour_penalty_threshold; must be >= 0".into()); - } - - // check the decay parameters - if self.decay_interval < Duration::from_secs(1) { - return Err("Invalid decay_interval; must be at least 1s".into()); - } - if self.decay_to_zero <= 0f64 || self.decay_to_zero >= 1f64 { - return Err("Invalid decay_to_zero; must be between 0 and 1".into()); - } - - // no need to check the score retention; a value of 0 means that we don't retain scores - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub struct TopicScoreParams { - /// The weight of the topic. - pub topic_weight: f64, - - /// P1: time in the mesh - /// This is the time the peer has been grafted in the mesh. - /// The value of of the parameter is the `time/time_in_mesh_quantum`, capped by `time_in_mesh_cap` - /// The weight of the parameter must be positive (or zero to disable). - pub time_in_mesh_weight: f64, - pub time_in_mesh_quantum: Duration, - pub time_in_mesh_cap: f64, - - /// P2: first message deliveries - /// This is the number of message deliveries in the topic. - /// The value of the parameter is a counter, decaying with `first_message_deliveries_decay`, and capped - /// by `first_message_deliveries_cap`. - /// The weight of the parameter MUST be positive (or zero to disable). - pub first_message_deliveries_weight: f64, - pub first_message_deliveries_decay: f64, - pub first_message_deliveries_cap: f64, - - /// P3: mesh message deliveries - /// This is the number of message deliveries in the mesh, within the - /// `mesh_message_deliveries_window` of message validation; deliveries during validation also - /// count and are retroactively applied when validation succeeds. - /// This window accounts for the minimum time before a hostile mesh peer trying to game the - /// score could replay back a valid message we just sent them. - /// It effectively tracks first and near-first deliveries, ie a message seen from a mesh peer - /// before we have forwarded it to them. - /// The parameter has an associated counter, decaying with `mesh_message_deliveries_decay`. - /// If the counter exceeds the threshold, its value is 0. - /// If the counter is below the `mesh_message_deliveries_threshold`, the value is the square of - /// the deficit, ie (`message_deliveries_threshold - counter)^2` - /// The penalty is only activated after `mesh_message_deliveries_activation` time in the mesh. - /// The weight of the parameter MUST be negative (or zero to disable). - pub mesh_message_deliveries_weight: f64, - pub mesh_message_deliveries_decay: f64, - pub mesh_message_deliveries_cap: f64, - pub mesh_message_deliveries_threshold: f64, - pub mesh_message_deliveries_window: Duration, - pub mesh_message_deliveries_activation: Duration, - - /// P3b: sticky mesh propagation failures - /// This is a sticky penalty that applies when a peer gets pruned from the mesh with an active - /// mesh message delivery penalty. - /// The weight of the parameter MUST be negative (or zero to disable) - pub mesh_failure_penalty_weight: f64, - pub mesh_failure_penalty_decay: f64, - - /// P4: invalid messages - /// This is the number of invalid messages in the topic. - /// The value of the parameter is the square of the counter, decaying with - /// `invalid_message_deliveries_decay`. - /// The weight of the parameter MUST be negative (or zero to disable). - pub invalid_message_deliveries_weight: f64, - pub invalid_message_deliveries_decay: f64, -} - -/// NOTE: The topic score parameters are very network specific. -/// For any production system, these values should be manually set. -impl Default for TopicScoreParams { - fn default() -> Self { - TopicScoreParams { - topic_weight: 0.5, - // P1 - time_in_mesh_weight: 1.0, - time_in_mesh_quantum: Duration::from_millis(1), - time_in_mesh_cap: 3600.0, - // P2 - first_message_deliveries_weight: 1.0, - first_message_deliveries_decay: 0.5, - first_message_deliveries_cap: 2000.0, - // P3 - mesh_message_deliveries_weight: -1.0, - mesh_message_deliveries_decay: 0.5, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_activation: Duration::from_secs(5), - // P3b - mesh_failure_penalty_weight: -1.0, - mesh_failure_penalty_decay: 0.5, - // P4 - invalid_message_deliveries_weight: -1.0, - invalid_message_deliveries_decay: 0.3, - } - } -} - -impl TopicScoreParams { - pub fn validate(&self) -> Result<(), &'static str> { - // make sure we have a sane topic weight - if self.topic_weight < 0f64 { - return Err("invalid topic weight; must be >= 0"); - } - - if self.time_in_mesh_quantum == Duration::from_secs(0) { - return Err("Invalid time_in_mesh_quantum; must be non zero"); - } - if self.time_in_mesh_weight < 0f64 { - return Err("Invalid time_in_mesh_weight; must be positive (or 0 to disable)"); - } - if self.time_in_mesh_weight != 0f64 && self.time_in_mesh_cap <= 0f64 { - return Err("Invalid time_in_mesh_cap must be positive"); - } - - if self.first_message_deliveries_weight < 0f64 { - return Err( - "Invalid first_message_deliveries_weight; must be positive (or 0 to disable)", - ); - } - if self.first_message_deliveries_weight != 0f64 - && (self.first_message_deliveries_decay <= 0f64 - || self.first_message_deliveries_decay >= 1f64) - { - return Err("Invalid first_message_deliveries_decay; must be between 0 and 1"); - } - if self.first_message_deliveries_weight != 0f64 && self.first_message_deliveries_cap <= 0f64 - { - return Err("Invalid first_message_deliveries_cap must be positive"); - } - - if self.mesh_message_deliveries_weight > 0f64 { - return Err( - "Invalid mesh_message_deliveries_weight; must be negative (or 0 to disable)", - ); - } - if self.mesh_message_deliveries_weight != 0f64 - && (self.mesh_message_deliveries_decay <= 0f64 - || self.mesh_message_deliveries_decay >= 1f64) - { - return Err("Invalid mesh_message_deliveries_decay; must be between 0 and 1"); - } - if self.mesh_message_deliveries_weight != 0f64 && self.mesh_message_deliveries_cap <= 0f64 { - return Err("Invalid mesh_message_deliveries_cap must be positive"); - } - if self.mesh_message_deliveries_weight != 0f64 - && self.mesh_message_deliveries_threshold <= 0f64 - { - return Err("Invalid mesh_message_deliveries_threshold; must be positive"); - } - if self.mesh_message_deliveries_weight != 0f64 - && self.mesh_message_deliveries_activation < Duration::from_secs(1) - { - return Err("Invalid mesh_message_deliveries_activation; must be at least 1s"); - } - - // check P3b - if self.mesh_failure_penalty_weight > 0f64 { - return Err("Invalid mesh_failure_penalty_weight; must be negative (or 0 to disable)"); - } - if self.mesh_failure_penalty_weight != 0f64 - && (self.mesh_failure_penalty_decay <= 0f64 || self.mesh_failure_penalty_decay >= 1f64) - { - return Err("Invalid mesh_failure_penalty_decay; must be between 0 and 1"); - } - - // check P4 - if self.invalid_message_deliveries_weight > 0f64 { - return Err( - "Invalid invalid_message_deliveries_weight; must be negative (or 0 to disable)", - ); - } - if self.invalid_message_deliveries_decay <= 0f64 - || self.invalid_message_deliveries_decay >= 1f64 - { - return Err("Invalid invalid_message_deliveries_decay; must be between 0 and 1"); - } - Ok(()) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/peer_score/tests.rs b/beacon_node/lighthouse_network/gossipsub/src/peer_score/tests.rs deleted file mode 100644 index 064e277eed..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/peer_score/tests.rs +++ /dev/null @@ -1,978 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -/// A collection of unit tests mostly ported from the go implementation. -use super::*; - -use crate::types::RawMessage; -use crate::{IdentTopic as Topic, Message}; - -// estimates a value within variance -fn within_variance(value: f64, expected: f64, variance: f64) -> bool { - if expected >= 0.0 { - return value > expected * (1.0 - variance) && value < expected * (1.0 + variance); - } - value > expected * (1.0 + variance) && value < expected * (1.0 - variance) -} - -// generates a random gossipsub message with sequence number i -fn make_test_message(seq: u64) -> (MessageId, RawMessage) { - let raw_message = RawMessage { - source: Some(PeerId::random()), - data: vec![12, 34, 56], - sequence_number: Some(seq), - topic: Topic::new("test").hash(), - signature: None, - key: None, - validated: true, - }; - - let message = Message { - source: raw_message.source, - data: raw_message.data.clone(), - sequence_number: raw_message.sequence_number, - topic: raw_message.topic.clone(), - }; - - let id = default_message_id()(&message); - (id, raw_message) -} - -fn default_message_id() -> fn(&Message) -> MessageId { - |message| { - // default message id is: source + sequence number - // NOTE: If either the peer_id or source is not provided, we set to 0; - let mut source_string = if let Some(peer_id) = message.source.as_ref() { - peer_id.to_base58() - } else { - PeerId::from_bytes(&[0, 1, 0]) - .expect("Valid peer id") - .to_base58() - }; - source_string.push_str(&message.sequence_number.unwrap_or_default().to_string()); - MessageId::from(source_string) - } -} - -#[test] -fn test_score_time_in_mesh() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams { - topic_score_cap: 1000.0, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 0.5, - time_in_mesh_weight: 1.0, - time_in_mesh_quantum: Duration::from_millis(1), - time_in_mesh_cap: 3600.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - - let peer_id = PeerId::random(); - - let mut peer_score = PeerScore::new(params); - // Peer score should start at 0 - peer_score.add_peer(peer_id); - - let score = peer_score.score(&peer_id); - assert!( - score == 0.0, - "expected score to start at zero. Score found: {score}" - ); - - // The time in mesh depends on how long the peer has been grafted - peer_score.graft(&peer_id, topic); - let elapsed = topic_params.time_in_mesh_quantum * 200; - std::thread::sleep(elapsed); - peer_score.refresh_scores(); - - let score = peer_score.score(&peer_id); - let expected = topic_params.topic_weight - * topic_params.time_in_mesh_weight - * (elapsed.as_millis() / topic_params.time_in_mesh_quantum.as_millis()) as f64; - assert!( - score >= expected, - "The score: {score} should be greater than or equal to: {expected}" - ); -} - -#[test] -fn test_score_time_in_mesh_cap() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 0.5, - time_in_mesh_weight: 1.0, - time_in_mesh_quantum: Duration::from_millis(1), - time_in_mesh_cap: 10.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - - let peer_id = PeerId::random(); - - let mut peer_score = PeerScore::new(params); - // Peer score should start at 0 - peer_score.add_peer(peer_id); - - let score = peer_score.score(&peer_id); - assert!( - score == 0.0, - "expected score to start at zero. Score found: {score}" - ); - - // The time in mesh depends on how long the peer has been grafted - peer_score.graft(&peer_id, topic); - let elapsed = topic_params.time_in_mesh_quantum * 40; - std::thread::sleep(elapsed); - peer_score.refresh_scores(); - - let score = peer_score.score(&peer_id); - let expected = topic_params.topic_weight - * topic_params.time_in_mesh_weight - * topic_params.time_in_mesh_cap; - let variance = 0.5; - assert!( - within_variance(score, expected, variance), - "The score: {} should be within {} of {}", - score, - score * variance, - expected - ); -} - -#[test] -fn test_score_first_message_deliveries() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - first_message_deliveries_weight: 1.0, - first_message_deliveries_decay: 1.0, - first_message_deliveries_cap: 2000.0, - time_in_mesh_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - - let peer_id = PeerId::random(); - - let mut peer_score = PeerScore::new(params); - // Peer score should start at 0 - peer_score.add_peer(peer_id); - peer_score.graft(&peer_id, topic); - - // deliver a bunch of messages from the peer - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id, &id, &msg.topic); - peer_score.deliver_message(&peer_id, &id, &msg.topic); - } - - peer_score.refresh_scores(); - - let score = peer_score.score(&peer_id); - let expected = - topic_params.topic_weight * topic_params.first_message_deliveries_weight * messages as f64; - assert!(score == expected, "The score: {score} should be {expected}"); -} - -#[test] -fn test_score_first_message_deliveries_cap() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - first_message_deliveries_weight: 1.0, - first_message_deliveries_decay: 1.0, // test without decay - first_message_deliveries_cap: 50.0, - time_in_mesh_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - - let peer_id = PeerId::random(); - - let mut peer_score = PeerScore::new(params); - // Peer score should start at 0 - peer_score.add_peer(peer_id); - peer_score.graft(&peer_id, topic); - - // deliver a bunch of messages from the peer - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id, &id, &msg.topic); - peer_score.deliver_message(&peer_id, &id, &msg.topic); - } - - peer_score.refresh_scores(); - let score = peer_score.score(&peer_id); - let expected = topic_params.topic_weight - * topic_params.first_message_deliveries_weight - * topic_params.first_message_deliveries_cap; - assert!(score == expected, "The score: {score} should be {expected}"); -} - -#[test] -fn test_score_first_message_deliveries_decay() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - first_message_deliveries_weight: 1.0, - first_message_deliveries_decay: 0.9, // decay 10% per decay interval - first_message_deliveries_cap: 2000.0, - time_in_mesh_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let peer_id = PeerId::random(); - let mut peer_score = PeerScore::new(params); - peer_score.add_peer(peer_id); - peer_score.graft(&peer_id, topic); - - // deliver a bunch of messages from the peer - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id, &id, &msg.topic); - peer_score.deliver_message(&peer_id, &id, &msg.topic); - } - - peer_score.refresh_scores(); - let score = peer_score.score(&peer_id); - let mut expected = topic_params.topic_weight - * topic_params.first_message_deliveries_weight - * topic_params.first_message_deliveries_decay - * messages as f64; - assert!(score == expected, "The score: {score} should be {expected}"); - - // refreshing the scores applies the decay param - let decay_intervals = 10; - for _ in 0..decay_intervals { - peer_score.refresh_scores(); - expected *= topic_params.first_message_deliveries_decay; - } - let score = peer_score.score(&peer_id); - assert!(score == expected, "The score: {score} should be {expected}"); -} - -#[test] -fn test_score_mesh_message_deliveries() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: -1.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 1.0, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - // peer A always delivers the message first. - // peer B delivers next (within the delivery window). - // peer C delivers outside the delivery window. - // we expect peers A and B to have a score of zero, since all other parameter weights are zero. - // Peer C should have a negative score. - let peer_id_a = PeerId::random(); - let peer_id_b = PeerId::random(); - let peer_id_c = PeerId::random(); - - let peers = vec![peer_id_a, peer_id_b, peer_id_c]; - - for peer_id in &peers { - peer_score.add_peer(*peer_id); - peer_score.graft(peer_id, topic.clone()); - } - - // assert that nobody has been penalized yet for not delivering messages before activation time - peer_score.refresh_scores(); - for peer_id in &peers { - let score = peer_score.score(peer_id); - assert!( - score >= 0.0, - "expected no mesh delivery penalty before activation time, got score {score}" - ); - } - - // wait for the activation time to kick in - std::thread::sleep(topic_params.mesh_message_deliveries_activation); - - // deliver a bunch of messages from peer A, with duplicates within the window from peer B, - // and duplicates outside the window from peer C. - let messages = 100; - let mut messages_to_send = Vec::new(); - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - peer_score.deliver_message(&peer_id_a, &id, &msg.topic); - - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - messages_to_send.push((id, msg)); - } - - std::thread::sleep(topic_params.mesh_message_deliveries_window + Duration::from_millis(20)); - - for (id, msg) in messages_to_send { - peer_score.duplicated_message(&peer_id_c, &id, &msg.topic); - } - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - let score_c = peer_score.score(&peer_id_c); - - assert!( - score_a >= 0.0, - "expected non-negative score for Peer A, got score {score_a}" - ); - assert!( - score_b >= 0.0, - "expected non-negative score for Peer B, got score {score_b}" - ); - - // the penalty is the difference between the threshold and the actual mesh deliveries, squared. - // since we didn't deliver anything, this is just the value of the threshold - let penalty = topic_params.mesh_message_deliveries_threshold - * topic_params.mesh_message_deliveries_threshold; - let expected = - topic_params.topic_weight * topic_params.mesh_message_deliveries_weight * penalty; - - assert!(score_c == expected, "Score: {score_c}. Expected {expected}"); -} - -#[test] -fn test_score_mesh_message_deliveries_decay() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: -1.0, - mesh_message_deliveries_activation: Duration::from_secs(0), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 0.9, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - mesh_failure_penalty_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - // deliver a bunch of messages from peer A - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - peer_score.deliver_message(&peer_id_a, &id, &msg.topic); - } - - // we should have a positive score, since we delivered more messages than the threshold - peer_score.refresh_scores(); - - let score_a = peer_score.score(&peer_id_a); - assert!( - score_a >= 0.0, - "expected non-negative score for Peer A, got score {score_a}" - ); - - let mut decayed_delivery_count = (messages as f64) * topic_params.mesh_message_deliveries_decay; - for _ in 0..20 { - peer_score.refresh_scores(); - decayed_delivery_count *= topic_params.mesh_message_deliveries_decay; - } - - let score_a = peer_score.score(&peer_id_a); - // the penalty is the difference between the threshold and the (decayed) mesh deliveries, squared. - let deficit = topic_params.mesh_message_deliveries_threshold - decayed_delivery_count; - let penalty = deficit * deficit; - let expected = - topic_params.topic_weight * topic_params.mesh_message_deliveries_weight * penalty; - - assert_eq!(score_a, expected, "Invalid score"); -} - -#[test] -fn test_score_mesh_failure_penalty() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - // the mesh failure penalty is applied when a peer is pruned while their - // mesh deliveries are under the threshold. - // for this test, we set the mesh delivery threshold, but set - // mesh_message_deliveries to zero, so the only affect on the score - // is from the mesh failure penalty - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - mesh_message_deliveries_activation: Duration::from_secs(0), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 1.0, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - mesh_failure_penalty_weight: -1.0, - mesh_failure_penalty_decay: 1.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - let peer_id_b = PeerId::random(); - - let peers = vec![peer_id_a, peer_id_b]; - - for peer_id in &peers { - peer_score.add_peer(*peer_id); - peer_score.graft(peer_id, topic.clone()); - } - - // deliver a bunch of messages from peer A - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - peer_score.deliver_message(&peer_id_a, &id, &msg.topic); - } - - // peers A and B should both have zero scores, since the failure penalty hasn't been applied yet - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - assert!( - score_a >= 0.0, - "expected non-negative score for Peer A, got score {score_a}" - ); - assert!( - score_b >= 0.0, - "expected non-negative score for Peer B, got score {score_b}" - ); - - // prune peer B to apply the penalty - peer_score.prune(&peer_id_b, topic.hash()); - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - - assert_eq!(score_a, 0.0, "expected Peer A to have a 0"); - - // penalty calculation is the same as for mesh_message_deliveries, but multiplied by - // mesh_failure_penalty_weigh - // instead of mesh_message_deliveries_weight - let penalty = topic_params.mesh_message_deliveries_threshold - * topic_params.mesh_message_deliveries_threshold; - let expected = topic_params.topic_weight * topic_params.mesh_failure_penalty_weight * penalty; - - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_b, expected, "Peer B should have expected score",); -} - -#[test] -fn test_score_invalid_message_deliveries() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 1.0, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - invalid_message_deliveries_weight: -1.0, - invalid_message_deliveries_decay: 1.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - // reject a bunch of messages from peer A - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationFailed); - } - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - - let expected = topic_params.topic_weight - * topic_params.invalid_message_deliveries_weight - * (messages * messages) as f64; - - assert_eq!(score_a, expected, "Peer has unexpected score",); -} - -#[test] -fn test_score_invalid_message_deliveris_decay() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - mesh_message_deliveries_activation: Duration::from_secs(1), - mesh_message_deliveries_window: Duration::from_millis(10), - mesh_message_deliveries_threshold: 20.0, - mesh_message_deliveries_cap: 100.0, - mesh_message_deliveries_decay: 1.0, - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - invalid_message_deliveries_weight: -1.0, - invalid_message_deliveries_decay: 0.9, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params.clone()); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - // reject a bunch of messages from peer A - let messages = 100; - for seq in 0..messages { - let (id, msg) = make_test_message(seq); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationFailed); - } - - peer_score.refresh_scores(); - - let decay = topic_params.invalid_message_deliveries_decay * messages as f64; - - let mut expected = - topic_params.topic_weight * topic_params.invalid_message_deliveries_weight * decay * decay; - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, expected, "Peer has unexpected score"); - - // refresh scores a few times to apply decay - for _ in 0..10 { - peer_score.refresh_scores(); - expected *= topic_params.invalid_message_deliveries_decay - * topic_params.invalid_message_deliveries_decay; - } - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, expected, "Peer has unexpected score"); -} - -#[test] -fn test_score_reject_message_deliveries() { - // This tests adds coverage for the dark corners of rejection tracing - - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams::default(); - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - first_message_deliveries_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - invalid_message_deliveries_weight: -1.0, - invalid_message_deliveries_decay: 1.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - let peer_id_b = PeerId::random(); - - let peers = vec![peer_id_a, peer_id_b]; - - for peer_id in &peers { - peer_score.add_peer(*peer_id); - } - - let (id, msg) = make_test_message(1); - - // these should have no effect in the score - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::BlackListedPeer); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::BlackListedSource); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationIgnored); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, 0.0, "Should have no effect on the score"); - assert_eq!(score_b, 0.0, "Should have no effect on the score"); - - // insert a record in the message deliveries - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - - // this should have no effect in the score, and subsequent duplicate messages should have no - // effect either - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationIgnored); - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, 0.0, "Should have no effect on the score"); - assert_eq!(score_b, 0.0, "Should have no effect on the score"); - - // now clear the delivery record - peer_score.deliveries.clear(); - - // insert a record in the message deliveries - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - - // this should have no effect in the score, and subsequent duplicate messages should have no - // effect either - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationIgnored); - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, 0.0, "Should have no effect on the score"); - assert_eq!(score_b, 0.0, "Should have no effect on the score"); - - // now clear the delivery record - peer_score.deliveries.clear(); - - // insert a new record in the message deliveries - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - - // and reject the message to make sure duplicates are also penalized - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationFailed); - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, -1.0, "Score should be effected"); - assert_eq!(score_b, -1.0, "Score should be effected"); - - // now clear the delivery record again - peer_score.deliveries.clear(); - - // insert a new record in the message deliveries - peer_score.validate_message(&peer_id_a, &id, &msg.topic); - - // and reject the message after a duplicate has arrived - peer_score.duplicated_message(&peer_id_b, &id, &msg.topic); - peer_score.reject_message(&peer_id_a, &id, &msg.topic, RejectReason::ValidationFailed); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - - assert_eq!(score_a, -4.0, "Score should be effected"); - assert_eq!(score_b, -4.0, "Score should be effected"); -} - -#[test] -fn test_application_score() { - // Create parameters with reasonable default values - let app_specific_weight = 0.5; - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams { - app_specific_weight, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - first_message_deliveries_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - invalid_message_deliveries_weight: 0.0, - invalid_message_deliveries_decay: 1.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - let messages = 100; - for i in -100..messages { - let app_score_value = i as f64; - peer_score.set_application_score(&peer_id_a, app_score_value); - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let expected = (i as f64) * app_specific_weight; - assert_eq!(score_a, expected, "Peer has unexpected score"); - } -} - -#[test] -fn test_score_ip_colocation() { - // Create parameters with reasonable default values - let ip_colocation_factor_weight = -1.0; - let ip_colocation_factor_threshold = 1.0; - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams { - ip_colocation_factor_weight, - ip_colocation_factor_threshold, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - first_message_deliveries_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - invalid_message_deliveries_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - let peer_id_b = PeerId::random(); - let peer_id_c = PeerId::random(); - let peer_id_d = PeerId::random(); - - let peers = vec![peer_id_a, peer_id_b, peer_id_c, peer_id_d]; - for peer_id in &peers { - peer_score.add_peer(*peer_id); - peer_score.graft(peer_id, topic.clone()); - } - - // peerA should have no penalty, but B, C, and D should be penalized for sharing an IP - peer_score.add_ip(&peer_id_a, "1.2.3.4".parse().unwrap()); - peer_score.add_ip(&peer_id_b, "2.3.4.5".parse().unwrap()); - peer_score.add_ip(&peer_id_c, "2.3.4.5".parse().unwrap()); - peer_score.add_ip(&peer_id_c, "3.4.5.6".parse().unwrap()); - peer_score.add_ip(&peer_id_d, "2.3.4.5".parse().unwrap()); - - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - let score_b = peer_score.score(&peer_id_b); - let score_c = peer_score.score(&peer_id_c); - let score_d = peer_score.score(&peer_id_d); - - assert_eq!(score_a, 0.0, "Peer A should be unaffected"); - - let n_shared = 3.0; - let ip_surplus = n_shared - ip_colocation_factor_threshold; - let penalty = ip_surplus * ip_surplus; - let expected = ip_colocation_factor_weight * penalty; - - assert_eq!(score_b, expected, "Peer B should have expected score"); - assert_eq!(score_c, expected, "Peer C should have expected score"); - assert_eq!(score_d, expected, "Peer D should have expected score"); -} - -#[test] -fn test_score_behaviour_penality() { - // Create parameters with reasonable default values - let behaviour_penalty_weight = -1.0; - let behaviour_penalty_decay = 0.99; - - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let mut params = PeerScoreParams { - behaviour_penalty_decay, - behaviour_penalty_weight, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 1.0, - mesh_message_deliveries_weight: 0.0, - first_message_deliveries_weight: 0.0, - mesh_failure_penalty_weight: 0.0, - time_in_mesh_weight: 0.0, - time_in_mesh_quantum: Duration::from_secs(1), - invalid_message_deliveries_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - - // add a penalty to a non-existent peer. - peer_score.add_penalty(&peer_id_a, 1); - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, 0.0, "Peer A should be unaffected"); - - // add the peer and test penalties - peer_score.add_peer(peer_id_a); - assert_eq!(score_a, 0.0, "Peer A should be unaffected"); - - peer_score.add_penalty(&peer_id_a, 1); - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, -1.0, "Peer A should have been penalized"); - - peer_score.add_penalty(&peer_id_a, 1); - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, -4.0, "Peer A should have been penalized"); - - peer_score.refresh_scores(); - - let score_a = peer_score.score(&peer_id_a); - assert_eq!(score_a, -3.9204, "Peer A should have been penalized"); -} - -#[test] -fn test_score_retention() { - // Create parameters with reasonable default values - let topic = Topic::new("test"); - let topic_hash = topic.hash(); - let app_specific_weight = 1.0; - let app_score_value = -1000.0; - let retain_score = Duration::from_secs(1); - let mut params = PeerScoreParams { - app_specific_weight, - retain_score, - ..Default::default() - }; - - let topic_params = TopicScoreParams { - topic_weight: 0.0, - mesh_message_deliveries_weight: 0.0, - mesh_message_deliveries_activation: Duration::from_secs(0), - first_message_deliveries_weight: 0.0, - time_in_mesh_weight: 0.0, - ..Default::default() - }; - - params.topics.insert(topic_hash, topic_params); - let mut peer_score = PeerScore::new(params); - - let peer_id_a = PeerId::random(); - peer_score.add_peer(peer_id_a); - peer_score.graft(&peer_id_a, topic); - - peer_score.set_application_score(&peer_id_a, app_score_value); - - // score should equal -1000 (app specific score) - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - assert_eq!( - score_a, app_score_value, - "Score should be the application specific score" - ); - - // disconnect & wait half of RetainScore time. Should still have negative score - peer_score.remove_peer(&peer_id_a); - std::thread::sleep(retain_score / 2); - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - assert_eq!( - score_a, app_score_value, - "Score should be the application specific score" - ); - - // wait remaining time (plus a little slop) and the score should reset to zero - std::thread::sleep(retain_score / 2 + Duration::from_millis(50)); - peer_score.refresh_scores(); - let score_a = peer_score.score(&peer_id_a); - assert_eq!( - score_a, 0.0, - "Score should be the application specific score" - ); -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/protocol.rs b/beacon_node/lighthouse_network/gossipsub/src/protocol.rs deleted file mode 100644 index b72f4ccc9b..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/protocol.rs +++ /dev/null @@ -1,646 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::config::ValidationMode; -use super::handler::HandlerEvent; -use super::rpc_proto::proto; -use super::topic::TopicHash; -use super::types::{ - ControlAction, Graft, IDontWant, IHave, IWant, MessageId, PeerInfo, PeerKind, Prune, - RawMessage, Rpc, Subscription, SubscriptionAction, -}; -use super::ValidationError; -use asynchronous_codec::{Decoder, Encoder, Framed}; -use byteorder::{BigEndian, ByteOrder}; -use bytes::BytesMut; -use futures::prelude::*; -use libp2p::core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; -use libp2p::identity::{PeerId, PublicKey}; -use libp2p::swarm::StreamProtocol; -use quick_protobuf::Writer; -use std::pin::Pin; -use void::Void; - -pub(crate) const SIGNING_PREFIX: &[u8] = b"libp2p-pubsub:"; - -pub(crate) const GOSSIPSUB_1_2_0_PROTOCOL: ProtocolId = ProtocolId { - protocol: StreamProtocol::new("/meshsub/1.2.0"), - kind: PeerKind::Gossipsubv1_2, -}; -pub(crate) const GOSSIPSUB_1_1_0_PROTOCOL: ProtocolId = ProtocolId { - protocol: StreamProtocol::new("/meshsub/1.1.0"), - kind: PeerKind::Gossipsubv1_1, -}; -pub(crate) const GOSSIPSUB_1_0_0_PROTOCOL: ProtocolId = ProtocolId { - protocol: StreamProtocol::new("/meshsub/1.0.0"), - kind: PeerKind::Gossipsub, -}; -pub(crate) const FLOODSUB_PROTOCOL: ProtocolId = ProtocolId { - protocol: StreamProtocol::new("/floodsub/1.0.0"), - kind: PeerKind::Floodsub, -}; - -/// Implementation of [`InboundUpgrade`] and [`OutboundUpgrade`] for the Gossipsub protocol. -#[derive(Debug, Clone)] -pub struct ProtocolConfig { - /// The Gossipsub protocol id to listen on. - pub(crate) protocol_ids: Vec, - /// The maximum transmit size for a packet. - pub(crate) max_transmit_size: usize, - /// Determines the level of validation to be done on incoming messages. - pub(crate) validation_mode: ValidationMode, -} - -impl Default for ProtocolConfig { - fn default() -> Self { - Self { - max_transmit_size: 65536, - validation_mode: ValidationMode::Strict, - protocol_ids: vec![ - GOSSIPSUB_1_2_0_PROTOCOL, - GOSSIPSUB_1_1_0_PROTOCOL, - GOSSIPSUB_1_0_0_PROTOCOL, - ], - } - } -} - -/// The protocol ID -#[derive(Clone, Debug, PartialEq)] -pub struct ProtocolId { - /// The RPC message type/name. - pub protocol: StreamProtocol, - /// The type of protocol we support - pub kind: PeerKind, -} - -impl AsRef for ProtocolId { - fn as_ref(&self) -> &str { - self.protocol.as_ref() - } -} - -impl UpgradeInfo for ProtocolConfig { - type Info = ProtocolId; - type InfoIter = Vec; - - fn protocol_info(&self) -> Self::InfoIter { - self.protocol_ids.clone() - } -} - -impl InboundUpgrade for ProtocolConfig -where - TSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - type Output = (Framed, PeerKind); - type Error = Void; - type Future = Pin> + Send>>; - - fn upgrade_inbound(self, socket: TSocket, protocol_id: Self::Info) -> Self::Future { - Box::pin(future::ok(( - Framed::new( - socket, - GossipsubCodec::new(self.max_transmit_size, self.validation_mode), - ), - protocol_id.kind, - ))) - } -} - -impl OutboundUpgrade for ProtocolConfig -where - TSocket: AsyncWrite + AsyncRead + Unpin + Send + 'static, -{ - type Output = (Framed, PeerKind); - type Error = Void; - type Future = Pin> + Send>>; - - fn upgrade_outbound(self, socket: TSocket, protocol_id: Self::Info) -> Self::Future { - Box::pin(future::ok(( - Framed::new( - socket, - GossipsubCodec::new(self.max_transmit_size, self.validation_mode), - ), - protocol_id.kind, - ))) - } -} - -/* Gossip codec for the framing */ - -pub struct GossipsubCodec { - /// Determines the level of validation performed on incoming messages. - validation_mode: ValidationMode, - /// The codec to handle common encoding/decoding of protobuf messages - codec: quick_protobuf_codec::Codec, -} - -impl GossipsubCodec { - pub fn new(max_length: usize, validation_mode: ValidationMode) -> GossipsubCodec { - let codec = quick_protobuf_codec::Codec::new(max_length); - GossipsubCodec { - validation_mode, - codec, - } - } - - /// Verifies a gossipsub message. This returns either a success or failure. All errors - /// are logged, which prevents error handling in the codec and handler. We simply drop invalid - /// messages and log warnings, rather than propagating errors through the codec. - fn verify_signature(message: &proto::Message) -> bool { - use quick_protobuf::MessageWrite; - - let Some(from) = message.from.as_ref() else { - tracing::debug!("Signature verification failed: No source id given"); - return false; - }; - - let Ok(source) = PeerId::from_bytes(from) else { - tracing::debug!("Signature verification failed: Invalid Peer Id"); - return false; - }; - - let Some(signature) = message.signature.as_ref() else { - tracing::debug!("Signature verification failed: No signature provided"); - return false; - }; - - // If there is a key value in the protobuf, use that key otherwise the key must be - // obtained from the inlined source peer_id. - let public_key = match message.key.as_deref().map(PublicKey::try_decode_protobuf) { - Some(Ok(key)) => key, - _ => match PublicKey::try_decode_protobuf(&source.to_bytes()[2..]) { - Ok(v) => v, - Err(_) => { - tracing::warn!("Signature verification failed: No valid public key supplied"); - return false; - } - }, - }; - - // The key must match the peer_id - if source != public_key.to_peer_id() { - tracing::warn!( - "Signature verification failed: Public key doesn't match source peer id" - ); - return false; - } - - // Construct the signature bytes - let mut message_sig = message.clone(); - message_sig.signature = None; - message_sig.key = None; - let mut buf = Vec::with_capacity(message_sig.get_size()); - let mut writer = Writer::new(&mut buf); - message_sig - .write_message(&mut writer) - .expect("Encoding to succeed"); - let mut signature_bytes = SIGNING_PREFIX.to_vec(); - signature_bytes.extend_from_slice(&buf); - public_key.verify(&signature_bytes, signature) - } -} - -impl Encoder for GossipsubCodec { - type Item<'a> = proto::RPC; - type Error = quick_protobuf_codec::Error; - - fn encode(&mut self, item: Self::Item<'_>, dst: &mut BytesMut) -> Result<(), Self::Error> { - self.codec.encode(item, dst) - } -} - -impl Decoder for GossipsubCodec { - type Item = HandlerEvent; - type Error = quick_protobuf_codec::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let Some(rpc) = self.codec.decode(src)? else { - return Ok(None); - }; - // Store valid messages. - let mut messages = Vec::with_capacity(rpc.publish.len()); - // Store any invalid messages. - let mut invalid_messages = Vec::new(); - - for message in rpc.publish.into_iter() { - // Keep track of the type of invalid message. - let mut invalid_kind = None; - let mut verify_signature = false; - let mut verify_sequence_no = false; - let mut verify_source = false; - - match self.validation_mode { - ValidationMode::Strict => { - // Validate everything - verify_signature = true; - verify_sequence_no = true; - verify_source = true; - } - ValidationMode::Permissive => { - // If the fields exist, validate them - if message.signature.is_some() { - verify_signature = true; - } - if message.seqno.is_some() { - verify_sequence_no = true; - } - if message.from.is_some() { - verify_source = true; - } - } - ValidationMode::Anonymous => { - if message.signature.is_some() { - tracing::warn!( - "Signature field was non-empty and anonymous validation mode is set" - ); - invalid_kind = Some(ValidationError::SignaturePresent); - } else if message.seqno.is_some() { - tracing::warn!( - "Sequence number was non-empty and anonymous validation mode is set" - ); - invalid_kind = Some(ValidationError::SequenceNumberPresent); - } else if message.from.is_some() { - tracing::warn!("Message dropped. Message source was non-empty and anonymous validation mode is set"); - invalid_kind = Some(ValidationError::MessageSourcePresent); - } - } - ValidationMode::None => {} - } - - // If the initial validation logic failed, add the message to invalid messages and - // continue processing the others. - if let Some(validation_error) = invalid_kind.take() { - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number: None, // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: None, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, validation_error)); - // proceed to the next message - continue; - } - - // verify message signatures if required - if verify_signature && !GossipsubCodec::verify_signature(&message) { - tracing::warn!("Invalid signature for received message"); - - // Build the invalid message (ignoring further validation of sequence number - // and source) - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number: None, // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: None, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, ValidationError::InvalidSignature)); - // proceed to the next message - continue; - } - - // ensure the sequence number is a u64 - let sequence_number = if verify_sequence_no { - if let Some(seq_no) = message.seqno { - if seq_no.is_empty() { - None - } else if seq_no.len() != 8 { - tracing::debug!( - sequence_number=?seq_no, - sequence_length=%seq_no.len(), - "Invalid sequence number length for received message" - ); - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number: None, // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: message.signature, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, ValidationError::InvalidSequenceNumber)); - // proceed to the next message - continue; - } else { - // valid sequence number - Some(BigEndian::read_u64(&seq_no)) - } - } else { - // sequence number was not present - tracing::debug!("Sequence number not present but expected"); - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number: None, // don't inform the application - topic: TopicHash::from_raw(message.topic), - signature: message.signature, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, ValidationError::EmptySequenceNumber)); - continue; - } - } else { - // Do not verify the sequence number, consider it empty - None - }; - - // Verify the message source if required - let source = if verify_source { - if let Some(bytes) = message.from { - if !bytes.is_empty() { - match PeerId::from_bytes(&bytes) { - Ok(peer_id) => Some(peer_id), // valid peer id - Err(_) => { - // invalid peer id, add to invalid messages - tracing::debug!("Message source has an invalid PeerId"); - let message = RawMessage { - source: None, // don't bother inform the application - data: message.data.unwrap_or_default(), - sequence_number, - topic: TopicHash::from_raw(message.topic), - signature: message.signature, // don't inform the application - key: message.key, - validated: false, - }; - invalid_messages.push((message, ValidationError::InvalidPeerId)); - continue; - } - } - } else { - None - } - } else { - None - } - } else { - None - }; - - // This message has passed all validation, add it to the validated messages. - messages.push(RawMessage { - source, - data: message.data.unwrap_or_default(), - sequence_number, - topic: TopicHash::from_raw(message.topic), - signature: message.signature, - key: message.key, - validated: false, - }); - } - - let mut control_msgs = Vec::new(); - - if let Some(rpc_control) = rpc.control { - // Collect the gossipsub control messages - let ihave_msgs: Vec = rpc_control - .ihave - .into_iter() - .map(|ihave| { - ControlAction::IHave(IHave { - topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), - message_ids: ihave - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - let iwant_msgs: Vec = rpc_control - .iwant - .into_iter() - .map(|iwant| { - ControlAction::IWant(IWant { - message_ids: iwant - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - let graft_msgs: Vec = rpc_control - .graft - .into_iter() - .map(|graft| { - ControlAction::Graft(Graft { - topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), - }) - }) - .collect(); - - let mut prune_msgs = Vec::new(); - - for prune in rpc_control.prune { - // filter out invalid peers - let peers = prune - .peers - .into_iter() - .filter_map(|info| { - info.peer_id - .as_ref() - .and_then(|id| PeerId::from_bytes(id).ok()) - .map(|peer_id| - //TODO signedPeerRecord, see https://github.com/libp2p/specs/pull/217 - PeerInfo { - peer_id: Some(peer_id), - }) - }) - .collect::>(); - - let topic_hash = TopicHash::from_raw(prune.topic_id.unwrap_or_default()); - prune_msgs.push(ControlAction::Prune(Prune { - topic_hash, - peers, - backoff: prune.backoff, - })); - } - - let idontwant_msgs: Vec = rpc_control - .idontwant - .into_iter() - .map(|idontwant| { - ControlAction::IDontWant(IDontWant { - message_ids: idontwant - .message_ids - .into_iter() - .map(MessageId::from) - .collect::>(), - }) - }) - .collect(); - - control_msgs.extend(ihave_msgs); - control_msgs.extend(iwant_msgs); - control_msgs.extend(graft_msgs); - control_msgs.extend(prune_msgs); - control_msgs.extend(idontwant_msgs); - } - - Ok(Some(HandlerEvent::Message { - rpc: Rpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| Subscription { - action: if Some(true) == sub.subscribe { - SubscriptionAction::Subscribe - } else { - SubscriptionAction::Unsubscribe - }, - topic_hash: TopicHash::from_raw(sub.topic_id.unwrap_or_default()), - }) - .collect(), - control_msgs, - }, - invalid_messages, - })) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::config::Config; - use crate::{Behaviour, ConfigBuilder, MessageAuthenticity}; - use crate::{IdentTopic as Topic, Version}; - use libp2p::identity::Keypair; - use quickcheck::*; - - #[derive(Clone, Debug)] - struct Message(RawMessage); - - impl Arbitrary for Message { - fn arbitrary(g: &mut Gen) -> Self { - let keypair = TestKeypair::arbitrary(g); - - // generate an arbitrary GossipsubMessage using the behaviour signing functionality - let config = Config::default(); - let mut gs: Behaviour = - Behaviour::new(MessageAuthenticity::Signed(keypair.0), config).unwrap(); - let mut data_g = quickcheck::Gen::new(10024); - let data = (0..u8::arbitrary(&mut data_g)) - .map(|_| u8::arbitrary(g)) - .collect::>(); - let topic_id = TopicId::arbitrary(g).0; - Message(gs.build_raw_message(topic_id, data).unwrap()) - } - } - - #[derive(Clone, Debug)] - struct TopicId(TopicHash); - - impl Arbitrary for TopicId { - fn arbitrary(g: &mut Gen) -> Self { - let mut data_g = quickcheck::Gen::new(1024); - let topic_string: String = (0..u8::arbitrary(&mut data_g)) - .map(|_| char::arbitrary(g)) - .collect::(); - TopicId(Topic::new(topic_string).into()) - } - } - - #[derive(Clone)] - struct TestKeypair(Keypair); - - impl Arbitrary for TestKeypair { - #[cfg(feature = "rsa")] - fn arbitrary(g: &mut Gen) -> Self { - let keypair = if bool::arbitrary(g) { - // Small enough to be inlined. - Keypair::generate_ed25519() - } else { - // Too large to be inlined. - let mut rsa_key = hex::decode("308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100ef930f41a71288b643c1cbecbf5f72ab53992249e2b00835bf07390b6745419f3848cbcc5b030faa127bc88cdcda1c1d6f3ff699f0524c15ab9d2c9d8015f5d4bd09881069aad4e9f91b8b0d2964d215cdbbae83ddd31a7622a8228acee07079f6e501aea95508fa26c6122816ef7b00ac526d422bd12aed347c37fff6c1c307f3ba57bb28a7f28609e0bdcc839da4eedca39f5d2fa855ba4b0f9c763e9764937db929a1839054642175312a3de2d3405c9d27bdf6505ef471ce85c5e015eee85bf7874b3d512f715de58d0794fd8afe021c197fbd385bb88a930342fac8da31c27166e2edab00fa55dc1c3814448ba38363077f4e8fe2bdea1c081f85f1aa6f02030100010282010028ff427a1aac1a470e7b4879601a6656193d3857ea79f33db74df61e14730e92bf9ffd78200efb0c40937c3356cbe049cd32e5f15be5c96d5febcaa9bd3484d7fded76a25062d282a3856a1b3b7d2c525cdd8434beae147628e21adf241dd64198d5819f310d033743915ba40ea0b6acdbd0533022ad6daa1ff42de51885f9e8bab2306c6ef1181902d1cd7709006eba1ab0587842b724e0519f295c24f6d848907f772ae9a0953fc931f4af16a07df450fb8bfa94572562437056613647818c238a6ff3f606cffa0533e4b8755da33418dfbc64a85110b1a036623c947400a536bb8df65e5ebe46f2dfd0cfc86e7aeeddd7574c253e8fbf755562b3669525d902818100f9fff30c6677b78dd31ec7a634361438457e80be7a7faf390903067ea8355faa78a1204a82b6e99cb7d9058d23c1ecf6cfe4a900137a00cecc0113fd68c5931602980267ea9a95d182d48ba0a6b4d5dd32fdac685cb2e5d8b42509b2eb59c9579ea6a67ccc7547427e2bd1fb1f23b0ccb4dd6ba7d206c8dd93253d70a451701302818100f5530dfef678d73ce6a401ae47043af10a2e3f224c71ae933035ecd68ccbc4df52d72bc6ca2b17e8faf3e548b483a2506c0369ab80df3b137b54d53fac98f95547c2bc245b416e650ce617e0d29db36066f1335a9ba02ad3e0edf9dc3d58fd835835042663edebce81803972696c789012847cb1f854ab2ac0a1bd3867ac7fb502818029c53010d456105f2bf52a9a8482bca2224a5eac74bf3cc1a4d5d291fafcdffd15a6a6448cce8efdd661f6617ca5fc37c8c885cc3374e109ac6049bcbf72b37eabf44602a2da2d4a1237fd145c863e6d75059976de762d9d258c42b0984e2a2befa01c95217c3ee9c736ff209c355466ff99375194eff943bc402ea1d172a1ed02818027175bf493bbbfb8719c12b47d967bf9eac061c90a5b5711172e9095c38bb8cc493c063abffe4bea110b0a2f22ac9311b3947ba31b7ef6bfecf8209eebd6d86c316a2366bbafda7279b2b47d5bb24b6202254f249205dcad347b574433f6593733b806f84316276c1990a016ce1bbdbe5f650325acc7791aefe515ecc60063bd02818100b6a2077f4adcf15a17092d9c4a346d6022ac48f3861b73cf714f84c440a07419a7ce75a73b9cbff4597c53c128bf81e87b272d70428a272d99f90cd9b9ea1033298e108f919c6477400145a102df3fb5601ffc4588203cf710002517bfa24e6ad32f4d09c6b1a995fa28a3104131bedd9072f3b4fb4a5c2056232643d310453f").unwrap(); - Keypair::rsa_from_pkcs8(&mut rsa_key).unwrap() - }; - TestKeypair(keypair) - } - - #[cfg(not(feature = "rsa"))] - fn arbitrary(_g: &mut Gen) -> Self { - // Small enough to be inlined. - TestKeypair(Keypair::generate_ed25519()) - } - } - - impl std::fmt::Debug for TestKeypair { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TestKeypair") - .field("public", &self.0.public()) - .finish() - } - } - - #[test] - /// Test that RPC messages can be encoded and decoded successfully. - fn encode_decode() { - fn prop(message: Message) { - let message = message.0; - - let rpc = crate::types::Rpc { - messages: vec![message.clone()], - subscriptions: vec![], - control_msgs: vec![], - }; - - let mut codec = GossipsubCodec::new(u32::MAX as usize, ValidationMode::Strict); - let mut buf = BytesMut::new(); - codec.encode(rpc.into_protobuf(), &mut buf).unwrap(); - let decoded_rpc = codec.decode(&mut buf).unwrap().unwrap(); - // mark as validated as its a published message - match decoded_rpc { - HandlerEvent::Message { mut rpc, .. } => { - rpc.messages[0].validated = true; - - assert_eq!(vec![message], rpc.messages); - } - _ => panic!("Must decode a message"), - } - } - - QuickCheck::new().quickcheck(prop as fn(_) -> _) - } - - #[test] - fn support_floodsub_with_custom_protocol() { - let protocol_config = ConfigBuilder::default() - .protocol_id("/foosub", Version::V1_1) - .support_floodsub() - .build() - .unwrap() - .protocol_config(); - - assert_eq!(protocol_config.protocol_ids[0].protocol, "/foosub"); - assert_eq!(protocol_config.protocol_ids[1].protocol, "/floodsub/1.0.0"); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/rpc_proto.rs b/beacon_node/lighthouse_network/gossipsub/src/rpc_proto.rs deleted file mode 100644 index f653779ba2..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/rpc_proto.rs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -pub(crate) mod proto { - #![allow(unreachable_pub)] - include!("generated/mod.rs"); - pub use self::gossipsub::pb::{mod_RPC::SubOpts, *}; -} - -#[cfg(test)] -mod test { - use crate::rpc_proto::proto::compat; - use crate::IdentTopic as Topic; - use libp2p::identity::PeerId; - use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; - use rand::Rng; - - #[test] - fn test_multi_topic_message_compatibility() { - let topic1 = Topic::new("t1").hash(); - let topic2 = Topic::new("t2").hash(); - - let new_message1 = super::proto::Message { - from: Some(PeerId::random().to_bytes()), - data: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - seqno: Some(rand::thread_rng().gen::<[u8; 8]>().to_vec()), - topic: topic1.clone().into_string(), - signature: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - key: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - }; - let old_message1 = compat::pb::Message { - from: Some(PeerId::random().to_bytes()), - data: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - seqno: Some(rand::thread_rng().gen::<[u8; 8]>().to_vec()), - topic_ids: vec![topic1.clone().into_string()], - signature: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - key: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - }; - let old_message2 = compat::pb::Message { - from: Some(PeerId::random().to_bytes()), - data: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - seqno: Some(rand::thread_rng().gen::<[u8; 8]>().to_vec()), - topic_ids: vec![topic1.clone().into_string(), topic2.clone().into_string()], - signature: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - key: Some(rand::thread_rng().gen::<[u8; 32]>().to_vec()), - }; - - let mut new_message1b = Vec::with_capacity(new_message1.get_size()); - let mut writer = Writer::new(&mut new_message1b); - new_message1.write_message(&mut writer).unwrap(); - - let mut old_message1b = Vec::with_capacity(old_message1.get_size()); - let mut writer = Writer::new(&mut old_message1b); - old_message1.write_message(&mut writer).unwrap(); - - let mut old_message2b = Vec::with_capacity(old_message2.get_size()); - let mut writer = Writer::new(&mut old_message2b); - old_message2.write_message(&mut writer).unwrap(); - - let mut reader = BytesReader::from_bytes(&old_message1b[..]); - let new_message = - super::proto::Message::from_reader(&mut reader, &old_message1b[..]).unwrap(); - assert_eq!(new_message.topic, topic1.clone().into_string()); - - let mut reader = BytesReader::from_bytes(&old_message2b[..]); - let new_message = - super::proto::Message::from_reader(&mut reader, &old_message2b[..]).unwrap(); - assert_eq!(new_message.topic, topic2.into_string()); - - let mut reader = BytesReader::from_bytes(&new_message1b[..]); - let old_message = - compat::pb::Message::from_reader(&mut reader, &new_message1b[..]).unwrap(); - assert_eq!(old_message.topic_ids, vec![topic1.into_string()]); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/subscription_filter.rs b/beacon_node/lighthouse_network/gossipsub/src/subscription_filter.rs deleted file mode 100644 index 02bb9b4eab..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/subscription_filter.rs +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::types::Subscription; -use crate::TopicHash; -use std::collections::{BTreeSet, HashMap, HashSet}; - -pub trait TopicSubscriptionFilter { - /// Returns true iff the topic is of interest and we can subscribe to it. - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool; - - /// Filters a list of incoming subscriptions and returns a filtered set - /// By default this deduplicates the subscriptions and calls - /// [`Self::filter_incoming_subscription_set`] on the filtered set. - fn filter_incoming_subscriptions<'a>( - &mut self, - subscriptions: &'a [Subscription], - currently_subscribed_topics: &BTreeSet, - ) -> Result, String> { - let mut filtered_subscriptions: HashMap = HashMap::new(); - for subscription in subscriptions { - use std::collections::hash_map::Entry::*; - match filtered_subscriptions.entry(subscription.topic_hash.clone()) { - Occupied(entry) => { - if entry.get().action != subscription.action { - entry.remove(); - } - } - Vacant(entry) => { - entry.insert(subscription); - } - } - } - self.filter_incoming_subscription_set( - filtered_subscriptions.into_values().collect(), - currently_subscribed_topics, - ) - } - - /// Filters a set of deduplicated subscriptions - /// By default this filters the elements based on [`Self::allow_incoming_subscription`]. - fn filter_incoming_subscription_set<'a>( - &mut self, - mut subscriptions: HashSet<&'a Subscription>, - _currently_subscribed_topics: &BTreeSet, - ) -> Result, String> { - subscriptions.retain(|s| { - if self.allow_incoming_subscription(s) { - true - } else { - tracing::debug!(subscription=?s, "Filtered incoming subscription"); - false - } - }); - Ok(subscriptions) - } - - /// Returns true iff we allow an incoming subscription. - /// This is used by the default implementation of filter_incoming_subscription_set to decide - /// whether to filter out a subscription or not. - /// By default this uses can_subscribe to decide the same for incoming subscriptions as for - /// outgoing ones. - fn allow_incoming_subscription(&mut self, subscription: &Subscription) -> bool { - self.can_subscribe(&subscription.topic_hash) - } -} - -//some useful implementers - -/// Allows all subscriptions -#[derive(Default, Clone)] -pub struct AllowAllSubscriptionFilter {} - -impl TopicSubscriptionFilter for AllowAllSubscriptionFilter { - fn can_subscribe(&mut self, _: &TopicHash) -> bool { - true - } -} - -/// Allows only whitelisted subscriptions -#[derive(Default, Clone)] -pub struct WhitelistSubscriptionFilter(pub HashSet); - -impl TopicSubscriptionFilter for WhitelistSubscriptionFilter { - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - self.0.contains(topic_hash) - } -} - -/// Adds a max count to a given subscription filter -pub struct MaxCountSubscriptionFilter { - pub filter: T, - pub max_subscribed_topics: usize, - pub max_subscriptions_per_request: usize, -} - -impl TopicSubscriptionFilter for MaxCountSubscriptionFilter { - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - self.filter.can_subscribe(topic_hash) - } - - fn filter_incoming_subscriptions<'a>( - &mut self, - subscriptions: &'a [Subscription], - currently_subscribed_topics: &BTreeSet, - ) -> Result, String> { - if subscriptions.len() > self.max_subscriptions_per_request { - return Err("too many subscriptions per request".into()); - } - let result = self - .filter - .filter_incoming_subscriptions(subscriptions, currently_subscribed_topics)?; - - use crate::types::SubscriptionAction::*; - - let mut unsubscribed = 0; - let mut new_subscribed = 0; - for s in &result { - let currently_contained = currently_subscribed_topics.contains(&s.topic_hash); - match s.action { - Unsubscribe => { - if currently_contained { - unsubscribed += 1; - } - } - Subscribe => { - if !currently_contained { - new_subscribed += 1; - } - } - } - } - - if new_subscribed + currently_subscribed_topics.len() - > self.max_subscribed_topics + unsubscribed - { - return Err("too many subscribed topics".into()); - } - - Ok(result) - } -} - -/// Combines two subscription filters -pub struct CombinedSubscriptionFilters { - pub filter1: T, - pub filter2: S, -} - -impl TopicSubscriptionFilter for CombinedSubscriptionFilters -where - T: TopicSubscriptionFilter, - S: TopicSubscriptionFilter, -{ - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - self.filter1.can_subscribe(topic_hash) && self.filter2.can_subscribe(topic_hash) - } - - fn filter_incoming_subscription_set<'a>( - &mut self, - subscriptions: HashSet<&'a Subscription>, - currently_subscribed_topics: &BTreeSet, - ) -> Result, String> { - let intermediate = self - .filter1 - .filter_incoming_subscription_set(subscriptions, currently_subscribed_topics)?; - self.filter2 - .filter_incoming_subscription_set(intermediate, currently_subscribed_topics) - } -} - -pub struct CallbackSubscriptionFilter(pub T) -where - T: FnMut(&TopicHash) -> bool; - -impl TopicSubscriptionFilter for CallbackSubscriptionFilter -where - T: FnMut(&TopicHash) -> bool, -{ - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - (self.0)(topic_hash) - } -} - -///A subscription filter that filters topics based on a regular expression. -pub struct RegexSubscriptionFilter(pub regex::Regex); - -impl TopicSubscriptionFilter for RegexSubscriptionFilter { - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - self.0.is_match(topic_hash.as_str()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::types::SubscriptionAction::*; - - #[test] - fn test_filter_incoming_allow_all_with_duplicates() { - let mut filter = AllowAllSubscriptionFilter {}; - - let t1 = TopicHash::from_raw("t1"); - let t2 = TopicHash::from_raw("t2"); - - let old = BTreeSet::from_iter(vec![t1.clone()]); - let subscriptions = vec![ - Subscription { - action: Unsubscribe, - topic_hash: t1.clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t2.clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t2, - }, - Subscription { - action: Subscribe, - topic_hash: t1.clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t1, - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, vec![&subscriptions[4]].into_iter().collect()); - } - - #[test] - fn test_filter_incoming_whitelist() { - let t1 = TopicHash::from_raw("t1"); - let t2 = TopicHash::from_raw("t2"); - - let mut filter = WhitelistSubscriptionFilter(HashSet::from_iter(vec![t1.clone()])); - - let old = Default::default(); - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t1, - }, - Subscription { - action: Subscribe, - topic_hash: t2, - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, vec![&subscriptions[0]].into_iter().collect()); - } - - #[test] - fn test_filter_incoming_too_many_subscriptions_per_request() { - let t1 = TopicHash::from_raw("t1"); - - let mut filter = MaxCountSubscriptionFilter { - filter: AllowAllSubscriptionFilter {}, - max_subscribed_topics: 100, - max_subscriptions_per_request: 2, - }; - - let old = Default::default(); - - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t1.clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t1.clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t1, - }, - ]; - - let result = filter.filter_incoming_subscriptions(&subscriptions, &old); - assert_eq!(result, Err("too many subscriptions per request".into())); - } - - #[test] - fn test_filter_incoming_too_many_subscriptions() { - let t: Vec<_> = (0..4) - .map(|i| TopicHash::from_raw(format!("t{i}"))) - .collect(); - - let mut filter = MaxCountSubscriptionFilter { - filter: AllowAllSubscriptionFilter {}, - max_subscribed_topics: 3, - max_subscriptions_per_request: 2, - }; - - let old = t[0..2].iter().cloned().collect(); - - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t[2].clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t[3].clone(), - }, - ]; - - let result = filter.filter_incoming_subscriptions(&subscriptions, &old); - assert_eq!(result, Err("too many subscribed topics".into())); - } - - #[test] - fn test_filter_incoming_max_subscribed_valid() { - let t: Vec<_> = (0..5) - .map(|i| TopicHash::from_raw(format!("t{i}"))) - .collect(); - - let mut filter = MaxCountSubscriptionFilter { - filter: WhitelistSubscriptionFilter(t.iter().take(4).cloned().collect()), - max_subscribed_topics: 2, - max_subscriptions_per_request: 5, - }; - - let old = t[0..2].iter().cloned().collect(); - - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t[4].clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t[2].clone(), - }, - Subscription { - action: Subscribe, - topic_hash: t[3].clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t[0].clone(), - }, - Subscription { - action: Unsubscribe, - topic_hash: t[1].clone(), - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, subscriptions[1..].iter().collect()); - } - - #[test] - fn test_callback_filter() { - let t1 = TopicHash::from_raw("t1"); - let t2 = TopicHash::from_raw("t2"); - - let mut filter = CallbackSubscriptionFilter(|h| h.as_str() == "t1"); - - let old = Default::default(); - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t1, - }, - Subscription { - action: Subscribe, - topic_hash: t2, - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, vec![&subscriptions[0]].into_iter().collect()); - } - - #[test] - fn test_regex_subscription_filter() { - let t1 = TopicHash::from_raw("tt"); - let t2 = TopicHash::from_raw("et3t3te"); - let t3 = TopicHash::from_raw("abcdefghijklmnopqrsuvwxyz"); - - let mut filter = RegexSubscriptionFilter(regex::Regex::new("t.*t").unwrap()); - - let old = Default::default(); - let subscriptions = vec![ - Subscription { - action: Subscribe, - topic_hash: t1, - }, - Subscription { - action: Subscribe, - topic_hash: t2, - }, - Subscription { - action: Subscribe, - topic_hash: t3, - }, - ]; - - let result = filter - .filter_incoming_subscriptions(&subscriptions, &old) - .unwrap(); - assert_eq!(result, subscriptions[..2].iter().collect()); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/time_cache.rs b/beacon_node/lighthouse_network/gossipsub/src/time_cache.rs deleted file mode 100644 index a3e5c01ac4..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/time_cache.rs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! This implements a time-based LRU cache for checking gossipsub message duplicates. - -use fnv::FnvHashMap; -use std::collections::hash_map::{ - self, - Entry::{Occupied, Vacant}, -}; -use std::collections::VecDeque; -use std::time::Duration; -use web_time::Instant; - -struct ExpiringElement { - /// The element that expires - element: Element, - /// The expire time. - expires: Instant, -} - -pub(crate) struct TimeCache { - /// Mapping a key to its value together with its latest expire time (can be updated through - /// reinserts). - map: FnvHashMap>, - /// An ordered list of keys by expires time. - list: VecDeque>, - /// The time elements remain in the cache. - ttl: Duration, -} - -pub(crate) struct OccupiedEntry<'a, K, V> { - entry: hash_map::OccupiedEntry<'a, K, ExpiringElement>, -} - -impl<'a, K, V> OccupiedEntry<'a, K, V> -where - K: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn into_mut(self) -> &'a mut V { - &mut self.entry.into_mut().element - } -} - -pub(crate) struct VacantEntry<'a, K, V> { - expiration: Instant, - entry: hash_map::VacantEntry<'a, K, ExpiringElement>, - list: &'a mut VecDeque>, -} - -impl<'a, K, V> VacantEntry<'a, K, V> -where - K: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn insert(self, value: V) -> &'a mut V { - self.list.push_back(ExpiringElement { - element: self.entry.key().clone(), - expires: self.expiration, - }); - &mut self - .entry - .insert(ExpiringElement { - element: value, - expires: self.expiration, - }) - .element - } -} - -pub(crate) enum Entry<'a, K: 'a, V: 'a> { - Occupied(OccupiedEntry<'a, K, V>), - Vacant(VacantEntry<'a, K, V>), -} - -impl<'a, K: 'a, V: 'a> Entry<'a, K, V> -where - K: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn or_default(self) -> &'a mut V - where - V: Default, - { - match self { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => entry.insert(V::default()), - } - } -} - -impl TimeCache -where - Key: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn new(ttl: Duration) -> Self { - TimeCache { - map: FnvHashMap::default(), - list: VecDeque::new(), - ttl, - } - } - - fn remove_expired_keys(&mut self, now: Instant) { - while let Some(element) = self.list.pop_front() { - if element.expires > now { - self.list.push_front(element); - break; - } - if let Occupied(entry) = self.map.entry(element.element.clone()) { - if entry.get().expires <= now { - entry.remove(); - } - } - } - } - - pub(crate) fn entry(&mut self, key: Key) -> Entry { - let now = Instant::now(); - self.remove_expired_keys(now); - match self.map.entry(key) { - Occupied(entry) => Entry::Occupied(OccupiedEntry { entry }), - Vacant(entry) => Entry::Vacant(VacantEntry { - expiration: now + self.ttl, - entry, - list: &mut self.list, - }), - } - } - - /// Empties the entire cache. - #[cfg(test)] - pub(crate) fn clear(&mut self) { - self.map.clear(); - self.list.clear(); - } - - pub(crate) fn contains_key(&self, key: &Key) -> bool { - self.map.contains_key(key) - } -} - -pub(crate) struct DuplicateCache(TimeCache); - -impl DuplicateCache -where - Key: Eq + std::hash::Hash + Clone, -{ - pub(crate) fn new(ttl: Duration) -> Self { - Self(TimeCache::new(ttl)) - } - - // Inserts new elements and removes any expired elements. - // - // If the key was not present this returns `true`. If the value was already present this - // returns `false`. - pub(crate) fn insert(&mut self, key: Key) -> bool { - if let Entry::Vacant(entry) = self.0.entry(key) { - entry.insert(()); - true - } else { - false - } - } - - pub(crate) fn contains(&self, key: &Key) -> bool { - self.0.contains_key(key) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn cache_added_entries_exist() { - let mut cache = DuplicateCache::new(Duration::from_secs(10)); - - cache.insert("t"); - cache.insert("e"); - - // Should report that 't' and 't' already exists - assert!(!cache.insert("t")); - assert!(!cache.insert("e")); - } - - #[test] - fn cache_entries_expire() { - let mut cache = DuplicateCache::new(Duration::from_millis(100)); - - cache.insert("t"); - assert!(!cache.insert("t")); - cache.insert("e"); - //assert!(!cache.insert("t")); - assert!(!cache.insert("e")); - // sleep until cache expiry - std::thread::sleep(Duration::from_millis(101)); - // add another element to clear previous cache - cache.insert("s"); - - // should be removed from the cache - assert!(cache.insert("t")); - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/topic.rs b/beacon_node/lighthouse_network/gossipsub/src/topic.rs deleted file mode 100644 index a73496b53f..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/topic.rs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::rpc_proto::proto; -use base64::prelude::*; -use prometheus_client::encoding::EncodeLabelSet; -use quick_protobuf::Writer; -use sha2::{Digest, Sha256}; -use std::fmt; - -/// A generic trait that can be extended for various hashing types for a topic. -pub trait Hasher { - /// The function that takes a topic string and creates a topic hash. - fn hash(topic_string: String) -> TopicHash; -} - -/// A type for representing topics who use the identity hash. -#[derive(Debug, Clone)] -pub struct IdentityHash {} -impl Hasher for IdentityHash { - /// Creates a [`TopicHash`] as a raw string. - fn hash(topic_string: String) -> TopicHash { - TopicHash { hash: topic_string } - } -} - -#[derive(Debug, Clone)] -pub struct Sha256Hash {} -impl Hasher for Sha256Hash { - /// Creates a [`TopicHash`] by SHA256 hashing the topic then base64 encoding the - /// hash. - fn hash(topic_string: String) -> TopicHash { - use quick_protobuf::MessageWrite; - - let topic_descripter = proto::TopicDescriptor { - name: Some(topic_string), - auth: None, - enc: None, - }; - let mut bytes = Vec::with_capacity(topic_descripter.get_size()); - let mut writer = Writer::new(&mut bytes); - topic_descripter - .write_message(&mut writer) - .expect("Encoding to succeed"); - let hash = BASE64_STANDARD.encode(Sha256::digest(&bytes)); - TopicHash { hash } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, EncodeLabelSet)] -pub struct TopicHash { - /// The topic hash. Stored as a string to align with the protobuf API. - hash: String, -} - -impl TopicHash { - pub fn from_raw(hash: impl Into) -> TopicHash { - TopicHash { hash: hash.into() } - } - - pub fn into_string(self) -> String { - self.hash - } - - pub fn as_str(&self) -> &str { - &self.hash - } -} - -/// A gossipsub topic. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Topic { - topic: String, - phantom_data: std::marker::PhantomData, -} - -impl From> for TopicHash { - fn from(topic: Topic) -> TopicHash { - topic.hash() - } -} - -impl Topic { - pub fn new(topic: impl Into) -> Self { - Topic { - topic: topic.into(), - phantom_data: std::marker::PhantomData, - } - } - - pub fn hash(&self) -> TopicHash { - H::hash(self.topic.clone()) - } -} - -impl fmt::Display for Topic { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.topic) - } -} - -impl fmt::Display for TopicHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.hash) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/transform.rs b/beacon_node/lighthouse_network/gossipsub/src/transform.rs deleted file mode 100644 index 6f57d9fc46..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/transform.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! This trait allows of extended user-level decoding that can apply to message-data before a -//! message-id is calculated. -//! -//! This is primarily designed to allow applications to implement their own custom compression -//! algorithms that can be topic-specific. Once the raw data is transformed the message-id is then -//! calculated, allowing for applications to employ message-id functions post compression. - -use crate::{Message, RawMessage, TopicHash}; - -/// A general trait of transforming a [`RawMessage`] into a [`Message`]. The -/// [`RawMessage`] is obtained from the wire and the [`Message`] is used to -/// calculate the [`crate::MessageId`] of the message and is what is sent to the application. -/// -/// The inbound/outbound transforms must be inverses. Applying the inbound transform and then the -/// outbound transform MUST leave the underlying data un-modified. -/// -/// By default, this is the identity transform for all fields in [`Message`]. -pub trait DataTransform { - /// Takes a [`RawMessage`] received and converts it to a [`Message`]. - fn inbound_transform(&self, raw_message: RawMessage) -> Result; - - /// Takes the data to be published (a topic and associated data) transforms the data. The - /// transformed data will then be used to create a [`crate::RawMessage`] to be sent to peers. - fn outbound_transform( - &self, - topic: &TopicHash, - data: Vec, - ) -> Result, std::io::Error>; -} - -/// The default transform, the raw data is propagated as is to the application layer gossipsub. -#[derive(Default, Clone)] -pub struct IdentityTransform; - -impl DataTransform for IdentityTransform { - fn inbound_transform(&self, raw_message: RawMessage) -> Result { - Ok(Message { - source: raw_message.source, - data: raw_message.data, - sequence_number: raw_message.sequence_number, - topic: raw_message.topic, - }) - } - - fn outbound_transform( - &self, - _topic: &TopicHash, - data: Vec, - ) -> Result, std::io::Error> { - Ok(data) - } -} diff --git a/beacon_node/lighthouse_network/gossipsub/src/types.rs b/beacon_node/lighthouse_network/gossipsub/src/types.rs deleted file mode 100644 index f5dac380e3..0000000000 --- a/beacon_node/lighthouse_network/gossipsub/src/types.rs +++ /dev/null @@ -1,882 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! A collection of types using the Gossipsub system. -use crate::metrics::Metrics; -use crate::TopicHash; -use async_channel::{Receiver, Sender}; -use futures::stream::Peekable; -use futures::{Future, Stream, StreamExt}; -use futures_timer::Delay; -use hashlink::LinkedHashMap; -use libp2p::identity::PeerId; -use libp2p::swarm::ConnectionId; -use prometheus_client::encoding::EncodeLabelValue; -use quick_protobuf::MessageWrite; -use std::collections::BTreeSet; -use std::fmt::Debug; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::Instant; -use std::{fmt, pin::Pin}; -use web_time::Duration; - -use crate::rpc_proto::proto; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// The type of messages that have expired while attempting to send to a peer. -#[derive(Clone, Debug, Default)] -pub struct FailedMessages { - /// The number of publish messages that failed to be published in a heartbeat. - pub publish: usize, - /// The number of forward messages that failed to be published in a heartbeat. - pub forward: usize, - /// The number of messages that were failed to be sent to the priority queue as it was full. - pub priority: usize, - /// The number of messages that were failed to be sent to the non-priority queue as it was full. - pub non_priority: usize, -} - -impl FailedMessages { - /// The total number of messages that expired due a timeout. - pub fn total_timeout(&self) -> usize { - self.publish + self.forward - } - - /// The total number of messages that failed due to the queue being full. - pub fn total_queue_full(&self) -> usize { - self.priority + self.non_priority - } - - /// The total failed messages in a heartbeat. - pub fn total(&self) -> usize { - self.total_timeout() + self.total_queue_full() - } -} - -#[derive(Debug)] -/// Validation kinds from the application for received messages. -pub enum MessageAcceptance { - /// The message is considered valid, and it should be delivered and forwarded to the network. - Accept, - /// The message is considered invalid, and it should be rejected and trigger the Pâ‚„ penalty. - Reject, - /// The message is neither delivered nor forwarded to the network, but the router does not - /// trigger the Pâ‚„ penalty. - Ignore, -} - -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct MessageId(pub Vec); - -impl MessageId { - pub fn new(value: &[u8]) -> Self { - Self(value.to_vec()) - } -} - -impl>> From for MessageId { - fn from(value: T) -> Self { - Self(value.into()) - } -} - -impl std::fmt::Display for MessageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex_fmt::HexFmt(&self.0)) - } -} - -impl std::fmt::Debug for MessageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "MessageId({})", hex_fmt::HexFmt(&self.0)) - } -} - -#[derive(Debug, Clone)] -pub(crate) struct PeerConnections { - /// The kind of protocol the peer supports. - pub(crate) kind: PeerKind, - /// Its current connections. - pub(crate) connections: Vec, - /// The rpc sender to the peer. - pub(crate) sender: RpcSender, - /// Subscribed topics. - pub(crate) topics: BTreeSet, - /// IDONTWANT messages received from the peer. - pub(crate) dont_send_received: LinkedHashMap, - /// IDONTWANT messages we sent to the peer. - pub(crate) dont_send_sent: LinkedHashMap, -} - -/// Describes the types of peers that can exist in the gossipsub context. -#[derive(Debug, Clone, PartialEq, Hash, EncodeLabelValue, Eq)] -#[allow(non_camel_case_types)] -pub enum PeerKind { - /// A gossipsub 1.2 peer. - Gossipsubv1_2, - /// A gossipsub 1.1 peer. - Gossipsubv1_1, - /// A gossipsub 1.0 peer. - Gossipsub, - /// A floodsub peer. - Floodsub, - /// The peer doesn't support any of the protocols. - NotSupported, -} - -impl PeerKind { - /// Returns true if peer speaks any gossipsub version. - pub(crate) fn is_gossipsub(&self) -> bool { - matches!( - self, - Self::Gossipsubv1_2 | Self::Gossipsubv1_1 | Self::Gossipsub - ) - } -} - -/// A message received by the gossipsub system and stored locally in caches.. -#[derive(Clone, PartialEq, Eq, Hash, Debug)] -pub struct RawMessage { - /// Id of the peer that published this message. - pub source: Option, - - /// Content of the message. Its meaning is out of scope of this library. - pub data: Vec, - - /// A random sequence number. - pub sequence_number: Option, - - /// The topic this message belongs to - pub topic: TopicHash, - - /// The signature of the message if it's signed. - pub signature: Option>, - - /// The public key of the message if it is signed and the source [`PeerId`] cannot be inlined. - pub key: Option>, - - /// Flag indicating if this message has been validated by the application or not. - pub validated: bool, -} - -impl RawMessage { - /// Calculates the encoded length of this message (used for calculating metrics). - pub fn raw_protobuf_len(&self) -> usize { - let message = proto::Message { - from: self.source.map(|m| m.to_bytes()), - data: Some(self.data.clone()), - seqno: self.sequence_number.map(|s| s.to_be_bytes().to_vec()), - topic: TopicHash::into_string(self.topic.clone()), - signature: self.signature.clone(), - key: self.key.clone(), - }; - message.get_size() - } -} - -impl From for proto::Message { - fn from(raw: RawMessage) -> Self { - proto::Message { - from: raw.source.map(|m| m.to_bytes()), - data: Some(raw.data), - seqno: raw.sequence_number.map(|s| s.to_be_bytes().to_vec()), - topic: TopicHash::into_string(raw.topic), - signature: raw.signature, - key: raw.key, - } - } -} - -/// The message sent to the user after a [`RawMessage`] has been transformed by a -/// [`crate::DataTransform`]. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct Message { - /// Id of the peer that published this message. - pub source: Option, - - /// Content of the message. - pub data: Vec, - - /// A random sequence number. - pub sequence_number: Option, - - /// The topic this message belongs to - pub topic: TopicHash, -} - -impl fmt::Debug for Message { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Message") - .field( - "data", - &format_args!("{:<20}", &hex_fmt::HexFmt(&self.data)), - ) - .field("source", &self.source) - .field("sequence_number", &self.sequence_number) - .field("topic", &self.topic) - .finish() - } -} - -/// A subscription received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Subscription { - /// Action to perform. - pub action: SubscriptionAction, - /// The topic from which to subscribe or unsubscribe. - pub topic_hash: TopicHash, -} - -/// Action that a subscription wants to perform. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum SubscriptionAction { - /// The remote wants to subscribe to the given topic. - Subscribe, - /// The remote wants to unsubscribe from the given topic. - Unsubscribe, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct PeerInfo { - pub(crate) peer_id: Option, - //TODO add this when RFC: Signed Address Records got added to the spec (see pull request - // https://github.com/libp2p/specs/pull/217) - //pub signed_peer_record: ?, -} - -/// A Control message received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ControlAction { - /// Node broadcasts known messages per topic - IHave control message. - IHave(IHave), - /// The node requests specific message ids (peer_id + sequence _number) - IWant control message. - IWant(IWant), - /// The node has been added to the mesh - Graft control message. - Graft(Graft), - /// The node has been removed from the mesh - Prune control message. - Prune(Prune), - /// The node requests us to not forward message ids (peer_id + sequence _number) - IDontWant control message. - IDontWant(IDontWant), -} - -/// Node broadcasts known messages per topic - IHave control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct IHave { - /// The topic of the messages. - pub(crate) topic_hash: TopicHash, - /// A list of known message ids (peer_id + sequence _number) as a string. - pub(crate) message_ids: Vec, -} - -/// The node requests specific message ids (peer_id + sequence _number) - IWant control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct IWant { - /// A list of known message ids (peer_id + sequence _number) as a string. - pub(crate) message_ids: Vec, -} - -/// The node has been added to the mesh - Graft control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Graft { - /// The mesh topic the peer should be added to. - pub(crate) topic_hash: TopicHash, -} - -/// The node has been removed from the mesh - Prune control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Prune { - /// The mesh topic the peer should be removed from. - pub(crate) topic_hash: TopicHash, - /// A list of peers to be proposed to the removed peer as peer exchange - pub(crate) peers: Vec, - /// The backoff time in seconds before we allow to reconnect - pub(crate) backoff: Option, -} - -/// The node requests us to not forward message ids - IDontWant control message. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct IDontWant { - /// A list of known message ids. - pub(crate) message_ids: Vec, -} - -/// A Gossipsub RPC message sent. -#[derive(Debug)] -pub enum RpcOut { - /// Publish a Gossipsub message on network. The [`Delay`] tags the time we attempted to - /// send it. - Publish { message: RawMessage, timeout: Delay }, - /// Forward a Gossipsub message to the network. The [`Delay`] tags the time we attempted to - /// send it. - Forward { message: RawMessage, timeout: Delay }, - /// Subscribe a topic. - Subscribe(TopicHash), - /// Unsubscribe a topic. - Unsubscribe(TopicHash), - /// Send a GRAFT control message. - Graft(Graft), - /// Send a PRUNE control message. - Prune(Prune), - /// Send a IHave control message. - IHave(IHave), - /// Send a IWant control message. - IWant(IWant), - /// Send a IDontWant control message. - IDontWant(IDontWant), -} - -impl RpcOut { - /// Converts the GossipsubRPC into its protobuf format. - // A convenience function to avoid explicitly specifying types. - pub fn into_protobuf(self) -> proto::RPC { - self.into() - } -} - -impl From for proto::RPC { - /// Converts the RPC into protobuf format. - fn from(rpc: RpcOut) -> Self { - match rpc { - RpcOut::Publish { - message, - timeout: _, - } => proto::RPC { - subscriptions: Vec::new(), - publish: vec![message.into()], - control: None, - }, - RpcOut::Forward { - message, - timeout: _, - } => proto::RPC { - publish: vec![message.into()], - subscriptions: Vec::new(), - control: None, - }, - RpcOut::Subscribe(topic) => proto::RPC { - publish: Vec::new(), - subscriptions: vec![proto::SubOpts { - subscribe: Some(true), - topic_id: Some(topic.into_string()), - }], - control: None, - }, - RpcOut::Unsubscribe(topic) => proto::RPC { - publish: Vec::new(), - subscriptions: vec![proto::SubOpts { - subscribe: Some(false), - topic_id: Some(topic.into_string()), - }], - control: None, - }, - RpcOut::IHave(IHave { - topic_hash, - message_ids, - }) => proto::RPC { - publish: Vec::new(), - subscriptions: Vec::new(), - control: Some(proto::ControlMessage { - ihave: vec![proto::ControlIHave { - topic_id: Some(topic_hash.into_string()), - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }], - iwant: vec![], - graft: vec![], - prune: vec![], - idontwant: vec![], - }), - }, - RpcOut::IWant(IWant { message_ids }) => proto::RPC { - publish: Vec::new(), - subscriptions: Vec::new(), - control: Some(proto::ControlMessage { - ihave: vec![], - iwant: vec![proto::ControlIWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }], - graft: vec![], - prune: vec![], - idontwant: vec![], - }), - }, - RpcOut::Graft(Graft { topic_hash }) => proto::RPC { - publish: Vec::new(), - subscriptions: vec![], - control: Some(proto::ControlMessage { - ihave: vec![], - iwant: vec![], - graft: vec![proto::ControlGraft { - topic_id: Some(topic_hash.into_string()), - }], - prune: vec![], - idontwant: vec![], - }), - }, - RpcOut::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - proto::RPC { - publish: Vec::new(), - subscriptions: vec![], - control: Some(proto::ControlMessage { - ihave: vec![], - iwant: vec![], - graft: vec![], - prune: vec![proto::ControlPrune { - topic_id: Some(topic_hash.into_string()), - peers: peers - .into_iter() - .map(|info| proto::PeerInfo { - peer_id: info.peer_id.map(|id| id.to_bytes()), - // TODO, see https://github.com/libp2p/specs/pull/217 - signed_peer_record: None, - }) - .collect(), - backoff, - }], - idontwant: vec![], - }), - } - } - RpcOut::IDontWant(IDontWant { message_ids }) => proto::RPC { - publish: Vec::new(), - subscriptions: Vec::new(), - control: Some(proto::ControlMessage { - ihave: vec![], - iwant: vec![], - graft: vec![], - prune: vec![], - idontwant: vec![proto::ControlIDontWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }], - }), - }, - } - } -} - -/// An RPC received/sent. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct Rpc { - /// List of messages that were part of this RPC query. - pub messages: Vec, - /// List of subscriptions. - pub subscriptions: Vec, - /// List of Gossipsub control messages. - pub control_msgs: Vec, -} - -impl Rpc { - /// Converts the GossipsubRPC into its protobuf format. - // A convenience function to avoid explicitly specifying types. - pub fn into_protobuf(self) -> proto::RPC { - self.into() - } -} - -impl From for proto::RPC { - /// Converts the RPC into protobuf format. - fn from(rpc: Rpc) -> Self { - // Messages - let mut publish = Vec::new(); - - for message in rpc.messages.into_iter() { - let message = proto::Message { - from: message.source.map(|m| m.to_bytes()), - data: Some(message.data), - seqno: message.sequence_number.map(|s| s.to_be_bytes().to_vec()), - topic: TopicHash::into_string(message.topic), - signature: message.signature, - key: message.key, - }; - - publish.push(message); - } - - // subscriptions - let subscriptions = rpc - .subscriptions - .into_iter() - .map(|sub| proto::SubOpts { - subscribe: Some(sub.action == SubscriptionAction::Subscribe), - topic_id: Some(sub.topic_hash.into_string()), - }) - .collect::>(); - - // control messages - let mut control = proto::ControlMessage { - ihave: Vec::new(), - iwant: Vec::new(), - graft: Vec::new(), - prune: Vec::new(), - idontwant: Vec::new(), - }; - - let empty_control_msg = rpc.control_msgs.is_empty(); - - for action in rpc.control_msgs { - match action { - // collect all ihave messages - ControlAction::IHave(IHave { - topic_hash, - message_ids, - }) => { - let rpc_ihave = proto::ControlIHave { - topic_id: Some(topic_hash.into_string()), - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.ihave.push(rpc_ihave); - } - ControlAction::IWant(IWant { message_ids }) => { - let rpc_iwant = proto::ControlIWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.iwant.push(rpc_iwant); - } - ControlAction::Graft(Graft { topic_hash }) => { - let rpc_graft = proto::ControlGraft { - topic_id: Some(topic_hash.into_string()), - }; - control.graft.push(rpc_graft); - } - ControlAction::Prune(Prune { - topic_hash, - peers, - backoff, - }) => { - let rpc_prune = proto::ControlPrune { - topic_id: Some(topic_hash.into_string()), - peers: peers - .into_iter() - .map(|info| proto::PeerInfo { - peer_id: info.peer_id.map(|id| id.to_bytes()), - // TODO, see https://github.com/libp2p/specs/pull/217 - signed_peer_record: None, - }) - .collect(), - backoff, - }; - control.prune.push(rpc_prune); - } - ControlAction::IDontWant(IDontWant { message_ids }) => { - let rpc_idontwant = proto::ControlIDontWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.idontwant.push(rpc_idontwant); - } - } - } - - proto::RPC { - subscriptions, - publish, - control: if empty_control_msg { - None - } else { - Some(control) - }, - } - } -} - -impl fmt::Debug for Rpc { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut b = f.debug_struct("GossipsubRpc"); - if !self.messages.is_empty() { - b.field("messages", &self.messages); - } - if !self.subscriptions.is_empty() { - b.field("subscriptions", &self.subscriptions); - } - if !self.control_msgs.is_empty() { - b.field("control_msgs", &self.control_msgs); - } - b.finish() - } -} - -impl PeerKind { - pub fn as_static_ref(&self) -> &'static str { - match self { - Self::NotSupported => "Not Supported", - Self::Floodsub => "Floodsub", - Self::Gossipsub => "Gossipsub v1.0", - Self::Gossipsubv1_1 => "Gossipsub v1.1", - Self::Gossipsubv1_2 => "Gossipsub v1.2", - } - } -} - -impl AsRef for PeerKind { - fn as_ref(&self) -> &str { - self.as_static_ref() - } -} - -impl fmt::Display for PeerKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_ref()) - } -} - -/// `RpcOut` sender that is priority aware. -#[derive(Debug, Clone)] -pub(crate) struct RpcSender { - cap: usize, - len: Arc, - pub(crate) priority_sender: Sender, - pub(crate) non_priority_sender: Sender, - priority_receiver: Receiver, - non_priority_receiver: Receiver, -} - -impl RpcSender { - /// Create a RpcSender. - pub(crate) fn new(cap: usize) -> RpcSender { - let (priority_sender, priority_receiver) = async_channel::unbounded(); - let (non_priority_sender, non_priority_receiver) = async_channel::bounded(cap / 2); - let len = Arc::new(AtomicUsize::new(0)); - RpcSender { - cap: cap / 2, - len, - priority_sender, - non_priority_sender, - priority_receiver, - non_priority_receiver, - } - } - - /// Create a new Receiver to the sender. - pub(crate) fn new_receiver(&self) -> RpcReceiver { - RpcReceiver { - priority_len: self.len.clone(), - priority: self.priority_receiver.clone().peekable(), - non_priority: self.non_priority_receiver.clone().peekable(), - } - } - - /// Send a `RpcOut::Graft` message to the `RpcReceiver` - /// this is high priority. - pub(crate) fn graft(&mut self, graft: Graft) { - self.priority_sender - .try_send(RpcOut::Graft(graft)) - .expect("Channel is unbounded and should always be open"); - } - - /// Send a `RpcOut::Prune` message to the `RpcReceiver` - /// this is high priority. - pub(crate) fn prune(&mut self, prune: Prune) { - self.priority_sender - .try_send(RpcOut::Prune(prune)) - .expect("Channel is unbounded and should always be open"); - } - - /// Send a `RpcOut::IHave` message to the `RpcReceiver` - /// this is low priority, if the queue is full an Err is returned. - #[allow(clippy::result_large_err)] - pub(crate) fn ihave(&mut self, ihave: IHave) -> Result<(), RpcOut> { - self.non_priority_sender - .try_send(RpcOut::IHave(ihave)) - .map_err(|err| err.into_inner()) - } - - /// Send a `RpcOut::IHave` message to the `RpcReceiver` - /// this is low priority, if the queue is full an Err is returned. - #[allow(clippy::result_large_err)] - pub(crate) fn iwant(&mut self, iwant: IWant) -> Result<(), RpcOut> { - self.non_priority_sender - .try_send(RpcOut::IWant(iwant)) - .map_err(|err| err.into_inner()) - } - - /// Send a `RpcOut::IWant` message to the `RpcReceiver` - /// this is low priority, if the queue is full an Err is returned. - #[allow(clippy::result_large_err)] - pub(crate) fn idontwant(&mut self, idontwant: IDontWant) -> Result<(), RpcOut> { - self.non_priority_sender - .try_send(RpcOut::IDontWant(idontwant)) - .map_err(|err| err.into_inner()) - } - - /// Send a `RpcOut::Subscribe` message to the `RpcReceiver` - /// this is high priority. - pub(crate) fn subscribe(&mut self, topic: TopicHash) { - self.priority_sender - .try_send(RpcOut::Subscribe(topic)) - .expect("Channel is unbounded and should always be open"); - } - - /// Send a `RpcOut::Unsubscribe` message to the `RpcReceiver` - /// this is high priority. - pub(crate) fn unsubscribe(&mut self, topic: TopicHash) { - self.priority_sender - .try_send(RpcOut::Unsubscribe(topic)) - .expect("Channel is unbounded and should always be open"); - } - - /// Send a `RpcOut::Publish` message to the `RpcReceiver` - /// this is high priority. If message sending fails, an `Err` is returned. - pub(crate) fn publish( - &mut self, - message: RawMessage, - timeout: Duration, - metrics: Option<&mut Metrics>, - ) -> Result<(), ()> { - if self.len.load(Ordering::Relaxed) >= self.cap { - return Err(()); - } - self.priority_sender - .try_send(RpcOut::Publish { - message: message.clone(), - timeout: Delay::new(timeout), - }) - .expect("Channel is unbounded and should always be open"); - self.len.fetch_add(1, Ordering::Relaxed); - - if let Some(m) = metrics { - m.msg_sent(&message.topic, message.raw_protobuf_len()); - } - - Ok(()) - } - - /// Send a `RpcOut::Forward` message to the `RpcReceiver` - /// this is high priority. If the queue is full the message is discarded. - pub(crate) fn forward( - &mut self, - message: RawMessage, - timeout: Duration, - metrics: Option<&mut Metrics>, - ) -> Result<(), ()> { - self.non_priority_sender - .try_send(RpcOut::Forward { - message: message.clone(), - timeout: Delay::new(timeout), - }) - .map_err(|_| ())?; - - if let Some(m) = metrics { - m.msg_sent(&message.topic, message.raw_protobuf_len()); - } - - Ok(()) - } - - /// Returns the current size of the priority queue. - pub(crate) fn priority_len(&self) -> usize { - self.len.load(Ordering::Relaxed) - } - - /// Returns the current size of the non-priority queue. - pub(crate) fn non_priority_len(&self) -> usize { - self.non_priority_sender.len() - } -} - -/// `RpcOut` sender that is priority aware. -#[derive(Debug)] -pub struct RpcReceiver { - /// The maximum length of the priority queue. - pub(crate) priority_len: Arc, - /// The priority queue receiver. - pub(crate) priority: Peekable>, - /// The non priority queue receiver. - pub(crate) non_priority: Peekable>, -} - -impl RpcReceiver { - // Peek the next message in the queues and return it if its timeout has elapsed. - // Returns `None` if there aren't any more messages on the stream or none is stale. - pub(crate) fn poll_stale(&mut self, cx: &mut Context<'_>) -> Poll> { - // Peek priority queue. - let priority = match Pin::new(&mut self.priority).poll_peek_mut(cx) { - Poll::Ready(Some(RpcOut::Publish { - message: _, - ref mut timeout, - })) => { - if Pin::new(timeout).poll(cx).is_ready() { - // Return the message. - let dropped = futures::ready!(self.priority.poll_next_unpin(cx)) - .expect("There should be a message"); - return Poll::Ready(Some(dropped)); - } - Poll::Ready(None) - } - poll => poll, - }; - - let non_priority = match Pin::new(&mut self.non_priority).poll_peek_mut(cx) { - Poll::Ready(Some(RpcOut::Forward { - message: _, - ref mut timeout, - })) => { - if Pin::new(timeout).poll(cx).is_ready() { - // Return the message. - let dropped = futures::ready!(self.non_priority.poll_next_unpin(cx)) - .expect("There should be a message"); - return Poll::Ready(Some(dropped)); - } - Poll::Ready(None) - } - poll => poll, - }; - - match (priority, non_priority) { - (Poll::Ready(None), Poll::Ready(None)) => Poll::Ready(None), - _ => Poll::Pending, - } - } - - /// Poll queues and return true if both are empty. - pub(crate) fn poll_is_empty(&mut self, cx: &mut Context<'_>) -> bool { - matches!( - ( - Pin::new(&mut self.priority).poll_peek(cx), - Pin::new(&mut self.non_priority).poll_peek(cx), - ), - (Poll::Ready(None), Poll::Ready(None)) - ) - } -} - -impl Stream for RpcReceiver { - type Item = RpcOut; - - fn poll_next( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - // The priority queue is first polled. - if let Poll::Ready(rpc) = Pin::new(&mut self.priority).poll_next(cx) { - if let Some(RpcOut::Publish { .. }) = rpc { - self.priority_len.fetch_sub(1, Ordering::Relaxed); - } - return Poll::Ready(rpc); - } - // Then we poll the non priority. - Pin::new(&mut self.non_priority).poll_next(cx) - } -} diff --git a/beacon_node/lighthouse_network/src/discovery/enr.rs b/beacon_node/lighthouse_network/src/discovery/enr.rs index 8067711954..e70c8047e0 100644 --- a/beacon_node/lighthouse_network/src/discovery/enr.rs +++ b/beacon_node/lighthouse_network/src/discovery/enr.rs @@ -9,13 +9,13 @@ use crate::NetworkConfig; use alloy_rlp::bytes::Bytes; use libp2p::identity::Keypair; use lighthouse_version::{client_name, version}; -use slog::{debug, warn}; use ssz::{Decode, Encode}; use ssz_types::BitVector; use std::fs::File; use std::io::prelude::*; use std::path::Path; use std::str::FromStr; +use tracing::{debug, warn}; use types::{ChainSpec, EnrForkId, EthSpec}; use super::enr_ext::{EnrExt, QUIC6_ENR_KEY, QUIC_ENR_KEY}; @@ -99,20 +99,19 @@ pub fn use_or_load_enr( enr_key: &CombinedKey, local_enr: &mut Enr, config: &NetworkConfig, - log: &slog::Logger, ) -> Result<(), String> { let enr_f = config.network_dir.join(ENR_FILENAME); if let Ok(mut enr_file) = File::open(enr_f.clone()) { let mut enr_string = String::new(); match enr_file.read_to_string(&mut enr_string) { - Err(_) => debug!(log, "Could not read ENR from file"), + Err(_) => debug!("Could not read ENR from file"), Ok(_) => { match Enr::from_str(&enr_string) { Ok(disk_enr) => { // if the same node id, then we may need to update our sequence number if local_enr.node_id() == disk_enr.node_id() { if compare_enr(local_enr, &disk_enr) { - debug!(log, "ENR loaded from disk"; "file" => ?enr_f); + debug!(file = ?enr_f,"ENR loaded from disk"); // the stored ENR has the same configuration, use it *local_enr = disk_enr; return Ok(()); @@ -125,18 +124,18 @@ pub fn use_or_load_enr( local_enr.set_seq(new_seq_no, enr_key).map_err(|e| { format!("Could not update ENR sequence number: {:?}", e) })?; - debug!(log, "ENR sequence number increased"; "seq" => new_seq_no); + debug!(seq = new_seq_no, "ENR sequence number increased"); } } Err(e) => { - warn!(log, "ENR from file could not be decoded"; "error" => ?e); + warn!(error = ?e,"ENR from file could not be decoded"); } } } } } - save_enr_to_disk(&config.network_dir, local_enr, log); + save_enr_to_disk(&config.network_dir, local_enr); Ok(()) } @@ -150,7 +149,6 @@ pub fn build_or_load_enr( local_key: Keypair, config: &NetworkConfig, enr_fork_id: &EnrForkId, - log: &slog::Logger, spec: &ChainSpec, ) -> Result { // Build the local ENR. @@ -159,7 +157,7 @@ pub fn build_or_load_enr( let enr_key = CombinedKey::from_libp2p(local_key)?; let mut local_enr = build_enr::(&enr_key, config, enr_fork_id, spec)?; - use_or_load_enr(&enr_key, &mut local_enr, config, log)?; + use_or_load_enr(&enr_key, &mut local_enr, config)?; Ok(local_enr) } @@ -314,18 +312,19 @@ pub fn load_enr_from_disk(dir: &Path) -> Result { } /// Saves an ENR to disk -pub fn save_enr_to_disk(dir: &Path, enr: &Enr, log: &slog::Logger) { +pub fn save_enr_to_disk(dir: &Path, enr: &Enr) { let _ = std::fs::create_dir_all(dir); match File::create(dir.join(Path::new(ENR_FILENAME))) .and_then(|mut f| f.write_all(enr.to_base64().as_bytes())) { Ok(_) => { - debug!(log, "ENR written to disk"); + debug!("ENR written to disk"); } Err(e) => { warn!( - log, - "Could not write ENR to file"; "file" => format!("{:?}{:?}",dir, ENR_FILENAME), "error" => %e + file = format!("{:?}{:?}",dir, ENR_FILENAME), + error = %e, + "Could not write ENR to file" ); } } diff --git a/beacon_node/lighthouse_network/src/discovery/mod.rs b/beacon_node/lighthouse_network/src/discovery/mod.rs index 33c7775ae2..ad54c6b8b1 100644 --- a/beacon_node/lighthouse_network/src/discovery/mod.rs +++ b/beacon_node/lighthouse_network/src/discovery/mod.rs @@ -31,8 +31,8 @@ pub use libp2p::{ SubstreamProtocol, ToSwarm, }, }; +use logging::crit; use lru::LruCache; -use slog::{crit, debug, error, info, trace, warn}; use ssz::Encode; use std::num::NonZeroUsize; use std::{ @@ -45,6 +45,7 @@ use std::{ time::{Duration, Instant}, }; use tokio::sync::mpsc; +use tracing::{debug, error, info, trace, warn}; use types::{ChainSpec, EnrForkId, EthSpec}; mod subnet_predicate; @@ -192,8 +193,6 @@ pub struct Discovery { /// Specifies whether various port numbers should be updated after the discovery service has been started update_ports: UpdatePorts, - /// Logger for the discovery behaviour. - log: slog::Logger, spec: Arc, } @@ -203,11 +202,8 @@ impl Discovery { local_key: Keypair, config: &NetworkConfig, network_globals: Arc>, - log: &slog::Logger, spec: &ChainSpec, ) -> Result { - let log = log.clone(); - let enr_dir = match config.network_dir.to_str() { Some(path) => String::from(path), None => String::from(""), @@ -216,9 +212,11 @@ impl Discovery { let local_enr = network_globals.local_enr.read().clone(); let local_node_id = local_enr.node_id(); - info!(log, "ENR Initialised"; "enr" => local_enr.to_base64(), "seq" => local_enr.seq(), "id"=> %local_enr.node_id(), - "ip4" => ?local_enr.ip4(), "udp4"=> ?local_enr.udp4(), "tcp4" => ?local_enr.tcp4(), "tcp6" => ?local_enr.tcp6(), "udp6" => ?local_enr.udp6(), - "quic4" => ?local_enr.quic4(), "quic6" => ?local_enr.quic6() + info!( + enr = local_enr.to_base64(), seq = local_enr.seq(), id = %local_enr.node_id(), + ip4 = ?local_enr.ip4(), udp4= ?local_enr.udp4(), tcp4 = ?local_enr.tcp4(), tcp6 = ?local_enr.tcp6(), udp6 = ?local_enr.udp6(), + quic4 = ?local_enr.quic4(), quic6 = ?local_enr.quic6(), + "ENR Initialised" ); // convert the keypair into an ENR key @@ -234,22 +232,20 @@ impl Discovery { continue; } debug!( - log, - "Adding node to routing table"; - "node_id" => %bootnode_enr.node_id(), - "peer_id" => %bootnode_enr.peer_id(), - "ip" => ?bootnode_enr.ip4(), - "udp" => ?bootnode_enr.udp4(), - "tcp" => ?bootnode_enr.tcp4(), - "quic" => ?bootnode_enr.quic4() + node_id = %bootnode_enr.node_id(), + peer_id = %bootnode_enr.peer_id(), + ip = ?bootnode_enr.ip4(), + udp = ?bootnode_enr.udp4(), + tcp = ?bootnode_enr.tcp4(), + quic = bootnode_enr.quic4(), + "Adding node to routing table" ); let repr = bootnode_enr.to_string(); let _ = discv5.add_enr(bootnode_enr).map_err(|e| { error!( - log, - "Could not add peer to the local routing table"; - "addr" => repr, - "error" => e.to_string(), + addr = repr, + error = e.to_string(), + "Could not add peer to the local routing table" ) }); } @@ -257,14 +253,14 @@ impl Discovery { // Start the discv5 service and obtain an event stream let event_stream = if !config.disable_discovery { discv5.start().map_err(|e| e.to_string()).await?; - debug!(log, "Discovery service started"); + debug!("Discovery service started"); EventStream::Awaiting(Box::pin(discv5.event_stream())) } else { EventStream::InActive }; if !config.boot_nodes_multiaddr.is_empty() { - info!(log, "Contacting Multiaddr boot-nodes for their ENR"); + info!("Contacting Multiaddr boot-nodes for their ENR"); } // get futures for requesting the Enrs associated to these multiaddr and wait for their @@ -286,26 +282,28 @@ impl Discovery { match result { Ok(enr) => { debug!( - log, - "Adding node to routing table"; - "node_id" => %enr.node_id(), - "peer_id" => %enr.peer_id(), - "ip" => ?enr.ip4(), - "udp" => ?enr.udp4(), - "tcp" => ?enr.tcp4(), - "quic" => ?enr.quic4() + node_id = %enr.node_id(), + peer_id = %enr.peer_id(), + ip4 = ?enr.ip4(), + udp4 = ?enr.udp4(), + tcp4 = ?enr.tcp4(), + quic4 = ?enr.quic4(), + "Adding node to routing table" ); let _ = discv5.add_enr(enr).map_err(|e| { error!( - log, - "Could not add peer to the local routing table"; - "addr" => original_addr.to_string(), - "error" => e.to_string(), + addr = original_addr.to_string(), + error = e.to_string(), + "Could not add peer to the local routing table" ) }); } Err(e) => { - error!(log, "Error getting mapping to ENR"; "multiaddr" => original_addr.to_string(), "error" => e.to_string()) + error!( + multiaddr = original_addr.to_string(), + error = e.to_string(), + "Error getting mapping to ENR" + ) } } } @@ -327,7 +325,6 @@ impl Discovery { event_stream, started: !config.disable_discovery, update_ports, - log, enr_dir, spec: Arc::new(spec.clone()), }) @@ -358,7 +355,7 @@ impl Discovery { } // Immediately start a FindNode query let target_peers = std::cmp::min(FIND_NODE_QUERY_CLOSEST_PEERS, target_peers); - debug!(self.log, "Starting a peer discovery request"; "target_peers" => target_peers ); + debug!(target_peers, "Starting a peer discovery request"); self.find_peer_active = true; self.start_query(QueryType::FindPeers, target_peers, |_| true); } @@ -370,9 +367,8 @@ impl Discovery { return; } trace!( - self.log, - "Starting discovery query for subnets"; - "subnets" => ?subnets_to_discover.iter().map(|s| s.subnet).collect::>() + subnets = ?subnets_to_discover.iter().map(|s| s.subnet).collect::>(), + "Starting discovery query for subnets" ); for subnet in subnets_to_discover { self.add_subnet_query(subnet.subnet, subnet.min_ttl, 0); @@ -386,9 +382,8 @@ impl Discovery { if let Err(e) = self.discv5.add_enr(enr) { debug!( - self.log, - "Could not add peer to the local routing table"; - "error" => %e + error = %e, + "Could not add peer to the local routing table" ) } } @@ -427,7 +422,7 @@ impl Discovery { // replace the global version *self.network_globals.local_enr.write() = self.discv5.local_enr(); // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); Ok(true) } @@ -463,7 +458,7 @@ impl Discovery { // replace the global version *self.network_globals.local_enr.write() = self.discv5.local_enr(); // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); Ok(true) } @@ -475,7 +470,7 @@ impl Discovery { const IS_TCP: bool = false; if self.discv5.update_local_enr_socket(socket_addr, IS_TCP) { // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); } *self.network_globals.local_enr.write() = self.discv5.local_enr(); Ok(()) @@ -561,7 +556,7 @@ impl Discovery { *self.network_globals.local_enr.write() = self.discv5.local_enr(); // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); Ok(()) } @@ -575,10 +570,11 @@ impl Discovery { format!("{:?}", enr_fork_id.next_fork_epoch) }; - info!(self.log, "Updating the ENR fork version"; - "fork_digest" => ?enr_fork_id.fork_digest, - "next_fork_version" => ?enr_fork_id.next_fork_version, - "next_fork_epoch" => next_fork_epoch_log, + info!( + fork_digest = ?enr_fork_id.fork_digest, + next_fork_version = ?enr_fork_id.next_fork_version, + next_fork_epoch = next_fork_epoch_log, + "Updating the ENR fork version" ); let _ = self @@ -586,9 +582,8 @@ impl Discovery { .enr_insert::(ETH2_ENR_KEY, &enr_fork_id.as_ssz_bytes().into()) .map_err(|e| { warn!( - self.log, - "Could not update eth2 ENR field"; - "error" => ?e + error = ?e, + "Could not update eth2 ENR field" ) }); @@ -596,7 +591,7 @@ impl Discovery { *self.network_globals.local_enr.write() = self.discv5.local_enr(); // persist modified enr to disk - enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr()); } // Bans a peer and it's associated seen IP addresses. @@ -642,10 +637,7 @@ impl Discovery { fn add_subnet_query(&mut self, subnet: Subnet, min_ttl: Option, retries: usize) { // remove the entry and complete the query if greater than the maximum search count if retries > MAX_DISCOVERY_RETRY { - debug!( - self.log, - "Subnet peer discovery did not find sufficient peers. Reached max retry limit" - ); + debug!("Subnet peer discovery did not find sufficient peers. Reached max retry limit"); return; } @@ -666,7 +658,7 @@ impl Discovery { } if !found { // update the metrics and insert into the queue. - trace!(self.log, "Queuing subnet query"; "subnet" => ?subnet, "retries" => retries); + trace!(?subnet, retries, "Queuing subnet query"); self.queued_queries.push_back(SubnetQuery { subnet, min_ttl, @@ -737,19 +729,21 @@ impl Discovery { .count(); if peers_on_subnet >= TARGET_SUBNET_PEERS { - debug!(self.log, "Discovery ignored"; - "reason" => "Already connected to desired peers", - "connected_peers_on_subnet" => peers_on_subnet, - "target_subnet_peers" => TARGET_SUBNET_PEERS, + debug!( + reason = "Already connected to desired peers", + connected_peers_on_subnet = peers_on_subnet, + target_subnet_peers = TARGET_SUBNET_PEERS, + "Discovery ignored" ); return false; } let target_peers = TARGET_SUBNET_PEERS.saturating_sub(peers_on_subnet); - trace!(self.log, "Discovery query started for subnet"; - "subnet_query" => ?subnet_query, - "connected_peers_on_subnet" => peers_on_subnet, - "peers_to_find" => target_peers, + trace!( + ?subnet_query, + connected_peers_on_subnet = peers_on_subnet, + peers_to_find = target_peers, + "Discovery query started for subnet" ); filtered_subnets.push(subnet_query.subnet); @@ -760,13 +754,11 @@ impl Discovery { // Only start a discovery query if we have a subnet to look for. if !filtered_subnet_queries.is_empty() { // build the subnet predicate as a combination of the eth2_fork_predicate and the subnet predicate - let subnet_predicate = - subnet_predicate::(filtered_subnets, &self.log, self.spec.clone()); + let subnet_predicate = subnet_predicate::(filtered_subnets, self.spec.clone()); debug!( - self.log, - "Starting grouped subnet query"; - "subnets" => ?filtered_subnet_queries, + subnets = ?filtered_subnet_queries, + "Starting grouped subnet query" ); self.start_query( QueryType::Subnet(filtered_subnet_queries), @@ -790,7 +782,7 @@ impl Discovery { let enr_fork_id = match self.local_enr().eth2() { Ok(v) => v, Err(e) => { - crit!(self.log, "Local ENR has no fork id"; "error" => e); + crit!(error = e, "Local ENR has no fork id"); return; } }; @@ -831,10 +823,10 @@ impl Discovery { self.find_peer_active = false; match query.result { Ok(r) if r.is_empty() => { - debug!(self.log, "Discovery query yielded no results."); + debug!("Discovery query yielded no results."); } Ok(r) => { - debug!(self.log, "Discovery query completed"; "peers_found" => r.len()); + debug!(peers_found = r.len(), "Discovery query completed"); let results = r .into_iter() .map(|enr| { @@ -846,7 +838,7 @@ impl Discovery { return Some(results); } Err(e) => { - warn!(self.log, "Discovery query failed"; "error" => %e); + warn!(error = %e, "Discovery query failed"); } } } @@ -855,13 +847,20 @@ impl Discovery { queries.iter().map(|query| query.subnet).collect(); match query.result { Ok(r) if r.is_empty() => { - debug!(self.log, "Grouped subnet discovery query yielded no results."; "subnets_searched_for" => ?subnets_searched_for); + debug!( + ?subnets_searched_for, + "Grouped subnet discovery query yielded no results." + ); queries.iter().for_each(|query| { self.add_subnet_query(query.subnet, query.min_ttl, query.retries + 1); }) } Ok(r) => { - debug!(self.log, "Peer grouped subnet discovery request completed"; "peers_found" => r.len(), "subnets_searched_for" => ?subnets_searched_for); + debug!( + peers_found = r.len(), + ?subnets_searched_for, + "Peer grouped subnet discovery request completed" + ); let mut mapped_results = HashMap::new(); @@ -888,11 +887,8 @@ impl Discovery { self.add_subnet_query(query.subnet, query.min_ttl, query.retries + 1); // Check the specific subnet against the enr - let subnet_predicate = subnet_predicate::( - vec![query.subnet], - &self.log, - self.spec.clone(), - ); + let subnet_predicate = + subnet_predicate::(vec![query.subnet], self.spec.clone()); r.clone() .into_iter() @@ -941,7 +937,7 @@ impl Discovery { } } Err(e) => { - warn!(self.log,"Grouped subnet discovery query failed"; "subnets_searched_for" => ?subnets_searched_for, "error" => %e); + warn!(?subnets_searched_for, error = %e,"Grouped subnet discovery query failed"); } } } @@ -1020,11 +1016,11 @@ impl NetworkBehaviour for Discovery { if let Poll::Ready(event_stream) = fut.poll_unpin(cx) { match event_stream { Ok(stream) => { - debug!(self.log, "Discv5 event stream ready"); + debug!("Discv5 event stream ready"); self.event_stream = EventStream::Present(stream); } Err(e) => { - slog::crit!(self.log, "Discv5 event stream failed"; "error" => %e); + crit!(error = %e, "Discv5 event stream failed"); self.event_stream = EventStream::InActive; } } @@ -1042,15 +1038,15 @@ impl NetworkBehaviour for Discovery { // log these to see if we are unnecessarily dropping discovered peers /* if enr.eth2() == self.local_enr().eth2() { - trace!(self.log, "Peer found in process of query"; "peer_id" => format!("{}", enr.peer_id()), "tcp_socket" => enr.tcp_socket()); + trace!( "Peer found in process of query"; "peer_id" => format!("{}", enr.peer_id()), "tcp_socket" => enr.tcp_socket()); } else { // this is temporary warning for debugging the DHT - warn!(self.log, "Found peer during discovery not on correct fork"; "peer_id" => format!("{}", enr.peer_id()), "tcp_socket" => enr.tcp_socket()); + warn!( "Found peer during discovery not on correct fork"; "peer_id" => format!("{}", enr.peer_id()), "tcp_socket" => enr.tcp_socket()); } */ } discv5::Event::SocketUpdated(socket_addr) => { - info!(self.log, "Address updated"; "ip" => %socket_addr.ip(), "udp_port" => %socket_addr.port()); + info!(ip = %socket_addr.ip(), udp_port = %socket_addr.port(),"Address updated"); metrics::inc_counter(&metrics::ADDRESS_UPDATE_COUNT); // Discv5 will have updated our local ENR. We save the updated version // to disk. @@ -1062,7 +1058,7 @@ impl NetworkBehaviour for Discovery { self.discv5.update_local_enr_socket(socket_addr, true); } let enr = self.discv5.local_enr(); - enr::save_enr_to_disk(Path::new(&self.enr_dir), &enr, &self.log); + enr::save_enr_to_disk(Path::new(&self.enr_dir), &enr); // update network globals *self.network_globals.local_enr.write() = enr; // A new UDP socket has been detected. @@ -1086,7 +1082,11 @@ impl NetworkBehaviour for Discovery { let addr = ev.addr; let listener_id = ev.listener_id; - trace!(self.log, "Received NewListenAddr event from swarm"; "listener_id" => ?listener_id, "addr" => ?addr); + trace!( + ?listener_id, + ?addr, + "Received NewListenAddr event from swarm" + ); let mut addr_iter = addr.iter(); @@ -1094,7 +1094,7 @@ impl NetworkBehaviour for Discovery { Some(Protocol::Ip4(_)) => match (addr_iter.next(), addr_iter.next()) { (Some(Protocol::Tcp(port)), None) => { if !self.update_ports.tcp4 { - debug!(self.log, "Skipping ENR update"; "multiaddr" => ?addr); + debug!(multiaddr = ?addr, "Skipping ENR update"); return; } @@ -1102,21 +1102,21 @@ impl NetworkBehaviour for Discovery { } (Some(Protocol::Udp(port)), Some(Protocol::QuicV1)) => { if !self.update_ports.quic4 { - debug!(self.log, "Skipping ENR update"; "multiaddr" => ?addr); + debug!(?addr, "Skipping ENR update"); return; } self.update_enr_quic_port(port, false) } _ => { - debug!(self.log, "Encountered unacceptable multiaddr for listening (unsupported transport)"; "addr" => ?addr); + debug!(?addr, "Encountered unacceptable multiaddr for listening (unsupported transport)"); return; } }, Some(Protocol::Ip6(_)) => match (addr_iter.next(), addr_iter.next()) { (Some(Protocol::Tcp(port)), None) => { if !self.update_ports.tcp6 { - debug!(self.log, "Skipping ENR update"; "multiaddr" => ?addr); + debug!(?addr, "Skipping ENR update"); return; } @@ -1124,19 +1124,22 @@ impl NetworkBehaviour for Discovery { } (Some(Protocol::Udp(port)), Some(Protocol::QuicV1)) => { if !self.update_ports.quic6 { - debug!(self.log, "Skipping ENR update"; "multiaddr" => ?addr); + debug!(?addr, "Skipping ENR update"); return; } self.update_enr_quic_port(port, true) } _ => { - debug!(self.log, "Encountered unacceptable multiaddr for listening (unsupported transport)"; "addr" => ?addr); + debug!(?addr, "Encountered unacceptable multiaddr for listening (unsupported transport)"); return; } }, _ => { - debug!(self.log, "Encountered unacceptable multiaddr for listening (no IP)"; "addr" => ?addr); + debug!( + ?addr, + "Encountered unacceptable multiaddr for listening (no IP)" + ); return; } }; @@ -1145,10 +1148,10 @@ impl NetworkBehaviour for Discovery { match attempt_enr_update { Ok(true) => { - info!(self.log, "Updated local ENR"; "enr" => local_enr.to_base64(), "seq" => local_enr.seq(), "id"=> %local_enr.node_id(), "ip4" => ?local_enr.ip4(), "udp4"=> ?local_enr.udp4(), "tcp4" => ?local_enr.tcp4(), "tcp6" => ?local_enr.tcp6(), "udp6" => ?local_enr.udp6()) + info!(enr = local_enr.to_base64(), seq = local_enr.seq(), id = %local_enr.node_id(), ip4 = ?local_enr.ip4(), udp4 = ?local_enr.udp4(), tcp4 = ?local_enr.tcp4(), tcp6 = ?local_enr.tcp6(), udp6 = ?local_enr.udp6(),"Updated local ENR") } Ok(false) => {} // Nothing to do, ENR already configured - Err(e) => warn!(self.log, "Failed to update ENR"; "error" => ?e), + Err(e) => warn!(error = ?e,"Failed to update ENR"), } } _ => { @@ -1171,7 +1174,7 @@ impl Discovery { return; } // set peer as disconnected in discovery DHT - debug!(self.log, "Marking peer disconnected in DHT"; "peer_id" => %peer_id, "error" => %ClearDialError(error)); + debug!(%peer_id, error = %ClearDialError(error),"Marking peer disconnected in DHT"); self.disconnect_peer(&peer_id); } DialError::LocalPeerId { .. } @@ -1179,7 +1182,7 @@ impl Discovery { | DialError::Transport(_) | DialError::WrongPeerId { .. } => { // set peer as disconnected in discovery DHT - debug!(self.log, "Marking peer disconnected in DHT"; "peer_id" => %peer_id, "error" => %ClearDialError(error)); + debug!(%peer_id, error = %ClearDialError(error),"Marking peer disconnected in DHT"); self.disconnect_peer(&peer_id); } DialError::DialPeerConditionFalse(_) | DialError::Aborted => {} @@ -1193,23 +1196,10 @@ mod tests { use super::*; use crate::rpc::methods::{MetaData, MetaDataV2}; use libp2p::identity::secp256k1; - use slog::{o, Drain}; use types::{BitVector, MinimalEthSpec, SubnetId}; type E = MinimalEthSpec; - pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - - if enabled { - slog::Logger::root(drain.filter_level(level).fuse(), o!()) - } else { - slog::Logger::root(drain.filter(|_| false).fuse(), o!()) - } - } - async fn build_discovery() -> Discovery { let spec = Arc::new(ChainSpec::default()); let keypair = secp256k1::Keypair::generate(); @@ -1218,7 +1208,6 @@ mod tests { let config = Arc::new(config); let enr_key: CombinedKey = CombinedKey::from_secp256k1(&keypair); let enr: Enr = build_enr::(&enr_key, &config, &EnrForkId::default(), &spec).unwrap(); - let log = build_log(slog::Level::Debug, false); let globals = NetworkGlobals::new( enr, MetaData::V2(MetaDataV2 { @@ -1228,12 +1217,11 @@ mod tests { }), vec![], false, - &log, config.clone(), spec.clone(), ); let keypair = keypair.into(); - Discovery::new(keypair, &config, Arc::new(globals), &log, &spec) + Discovery::new(keypair, &config, Arc::new(globals), &spec) .await .unwrap() } diff --git a/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs b/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs index 400a0c2d56..735ef5b0f2 100644 --- a/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs +++ b/beacon_node/lighthouse_network/src/discovery/subnet_predicate.rs @@ -1,22 +1,19 @@ //! The subnet predicate used for searching for a particular subnet. use super::*; use crate::types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield}; -use slog::trace; use std::ops::Deref; +use tracing::trace; use types::data_column_custody_group::compute_subnets_for_node; use types::ChainSpec; /// Returns the predicate for a given subnet. pub fn subnet_predicate( subnets: Vec, - log: &slog::Logger, spec: Arc, ) -> impl Fn(&Enr) -> bool + Send where E: EthSpec, { - let log_clone = log.clone(); - move |enr: &Enr| { let attestation_bitfield: EnrAttestationBitfield = match enr.attestation_bitfield::() { @@ -48,9 +45,8 @@ where if !predicate { trace!( - log_clone, - "Peer found but not on any of the desired subnets"; - "peer_id" => %enr.peer_id() + peer_id = %enr.peer_id(), + "Peer found but not on any of the desired subnets" ); } predicate diff --git a/beacon_node/lighthouse_network/src/listen_addr.rs b/beacon_node/lighthouse_network/src/listen_addr.rs index 53f7d9daca..3b0ff98b34 100644 --- a/beacon_node/lighthouse_network/src/listen_addr.rs +++ b/beacon_node/lighthouse_network/src/listen_addr.rs @@ -104,25 +104,3 @@ impl ListenAddress { }) } } - -impl slog::KV for ListenAddress { - fn serialize( - &self, - _record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - if let Some(v4_addr) = self.v4() { - serializer.emit_arguments("ip4_address", &format_args!("{}", v4_addr.addr))?; - serializer.emit_u16("disc4_port", v4_addr.disc_port)?; - serializer.emit_u16("quic4_port", v4_addr.quic_port)?; - serializer.emit_u16("tcp4_port", v4_addr.tcp_port)?; - } - if let Some(v6_addr) = self.v6() { - serializer.emit_arguments("ip6_address", &format_args!("{}", v6_addr.addr))?; - serializer.emit_u16("disc6_port", v6_addr.disc_port)?; - serializer.emit_u16("quic6_port", v6_addr.quic_port)?; - serializer.emit_u16("tcp6_port", v6_addr.tcp_port)?; - } - slog::Result::Ok(()) - } -} diff --git a/beacon_node/lighthouse_network/src/peer_manager/mod.rs b/beacon_node/lighthouse_network/src/peer_manager/mod.rs index 07c4be7959..8c642ec91f 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/mod.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/mod.rs @@ -11,12 +11,12 @@ use libp2p::identify::Info as IdentifyInfo; use lru_cache::LRUTimeCache; use peerdb::{BanOperation, BanResult, ScoreUpdateResult}; use rand::seq::SliceRandom; -use slog::{debug, error, trace, warn}; use smallvec::SmallVec; use std::{ sync::Arc, time::{Duration, Instant}, }; +use tracing::{debug, error, trace, warn}; use types::{DataColumnSubnetId, EthSpec, SyncSubnetId}; pub use libp2p::core::Multiaddr; @@ -114,8 +114,6 @@ pub struct PeerManager { metrics_enabled: bool, /// Keeps track of whether the QUIC protocol is enabled or not. quic_enabled: bool, - /// The logger associated with the `PeerManager`. - log: slog::Logger, } /// The events that the `PeerManager` outputs (requests). @@ -150,7 +148,6 @@ impl PeerManager { pub fn new( cfg: config::Config, network_globals: Arc>, - log: &slog::Logger, ) -> Result { let config::Config { discovery_enabled, @@ -195,7 +192,6 @@ impl PeerManager { discovery_enabled, metrics_enabled, quic_enabled, - log: log.clone(), }) } @@ -209,7 +205,7 @@ impl PeerManager { pub fn goodbye_peer(&mut self, peer_id: &PeerId, reason: GoodbyeReason, source: ReportSource) { // Update the sync status if required if let Some(info) = self.network_globals.peers.write().peer_info_mut(peer_id) { - debug!(self.log, "Sending goodbye to peer"; "peer_id" => %peer_id, "reason" => %reason, "score" => %info.score()); + debug!(%peer_id, %reason, score = %info.score(), "Sending goodbye to peer"); if matches!(reason, GoodbyeReason::IrrelevantNetwork) { info.update_sync_status(SyncStatus::IrrelevantPeer); } @@ -369,7 +365,7 @@ impl PeerManager { .update_min_ttl(&peer_id, min_ttl); } if self.dial_peer(enr) { - debug!(self.log, "Added discovered ENR peer to dial queue"; "peer_id" => %peer_id); + debug!(%peer_id, "Added discovered ENR peer to dial queue"); to_dial_peers += 1; } } @@ -382,7 +378,10 @@ impl PeerManager { // reach out target. To prevent the infinite loop, if a query returns no useful peers, we // will cancel the recursiveness and wait for the heartbeat to trigger another query latter. if results_count > 0 && to_dial_peers == 0 { - debug!(self.log, "Skipping recursive discovery query after finding no useful results"; "results" => results_count); + debug!( + results = results_count, + "Skipping recursive discovery query after finding no useful results" + ); metrics::inc_counter(&metrics::DISCOVERY_NO_USEFUL_ENRS); } else { // Queue another discovery if we need to @@ -481,16 +480,21 @@ impl PeerManager { if previous_kind != peer_info.client().kind || *peer_info.listening_addresses() != previous_listening_addresses { - debug!(self.log, "Identified Peer"; "peer" => %peer_id, - "protocol_version" => &info.protocol_version, - "agent_version" => &info.agent_version, - "listening_addresses" => ?info.listen_addrs, - "observed_address" => ?info.observed_addr, - "protocols" => ?info.protocols + debug!( + %peer_id, + protocol_version = &info.protocol_version, + agent_version = &info.agent_version, + listening_addresses = ?info.listen_addrs, + observed_address = ?info.observed_addr, + protocols = ?info.protocols, + "Identified Peer" ); } } else { - error!(self.log, "Received an Identify response from an unknown peer"; "peer_id" => peer_id.to_string()); + error!( + peer_id = peer_id.to_string(), + "Received an Identify response from an unknown peer" + ); } } @@ -506,8 +510,7 @@ impl PeerManager { ) { let client = self.network_globals.client(peer_id); let score = self.network_globals.peers.read().score(peer_id); - debug!(self.log, "RPC Error"; "protocol" => %protocol, "err" => %err, "client" => %client, - "peer_id" => %peer_id, "score" => %score, "direction" => ?direction); + debug!(%protocol, %err, %client, %peer_id, %score, ?direction, "RPC Error"); metrics::inc_counter_vec( &metrics::TOTAL_RPC_ERRORS_PER_CLIENT, &[ @@ -524,7 +527,7 @@ impl PeerManager { PeerAction::MidToleranceError } RPCError::InternalError(e) => { - debug!(self.log, "Internal RPC Error"; "error" => %e, "peer_id" => %peer_id); + debug!(error = %e, %peer_id, "Internal RPC Error"); return; } RPCError::HandlerRejected => PeerAction::Fatal, @@ -617,7 +620,7 @@ impl PeerManager { RPCError::StreamTimeout => match direction { ConnectionDirection::Incoming => { // There was a timeout responding to a peer. - debug!(self.log, "Timed out responding to RPC Request"; "peer_id" => %peer_id); + debug!(%peer_id, "Timed out responding to RPC Request"); return; } ConnectionDirection::Outgoing => match protocol { @@ -656,7 +659,7 @@ impl PeerManager { if let Some(peer_info) = self.network_globals.peers.read().peer_info(peer_id) { // received a ping // reset the to-ping timer for this peer - trace!(self.log, "Received a ping request"; "peer_id" => %peer_id, "seq_no" => seq); + trace!(%peer_id, seq_no = seq, "Received a ping request"); match peer_info.connection_direction() { Some(ConnectionDirection::Incoming) => { self.inbound_ping_peers.insert(*peer_id); @@ -665,26 +668,23 @@ impl PeerManager { self.outbound_ping_peers.insert(*peer_id); } None => { - warn!(self.log, "Received a ping from a peer with an unknown connection direction"; "peer_id" => %peer_id); + warn!(%peer_id, "Received a ping from a peer with an unknown connection direction"); } } // if the sequence number is unknown send an update the meta data of the peer. if let Some(meta_data) = &peer_info.meta_data() { if *meta_data.seq_number() < seq { - trace!(self.log, "Requesting new metadata from peer"; - "peer_id" => %peer_id, "known_seq_no" => meta_data.seq_number(), "ping_seq_no" => seq); + trace!(%peer_id, known_seq_no = meta_data.seq_number(), ping_seq_no = seq, "Requesting new metadata from peer"); self.events.push(PeerManagerEvent::MetaData(*peer_id)); } } else { // if we don't know the meta-data, request it - debug!(self.log, "Requesting first metadata from peer"; - "peer_id" => %peer_id); + debug!(%peer_id, "Requesting first metadata from peer"); self.events.push(PeerManagerEvent::MetaData(*peer_id)); } } else { - error!(self.log, "Received a PING from an unknown peer"; - "peer_id" => %peer_id); + error!(%peer_id, "Received a PING from an unknown peer"); } } @@ -696,18 +696,16 @@ impl PeerManager { // if the sequence number is unknown send update the meta data of the peer. if let Some(meta_data) = &peer_info.meta_data() { if *meta_data.seq_number() < seq { - trace!(self.log, "Requesting new metadata from peer"; - "peer_id" => %peer_id, "known_seq_no" => meta_data.seq_number(), "pong_seq_no" => seq); + trace!(%peer_id, known_seq_no = meta_data.seq_number(), pong_seq_no = seq, "Requesting new metadata from peer"); self.events.push(PeerManagerEvent::MetaData(*peer_id)); } } else { // if we don't know the meta-data, request it - trace!(self.log, "Requesting first metadata from peer"; - "peer_id" => %peer_id); + trace!(%peer_id, "Requesting first metadata from peer"); self.events.push(PeerManagerEvent::MetaData(*peer_id)); } } else { - error!(self.log, "Received a PONG from an unknown peer"; "peer_id" => %peer_id); + error!(%peer_id, "Received a PONG from an unknown peer"); } } @@ -718,18 +716,15 @@ impl PeerManager { if let Some(peer_info) = self.network_globals.peers.write().peer_info_mut(peer_id) { if let Some(known_meta_data) = &peer_info.meta_data() { if *known_meta_data.seq_number() < *meta_data.seq_number() { - trace!(self.log, "Updating peer's metadata"; - "peer_id" => %peer_id, "known_seq_no" => known_meta_data.seq_number(), "new_seq_no" => meta_data.seq_number()); + trace!(%peer_id, known_seq_no = known_meta_data.seq_number(), new_seq_no = meta_data.seq_number(), "Updating peer's metadata"); } else { - trace!(self.log, "Received old metadata"; - "peer_id" => %peer_id, "known_seq_no" => known_meta_data.seq_number(), "new_seq_no" => meta_data.seq_number()); + trace!(%peer_id, known_seq_no = known_meta_data.seq_number(), new_seq_no = meta_data.seq_number(), "Received old metadata"); // Updating metadata even in this case to prevent storing // incorrect `attnets/syncnets` for a peer } } else { // we have no meta-data for this peer, update - debug!(self.log, "Obtained peer's metadata"; - "peer_id" => %peer_id, "new_seq_no" => meta_data.seq_number()); + debug!(%peer_id, new_seq_no = meta_data.seq_number(), "Obtained peer's metadata"); } let custody_group_count_opt = meta_data.custody_group_count().copied().ok(); @@ -749,10 +744,9 @@ impl PeerManager { .cloned() .unwrap_or_else(|| { warn!( - self.log, - "Custody group not found in subnet mapping"; - "custody_index" => custody_index, - "peer_id" => %peer_id + %custody_index, + %peer_id, + "Custody group not found in subnet mapping" ); vec![] }) @@ -761,11 +755,12 @@ impl PeerManager { peer_info.set_custody_subnets(custody_subnets); } Err(err) => { - debug!(self.log, "Unable to compute peer custody groups from metadata"; - "info" => "Sending goodbye to peer", - "peer_id" => %peer_id, - "custody_group_count" => custody_group_count, - "error" => ?err, + debug!( + info = "Sending goodbye to peer", + peer_id = %peer_id, + custody_group_count, + error = ?err, + "Unable to compute peer custody groups from metadata" ); invalid_meta_data = true; } @@ -773,8 +768,7 @@ impl PeerManager { } } } else { - error!(self.log, "Received METADATA from an unknown peer"; - "peer_id" => %peer_id); + error!(%peer_id, "Received METADATA from an unknown peer"); } // Disconnect peers with invalid metadata and find other peers instead. @@ -866,7 +860,7 @@ impl PeerManager { let mut peerdb = self.network_globals.peers.write(); if peerdb.ban_status(peer_id).is_some() { // don't connect if the peer is banned - error!(self.log, "Connection has been allowed to a banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Connection has been allowed to a banned peer"); } match connection { @@ -934,9 +928,8 @@ impl PeerManager { // request the subnet query from discovery if !subnets_to_discover.is_empty() { debug!( - self.log, - "Making subnet queries for maintaining sync committee peers"; - "subnets" => ?subnets_to_discover.iter().map(|s| s.subnet).collect::>() + subnets = ?subnets_to_discover.iter().map(|s| s.subnet).collect::>(), + "Making subnet queries for maintaining sync committee peers" ); self.events .push(PeerManagerEvent::DiscoverSubnetPeers(subnets_to_discover)); @@ -965,7 +958,13 @@ impl PeerManager { if wanted_peers != 0 { // We need more peers, re-queue a discovery lookup. - debug!(self.log, "Starting a new peer discovery query"; "connected" => peer_count, "target" => self.target_peers, "outbound" => outbound_only_peer_count, "wanted" => wanted_peers); + debug!( + connected = peer_count, + target = self.target_peers, + outbound = outbound_only_peer_count, + wanted = wanted_peers, + "Starting a new peer discovery query" + ); self.events .push(PeerManagerEvent::DiscoverPeers(wanted_peers)); } @@ -1491,21 +1490,8 @@ enum ConnectingType { mod tests { use super::*; use crate::NetworkConfig; - use slog::{o, Drain}; use types::MainnetEthSpec as E; - pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - - if enabled { - slog::Logger::root(drain.filter_level(level).fuse(), o!()) - } else { - slog::Logger::root(drain.filter(|_| false).fuse(), o!()) - } - } - async fn build_peer_manager(target_peer_count: usize) -> PeerManager { build_peer_manager_with_trusted_peers(vec![], target_peer_count).await } @@ -1523,10 +1509,9 @@ mod tests { target_peers: target_peer_count, ..Default::default() }); - let log = build_log(slog::Level::Debug, false); let spec = Arc::new(E::default_spec()); - let globals = NetworkGlobals::new_test_globals(trusted_peers, &log, network_config, spec); - PeerManager::new(config, Arc::new(globals), &log).unwrap() + let globals = NetworkGlobals::new_test_globals(trusted_peers, network_config, spec); + PeerManager::new(config, Arc::new(globals)).unwrap() } #[tokio::test] diff --git a/beacon_node/lighthouse_network/src/peer_manager/network_behaviour.rs b/beacon_node/lighthouse_network/src/peer_manager/network_behaviour.rs index abafb200be..1ad55ce5c4 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/network_behaviour.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/network_behaviour.rs @@ -13,7 +13,7 @@ use libp2p::swarm::dial_opts::{DialOpts, PeerCondition}; use libp2p::swarm::dummy::ConnectionHandler; use libp2p::swarm::{ConnectionDenied, ConnectionId, NetworkBehaviour, ToSwarm}; pub use metrics::{set_gauge_vec, NAT_OPEN}; -use slog::{debug, error, trace}; +use tracing::{debug, error, trace}; use types::EthSpec; use crate::discovery::enr_ext::EnrExt; @@ -54,7 +54,10 @@ impl NetworkBehaviour for PeerManager { self.events.push(PeerManagerEvent::Ping(peer_id)); } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for inbound peers to ping"; "error" => e.to_string()) + error!( + error = e.to_string(), + "Failed to check for inbound peers to ping" + ) } Poll::Ready(None) | Poll::Pending => break, } @@ -67,7 +70,10 @@ impl NetworkBehaviour for PeerManager { self.events.push(PeerManagerEvent::Ping(peer_id)); } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for outbound peers to ping"; "error" => e.to_string()) + error!( + error = e.to_string(), + "Failed to check for outbound peers to ping" + ) } Poll::Ready(None) | Poll::Pending => break, } @@ -84,7 +90,7 @@ impl NetworkBehaviour for PeerManager { self.events.push(PeerManagerEvent::Status(peer_id)) } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for peers to ping"; "error" => e.to_string()) + error!(error = e.to_string(), "Failed to check for peers to ping") } Poll::Ready(None) | Poll::Pending => break, } @@ -109,7 +115,7 @@ impl NetworkBehaviour for PeerManager { ] .concat(); - debug!(self.log, "Dialing peer"; "peer_id"=> %enr.peer_id(), "multiaddrs" => ?multiaddrs); + debug!(peer_id = %enr.peer_id(), ?multiaddrs, "Dialing peer"); return Poll::Ready(ToSwarm::Dial { opts: DialOpts::peer_id(enr.peer_id()) .condition(PeerCondition::Disconnected) @@ -141,7 +147,7 @@ impl NetworkBehaviour for PeerManager { error, connection_id: _, }) => { - debug!(self.log, "Failed to dial peer"; "peer_id"=> ?peer_id, "error" => %ClearDialError(error)); + debug!(?peer_id, error = %ClearDialError(error),"Failed to dial peer"); self.on_dial_failure(peer_id); } _ => { @@ -186,7 +192,7 @@ impl NetworkBehaviour for PeerManager { _local_addr: &libp2p::Multiaddr, remote_addr: &libp2p::Multiaddr, ) -> Result, ConnectionDenied> { - trace!(self.log, "Inbound connection"; "peer_id" => %peer_id, "multiaddr" => %remote_addr); + trace!(%peer_id, multiaddr = %remote_addr, "Inbound connection"); // We already checked if the peer was banned on `handle_pending_inbound_connection`. if self.ban_status(&peer_id).is_some() { return Err(ConnectionDenied::new( @@ -201,7 +207,7 @@ impl NetworkBehaviour for PeerManager { .peers .read() .peer_info(&peer_id) - .map_or(true, |peer| !peer.has_future_duty()) + .is_none_or(|peer| !peer.has_future_duty()) { return Err(ConnectionDenied::new( "Connection to peer rejected: too many connections", @@ -227,9 +233,9 @@ impl NetworkBehaviour for PeerManager { _role_override: libp2p::core::Endpoint, _port_use: PortUse, ) -> Result, libp2p::swarm::ConnectionDenied> { - trace!(self.log, "Outbound connection"; "peer_id" => %peer_id, "multiaddr" => %addr); + trace!(%peer_id, multiaddr = %addr,"Outbound connection"); if let Some(cause) = self.ban_status(&peer_id) { - error!(self.log, "Connected a banned peer. Rejecting connection"; "peer_id" => %peer_id); + error!(%peer_id, "Connected a banned peer. Rejecting connection"); return Err(ConnectionDenied::new(cause)); } @@ -240,7 +246,7 @@ impl NetworkBehaviour for PeerManager { .peers .read() .peer_info(&peer_id) - .map_or(true, |peer| !peer.has_future_duty()) + .is_none_or(|peer| !peer.has_future_duty()) { return Err(ConnectionDenied::new( "Connection to peer rejected: too many connections", @@ -258,9 +264,11 @@ impl PeerManager { endpoint: &ConnectedPoint, _other_established: usize, ) { - debug!(self.log, "Connection established"; "peer_id" => %peer_id, - "multiaddr" => %endpoint.get_remote_address(), - "connection" => ?endpoint.to_endpoint() + debug!( + multiaddr = %endpoint.get_remote_address(), + connection = ?endpoint.to_endpoint(), + %peer_id, + "Connection established" ); // Update the prometheus metrics @@ -309,7 +317,7 @@ impl PeerManager { // Inform the application. self.events .push(PeerManagerEvent::PeerDisconnected(peer_id)); - debug!(self.log, "Peer disconnected"; "peer_id" => %peer_id); + debug!(%peer_id,"Peer disconnected"); } // NOTE: It may be the case that a rejected node, due to too many peers is disconnected diff --git a/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs b/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs index 37cb5df6ea..4a0388058b 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs @@ -2,9 +2,9 @@ use crate::discovery::enr::PEERDAS_CUSTODY_GROUP_COUNT_ENR_KEY; use crate::discovery::{peer_id_to_node_id, CombinedKey}; use crate::{metrics, multiaddr::Multiaddr, types::Subnet, Enr, EnrExt, Gossipsub, PeerId}; use itertools::Itertools; +use logging::crit; use peer_info::{ConnectionDirection, PeerConnectionStatus, PeerInfo}; use score::{PeerAction, ReportSource, Score, ScoreState}; -use slog::{crit, debug, error, trace, warn}; use std::net::IpAddr; use std::time::Instant; use std::{cmp::Ordering, fmt::Display}; @@ -13,6 +13,7 @@ use std::{ fmt::Formatter, }; use sync_status::SyncStatus; +use tracing::{debug, error, trace, warn}; use types::data_column_custody_group::compute_subnets_for_node; use types::{ChainSpec, DataColumnSubnetId, EthSpec}; @@ -44,19 +45,16 @@ pub struct PeerDB { banned_peers_count: BannedPeersCount, /// Specifies if peer scoring is disabled. disable_peer_scoring: bool, - /// PeerDB's logger - log: slog::Logger, } impl PeerDB { - pub fn new(trusted_peers: Vec, disable_peer_scoring: bool, log: &slog::Logger) -> Self { + pub fn new(trusted_peers: Vec, disable_peer_scoring: bool) -> Self { // Initialize the peers hashmap with trusted peers let peers = trusted_peers .into_iter() .map(|peer_id| (peer_id, PeerInfo::trusted_peer_info())) .collect(); Self { - log: log.clone(), disconnected_peers: 0, banned_peers_count: BannedPeersCount::default(), disable_peer_scoring, @@ -385,15 +383,15 @@ impl PeerDB { // Update scores info.score_update(); - match Self::handle_score_transition(previous_state, peer_id, info, &self.log) { + match Self::handle_score_transition(previous_state, peer_id, info) { // A peer should not be able to be banned from a score update. ScoreTransitionResult::Banned => { - error!(self.log, "Peer has been banned in an update"; "peer_id" => %peer_id) + error!(%peer_id, "Peer has been banned in an update"); } // A peer should not be able to transition to a disconnected state from a healthy // state in a score update. ScoreTransitionResult::Disconnected => { - error!(self.log, "Peer has been disconnected in an update"; "peer_id" => %peer_id) + error!(%peer_id, "Peer has been disconnected in an update"); } ScoreTransitionResult::Unbanned => { peers_to_unban.push(*peer_id); @@ -466,7 +464,7 @@ impl PeerDB { actions.push(( *peer_id, - Self::handle_score_transition(previous_state, peer_id, info, &self.log), + Self::handle_score_transition(previous_state, peer_id, info), )); } @@ -537,15 +535,13 @@ impl PeerDB { &metrics::PEER_ACTION_EVENTS_PER_CLIENT, &[info.client().kind.as_ref(), action.as_ref(), source.into()], ); - let result = - Self::handle_score_transition(previous_state, peer_id, info, &self.log); + let result = Self::handle_score_transition(previous_state, peer_id, info); if previous_state == info.score_state() { debug!( - self.log, - "Peer score adjusted"; - "msg" => %msg, - "peer_id" => %peer_id, - "score" => %info.score() + %msg, + %peer_id, + score = %info.score(), + "Peer score adjusted" ); } match result { @@ -567,10 +563,9 @@ impl PeerDB { ScoreTransitionResult::NoAction => ScoreUpdateResult::NoAction, ScoreTransitionResult::Unbanned => { error!( - self.log, - "Report peer action lead to an unbanning"; - "msg" => %msg, - "peer_id" => %peer_id + %msg, + %peer_id, + "Report peer action lead to an unbanning" ); ScoreUpdateResult::NoAction } @@ -578,10 +573,9 @@ impl PeerDB { } None => { debug!( - self.log, - "Reporting a peer that doesn't exist"; - "msg" => %msg, - "peer_id" =>%peer_id + %msg, + %peer_id, + "Reporting a peer that doesn't exist" ); ScoreUpdateResult::NoAction } @@ -601,7 +595,7 @@ impl PeerDB { .checked_duration_since(Instant::now()) .map(|duration| duration.as_secs()) .unwrap_or_else(|| 0); - debug!(self.log, "Updating the time a peer is required for"; "peer_id" => %peer_id, "future_min_ttl_secs" => min_ttl_secs); + debug!(%peer_id, future_min_ttl_secs = min_ttl_secs, "Updating the time a peer is required for"); } } @@ -625,12 +619,14 @@ impl PeerDB { /// min_ttl than what's given. // VISIBILITY: The behaviour is able to adjust subscriptions. pub(crate) fn extend_peers_on_subnet(&mut self, subnet: &Subnet, min_ttl: Instant) { - let log = &self.log; - self.peers.iter_mut() + self.peers + .iter_mut() .filter(move |(_, info)| { - info.is_connected() && info.on_subnet_metadata(subnet) && info.on_subnet_gossipsub(subnet) + info.is_connected() + && info.on_subnet_metadata(subnet) + && info.on_subnet_gossipsub(subnet) }) - .for_each(|(peer_id,info)| { + .for_each(|(peer_id, info)| { if info.min_ttl().is_none() || Some(&min_ttl) > info.min_ttl() { info.set_min_ttl(min_ttl); } @@ -638,7 +634,7 @@ impl PeerDB { .checked_duration_since(Instant::now()) .map(|duration| duration.as_secs()) .unwrap_or_else(|| 0); - trace!(log, "Updating minimum duration a peer is required for"; "peer_id" => %peer_id, "min_ttl" => min_ttl_secs); + trace!(%peer_id, min_ttl_secs, "Updating minimum duration a peer is required for"); }); } @@ -689,8 +685,8 @@ impl PeerDB { &mut self, supernode: bool, spec: &ChainSpec, + enr_key: CombinedKey, ) -> PeerId { - let enr_key = CombinedKey::generate_secp256k1(); let mut enr = Enr::builder().build(&enr_key).unwrap(); let peer_id = enr.peer_id(); @@ -740,7 +736,6 @@ impl PeerDB { peer_id: &PeerId, new_state: NewConnectionState, ) -> Option { - let log_ref = &self.log; let info = self.peers.entry(*peer_id).or_insert_with(|| { // If we are not creating a new connection (or dropping a current inbound connection) log a warning indicating we are updating a // connection state for an unknown peer. @@ -752,8 +747,7 @@ impl PeerDB { | NewConnectionState::Disconnected { .. } // Dialing a peer that responds by a different ID can be immediately // disconnected without having being stored in the db before ) { - warn!(log_ref, "Updating state of unknown peer"; - "peer_id" => %peer_id, "new_state" => ?new_state); + warn!(%peer_id, ?new_state, "Updating state of unknown peer"); } if self.disable_peer_scoring { PeerInfo::trusted_peer_info() @@ -768,7 +762,7 @@ impl PeerDB { ScoreState::Banned => {} _ => { // If score isn't low enough to ban, this function has been called incorrectly. - error!(self.log, "Banning a peer with a good score"; "peer_id" => %peer_id); + error!(%peer_id, "Banning a peer with a good score"); info.apply_peer_action_to_score(score::PeerAction::Fatal); } } @@ -799,13 +793,13 @@ impl PeerDB { self.disconnected_peers = self.disconnected_peers.saturating_sub(1); } PeerConnectionStatus::Banned { .. } => { - error!(self.log, "Accepted a connection from a banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Accepted a connection from a banned peer"); // TODO: check if this happens and report the unban back self.banned_peers_count .remove_banned_peer(info.seen_ip_addresses()); } PeerConnectionStatus::Disconnecting { .. } => { - warn!(self.log, "Connected to a disconnecting peer"; "peer_id" => %peer_id) + warn!(%peer_id, "Connected to a disconnecting peer"); } PeerConnectionStatus::Unknown | PeerConnectionStatus::Connected { .. } @@ -827,7 +821,7 @@ impl PeerDB { (old_state, NewConnectionState::Dialing { enr }) => { match old_state { PeerConnectionStatus::Banned { .. } => { - warn!(self.log, "Dialing a banned peer"; "peer_id" => %peer_id); + warn!(%peer_id, "Dialing a banned peer"); self.banned_peers_count .remove_banned_peer(info.seen_ip_addresses()); } @@ -835,13 +829,13 @@ impl PeerDB { self.disconnected_peers = self.disconnected_peers.saturating_sub(1); } PeerConnectionStatus::Connected { .. } => { - warn!(self.log, "Dialing an already connected peer"; "peer_id" => %peer_id) + warn!(%peer_id, "Dialing an already connected peer"); } PeerConnectionStatus::Dialing { .. } => { - warn!(self.log, "Dialing an already dialing peer"; "peer_id" => %peer_id) + warn!(%peer_id, "Dialing an already dialing peer"); } PeerConnectionStatus::Disconnecting { .. } => { - warn!(self.log, "Dialing a disconnecting peer"; "peer_id" => %peer_id) + warn!(%peer_id, "Dialing a disconnecting peer"); } PeerConnectionStatus::Unknown => {} // default behaviour } @@ -851,7 +845,7 @@ impl PeerDB { } if let Err(e) = info.set_dialing_peer() { - error!(self.log, "{}", e; "peer_id" => %peer_id); + error!(%peer_id, e); } } @@ -907,7 +901,7 @@ impl PeerDB { * Handles the transition to a disconnecting state */ (PeerConnectionStatus::Banned { .. }, NewConnectionState::Disconnecting { to_ban }) => { - error!(self.log, "Disconnecting from a banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Disconnecting from a banned peer"); info.set_connection_status(PeerConnectionStatus::Disconnecting { to_ban }); } ( @@ -951,13 +945,13 @@ impl PeerDB { (PeerConnectionStatus::Disconnecting { .. }, NewConnectionState::Banned) => { // NOTE: This can occur due a rapid downscore of a peer. It goes through the // disconnection phase and straight into banning in a short time-frame. - debug!(log_ref, "Banning peer that is currently disconnecting"; "peer_id" => %peer_id); + debug!(%peer_id, "Banning peer that is currently disconnecting"); // Ban the peer once the disconnection process completes. info.set_connection_status(PeerConnectionStatus::Disconnecting { to_ban: true }); return Some(BanOperation::PeerDisconnecting); } (PeerConnectionStatus::Banned { .. }, NewConnectionState::Banned) => { - error!(log_ref, "Banning already banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Banning already banned peer"); let known_banned_ips = self.banned_peers_count.banned_ips(); let banned_ips = info .seen_ip_addresses() @@ -975,7 +969,7 @@ impl PeerDB { } (PeerConnectionStatus::Unknown, NewConnectionState::Banned) => { // shift the peer straight to banned - warn!(log_ref, "Banning a peer of unknown connection state"; "peer_id" => %peer_id); + warn!(%peer_id, "Banning a peer of unknown connection state"); self.banned_peers_count .add_banned_peer(info.seen_ip_addresses()); info.set_connection_status(PeerConnectionStatus::Banned { @@ -996,15 +990,15 @@ impl PeerDB { */ (old_state, NewConnectionState::Unbanned) => { if matches!(info.score_state(), ScoreState::Banned) { - error!(self.log, "Unbanning a banned peer"; "peer_id" => %peer_id); + error!(%peer_id, "Unbanning a banned peer"); } match old_state { PeerConnectionStatus::Unknown | PeerConnectionStatus::Connected { .. } => { - error!(self.log, "Unbanning a connected peer"; "peer_id" => %peer_id); + error!(%peer_id, "Unbanning a connected peer"); } PeerConnectionStatus::Disconnected { .. } | PeerConnectionStatus::Disconnecting { .. } => { - debug!(self.log, "Unbanning disconnected or disconnecting peer"; "peer_id" => %peer_id); + debug!(%peer_id, "Unbanning disconnected or disconnecting peer"); } // These are odd but fine. PeerConnectionStatus::Dialing { .. } => {} // Also odd but acceptable PeerConnectionStatus::Banned { since } => { @@ -1073,15 +1067,12 @@ impl PeerDB { Some((*id, unbanned_ips)) } else { // If there is no minimum, this is a coding error. - crit!( - self.log, - "banned_peers > MAX_BANNED_PEERS despite no banned peers in db!" - ); + crit!("banned_peers > MAX_BANNED_PEERS despite no banned peers in db!"); // reset banned_peers this will also exit the loop self.banned_peers_count = BannedPeersCount::default(); None } { - debug!(self.log, "Removing old banned peer"; "peer_id" => %to_drop); + debug!(peer_id = %to_drop, "Removing old banned peer"); self.peers.remove(&to_drop); unbanned_peers.push((to_drop, unbanned_ips)) } @@ -1100,7 +1091,11 @@ impl PeerDB { .min_by_key(|(_, age)| *age) .map(|(id, _)| *id) { - debug!(self.log, "Removing old disconnected peer"; "peer_id" => %to_drop, "disconnected_size" => self.disconnected_peers.saturating_sub(1)); + debug!( + peer_id = %to_drop, + disconnected_size = self.disconnected_peers.saturating_sub(1), + "Removing old disconnected peer" + ); self.peers.remove(&to_drop); } // If there is no minimum, this is a coding error. For safety we decrease @@ -1117,15 +1112,19 @@ impl PeerDB { previous_state: ScoreState, peer_id: &PeerId, info: &PeerInfo, - log: &slog::Logger, ) -> ScoreTransitionResult { match (info.score_state(), previous_state) { (ScoreState::Banned, ScoreState::Healthy | ScoreState::ForcedDisconnect) => { - debug!(log, "Peer has been banned"; "peer_id" => %peer_id, "score" => %info.score()); + debug!(%peer_id, score = %info.score(), "Peer has been banned"); ScoreTransitionResult::Banned } (ScoreState::ForcedDisconnect, ScoreState::Banned | ScoreState::Healthy) => { - debug!(log, "Peer transitioned to forced disconnect score state"; "peer_id" => %peer_id, "score" => %info.score(), "past_score_state" => %previous_state); + debug!( + %peer_id, + score = %info.score(), + past_score_state = %previous_state, + "Peer transitioned to forced disconnect score state" + ); // disconnect the peer if it's currently connected or dialing if info.is_connected_or_dialing() { ScoreTransitionResult::Disconnected @@ -1138,11 +1137,21 @@ impl PeerDB { } } (ScoreState::Healthy, ScoreState::ForcedDisconnect) => { - debug!(log, "Peer transitioned to healthy score state"; "peer_id" => %peer_id, "score" => %info.score(), "past_score_state" => %previous_state); + debug!( + %peer_id, + score = %info.score(), + past_score_state = %previous_state, + "Peer transitioned to healthy score state" + ); ScoreTransitionResult::NoAction } (ScoreState::Healthy, ScoreState::Banned) => { - debug!(log, "Peer transitioned to healthy score state"; "peer_id" => %peer_id, "score" => %info.score(), "past_score_state" => %previous_state); + debug!( + %peer_id, + score = %info.score(), + past_score_state = %previous_state, + "Peer transitioned to healthy score state" + ); // unban the peer if it was previously banned. ScoreTransitionResult::Unbanned } @@ -1309,24 +1318,11 @@ impl BannedPeersCount { mod tests { use super::*; use libp2p::core::multiaddr::Protocol; - use slog::{o, Drain}; use std::net::{Ipv4Addr, Ipv6Addr}; use types::MinimalEthSpec; type M = MinimalEthSpec; - pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - - if enabled { - slog::Logger::root(drain.filter_level(level).fuse(), o!()) - } else { - slog::Logger::root(drain.filter(|_| false).fuse(), o!()) - } - } - fn add_score(db: &mut PeerDB, peer_id: &PeerId, score: f64) { if let Some(info) = db.peer_info_mut(peer_id) { info.add_to_score(score); @@ -1340,8 +1336,7 @@ mod tests { } fn get_db() -> PeerDB { - let log = build_log(slog::Level::Debug, false); - PeerDB::new(vec![], false, &log) + PeerDB::new(vec![], false) } #[test] @@ -2039,8 +2034,7 @@ mod tests { #[allow(clippy::float_cmp)] fn test_trusted_peers_score() { let trusted_peer = PeerId::random(); - let log = build_log(slog::Level::Debug, false); - let mut pdb: PeerDB = PeerDB::new(vec![trusted_peer], false, &log); + let mut pdb: PeerDB = PeerDB::new(vec![trusted_peer], false); pdb.connect_ingoing(&trusted_peer, "/ip4/0.0.0.0".parse().unwrap(), None); @@ -2063,8 +2057,7 @@ mod tests { #[test] fn test_disable_peer_scoring() { let peer = PeerId::random(); - let log = build_log(slog::Level::Debug, false); - let mut pdb: PeerDB = PeerDB::new(vec![], true, &log); + let mut pdb: PeerDB = PeerDB::new(vec![], true); pdb.connect_ingoing(&peer, "/ip4/0.0.0.0".parse().unwrap(), None); diff --git a/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs b/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs index 576135f6ad..4cbff59ce2 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/peerdb/peer_info.rs @@ -4,7 +4,6 @@ use super::sync_status::SyncStatus; use crate::discovery::Eth2Enr; use crate::{rpc::MetaData, types::Subnet}; use discv5::Enr; -use eth2::types::{PeerDirection, PeerState}; use libp2p::core::multiaddr::{Multiaddr, Protocol}; use serde::{ ser::{SerializeStruct, Serializer}, @@ -235,6 +234,11 @@ impl PeerInfo { self.custody_subnets.contains(subnet) } + /// Returns an iterator on this peer's custody subnets + pub fn custody_subnets_iter(&self) -> impl Iterator { + self.custody_subnets.iter() + } + /// Returns true if the peer is connected to a long-lived subnet. pub fn has_long_lived_subnet(&self) -> bool { // Check the meta_data @@ -527,15 +531,6 @@ pub enum ConnectionDirection { Outgoing, } -impl From for PeerDirection { - fn from(direction: ConnectionDirection) -> Self { - match direction { - ConnectionDirection::Incoming => PeerDirection::Inbound, - ConnectionDirection::Outgoing => PeerDirection::Outbound, - } - } -} - /// Connection Status of the peer. #[derive(Debug, Clone, Default)] pub enum PeerConnectionStatus { @@ -629,14 +624,3 @@ impl Serialize for PeerConnectionStatus { } } } - -impl From for PeerState { - fn from(status: PeerConnectionStatus) -> Self { - match status { - Connected { .. } => PeerState::Connected, - Dialing { .. } => PeerState::Connecting, - Disconnecting { .. } => PeerState::Disconnecting, - Disconnected { .. } | Banned { .. } | Unknown => PeerState::Disconnected, - } - } -} diff --git a/beacon_node/lighthouse_network/src/rpc/codec.rs b/beacon_node/lighthouse_network/src/rpc/codec.rs index 2bf35b0e35..3adc04eb6a 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec.rs @@ -765,8 +765,8 @@ fn handle_rpc_response( SupportedProtocol::PingV1 => Ok(Some(RpcSuccessResponse::Pong(Ping { data: u64::from_ssz_bytes(decoded_buffer)?, }))), - SupportedProtocol::MetaDataV1 => Ok(Some(RpcSuccessResponse::MetaData(MetaData::V1( - MetaDataV1::from_ssz_bytes(decoded_buffer)?, + SupportedProtocol::MetaDataV1 => Ok(Some(RpcSuccessResponse::MetaData(Arc::new( + MetaData::V1(MetaDataV1::from_ssz_bytes(decoded_buffer)?), )))), SupportedProtocol::LightClientBootstrapV1 => match fork_name { Some(fork_name) => Ok(Some(RpcSuccessResponse::LightClientBootstrap(Arc::new( @@ -826,11 +826,11 @@ fn handle_rpc_response( )), }, // MetaData V2/V3 responses have no context bytes, so behave similarly to V1 responses - SupportedProtocol::MetaDataV3 => Ok(Some(RpcSuccessResponse::MetaData(MetaData::V3( - MetaDataV3::from_ssz_bytes(decoded_buffer)?, + SupportedProtocol::MetaDataV3 => Ok(Some(RpcSuccessResponse::MetaData(Arc::new( + MetaData::V3(MetaDataV3::from_ssz_bytes(decoded_buffer)?), )))), - SupportedProtocol::MetaDataV2 => Ok(Some(RpcSuccessResponse::MetaData(MetaData::V2( - MetaDataV2::from_ssz_bytes(decoded_buffer)?, + SupportedProtocol::MetaDataV2 => Ok(Some(RpcSuccessResponse::MetaData(Arc::new( + MetaData::V2(MetaDataV2::from_ssz_bytes(decoded_buffer)?), )))), SupportedProtocol::BlocksByRangeV2 => match fork_name { Some(ForkName::Altair) => Ok(Some(RpcSuccessResponse::BlocksByRange(Arc::new( @@ -1008,6 +1008,7 @@ mod tests { ) -> SignedBeaconBlock { let mut block: BeaconBlockBellatrix<_, FullPayload> = BeaconBlockBellatrix::empty(&Spec::default_spec()); + let tx = VariableList::from(vec![0; 1024]); let txs = VariableList::from(std::iter::repeat(tx).take(5000).collect::>()); @@ -1027,6 +1028,7 @@ mod tests { ) -> SignedBeaconBlock { let mut block: BeaconBlockBellatrix<_, FullPayload> = BeaconBlockBellatrix::empty(&Spec::default_spec()); + let tx = VariableList::from(vec![0; 1024]); let txs = VariableList::from(std::iter::repeat(tx).take(100000).collect::>()); @@ -1105,28 +1107,31 @@ mod tests { Ping { data: 1 } } - fn metadata() -> MetaData { + fn metadata() -> Arc> { MetaData::V1(MetaDataV1 { seq_number: 1, attnets: EnrAttestationBitfield::::default(), }) + .into() } - fn metadata_v2() -> MetaData { + fn metadata_v2() -> Arc> { MetaData::V2(MetaDataV2 { seq_number: 1, attnets: EnrAttestationBitfield::::default(), syncnets: EnrSyncCommitteeBitfield::::default(), }) + .into() } - fn metadata_v3() -> MetaData { + fn metadata_v3() -> Arc> { MetaData::V3(MetaDataV3 { seq_number: 1, attnets: EnrAttestationBitfield::::default(), syncnets: EnrSyncCommitteeBitfield::::default(), custody_group_count: 1, }) + .into() } /// Encodes the given protocol response as bytes. diff --git a/beacon_node/lighthouse_network/src/rpc/handler.rs b/beacon_node/lighthouse_network/src/rpc/handler.rs index 03203fcade..8353b661c5 100644 --- a/beacon_node/lighthouse_network/src/rpc/handler.rs +++ b/beacon_node/lighthouse_network/src/rpc/handler.rs @@ -15,8 +15,9 @@ use libp2p::swarm::handler::{ ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound, StreamUpgradeError, SubstreamProtocol, }; -use libp2p::swarm::Stream; -use slog::{crit, debug, trace}; +use libp2p::swarm::{ConnectionId, Stream}; +use libp2p::PeerId; +use logging::crit; use smallvec::SmallVec; use std::{ collections::{hash_map::Entry, VecDeque}, @@ -27,6 +28,7 @@ use std::{ }; use tokio::time::{sleep, Sleep}; use tokio_util::time::{delay_queue, DelayQueue}; +use tracing::{debug, trace}; use types::{EthSpec, ForkContext}; /// The number of times to retry an outbound upgrade in the case of IO errors. @@ -135,11 +137,11 @@ where /// Waker, to be sure the handler gets polled when needed. waker: Option, - /// Logger for handling RPC streams - log: slog::Logger, - /// Timeout that will me used for inbound and outbound responses. resp_timeout: Duration, + + /// Information about this handler for logging purposes. + log_info: (PeerId, ConnectionId), } enum HandlerState { @@ -221,8 +223,9 @@ where pub fn new( listen_protocol: SubstreamProtocol, ()>, fork_context: Arc, - log: &slog::Logger, resp_timeout: Duration, + peer_id: PeerId, + connection_id: ConnectionId, ) -> Self { RPCHandler { listen_protocol, @@ -240,8 +243,8 @@ where outbound_io_error_retries: 0, fork_context, waker: None, - log: log.clone(), resp_timeout, + log_info: (peer_id, connection_id), } } @@ -250,7 +253,12 @@ where fn shutdown(&mut self, goodbye_reason: Option<(Id, GoodbyeReason)>) { if matches!(self.state, HandlerState::Active) { if !self.dial_queue.is_empty() { - debug!(self.log, "Starting handler shutdown"; "unsent_queued_requests" => self.dial_queue.len()); + debug!( + unsent_queued_requests = self.dial_queue.len(), + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Starting handler shutdown" + ); } // We now drive to completion communications already dialed/established while let Some((id, req)) = self.dial_queue.pop() { @@ -297,8 +305,10 @@ where let Some(inbound_info) = self.inbound_substreams.get_mut(&inbound_id) else { if !matches!(response, RpcResponse::StreamTermination(..)) { // the stream is closed after sending the expected number of responses - trace!(self.log, "Inbound stream has expired. Response not sent"; - "response" => %response, "id" => inbound_id); + trace!(%response, id = ?inbound_id, + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Inbound stream has expired. Response not sent"); } return; }; @@ -313,8 +323,10 @@ where if matches!(self.state, HandlerState::Deactivated) { // we no longer send responses after the handler is deactivated - debug!(self.log, "Response not sent. Deactivated handler"; - "response" => %response, "id" => inbound_id); + debug!(%response, id = ?inbound_id, + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Response not sent. Deactivated handler"); return; } inbound_info.pending_items.push_back(response); @@ -381,7 +393,11 @@ where match delay.as_mut().poll(cx) { Poll::Ready(_) => { self.state = HandlerState::Deactivated; - debug!(self.log, "Shutdown timeout elapsed, Handler deactivated"); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Shutdown timeout elapsed, Handler deactivated" + ); return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( HandlerEvent::Close(RPCError::Disconnected), )); @@ -428,7 +444,10 @@ where outbound_err, ))); } else { - crit!(self.log, "timed out substream not in the books"; "stream_id" => outbound_id.get_ref()); + crit!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + stream_id = ?outbound_id.get_ref(), "timed out substream not in the books"); } } @@ -557,10 +576,24 @@ where // BlocksByRange is the one that typically consumes the most time. // Its useful to log when the request was completed. if matches!(info.protocol, Protocol::BlocksByRange) { - debug!(self.log, "BlocksByRange Response sent"; "duration" => Instant::now().duration_since(info.request_start_time).as_secs()); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + duration = Instant::now() + .duration_since(info.request_start_time) + .as_secs(), + "BlocksByRange Response sent" + ); } if matches!(info.protocol, Protocol::BlobsByRange) { - debug!(self.log, "BlobsByRange Response sent"; "duration" => Instant::now().duration_since(info.request_start_time).as_secs()); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + duration = Instant::now() + .duration_since(info.request_start_time) + .as_secs(), + "BlobsByRange Response sent" + ); } // There is nothing more to process on this substream as it has @@ -583,10 +616,20 @@ where })); if matches!(info.protocol, Protocol::BlocksByRange) { - debug!(self.log, "BlocksByRange Response failed"; "duration" => info.request_start_time.elapsed().as_secs()); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + duration = info.request_start_time.elapsed().as_secs(), + "BlocksByRange Response failed" + ); } if matches!(info.protocol, Protocol::BlobsByRange) { - debug!(self.log, "BlobsByRange Response failed"; "duration" => info.request_start_time.elapsed().as_secs()); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + duration = info.request_start_time.elapsed().as_secs(), + "BlobsByRange Response failed" + ); } break; } @@ -695,7 +738,7 @@ where // stream closed // if we expected multiple streams send a stream termination, // else report the stream terminating only. - //trace!(self.log, "RPC Response - stream closed by remote"); + //"RPC Response - stream closed by remote"); // drop the stream let delay_key = &entry.get().delay_key; let request_id = entry.get().req_id; @@ -772,7 +815,11 @@ where } } OutboundSubstreamState::Poisoned => { - crit!(self.log, "Poisoned outbound substream"); + crit!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Poisoned outbound substream" + ); unreachable!("Coding Error: Outbound substream is poisoned") } } @@ -804,7 +851,11 @@ where && self.events_out.is_empty() && self.dial_negotiated == 0 { - debug!(self.log, "Goodbye sent, Handler deactivated"); + debug!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + "Goodbye sent, Handler deactivated" + ); self.state = HandlerState::Deactivated; return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( HandlerEvent::Close(RPCError::Disconnected), @@ -997,7 +1048,11 @@ where ) .is_some() { - crit!(self.log, "Duplicate outbound substream id"; "id" => self.current_outbound_substream_id); + crit!( + peer_id = %self.log_info.0, + connection_id = %self.log_info.1, + + id = ?self.current_outbound_substream_id, "Duplicate outbound substream id"); } self.current_outbound_substream_id.0 += 1; } @@ -1045,17 +1100,6 @@ where } } -impl slog::Value for SubstreamId { - fn serialize( - &self, - record: &slog::Record, - key: slog::Key, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - slog::Value::serialize(&self.0, record, key, serializer) - } -} - /// Creates a future that can be polled that will send any queued message to the peer. /// /// This function returns the given substream, along with whether it has been closed or not. Any diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index 2f6200a836..b748ab11c0 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -578,7 +578,7 @@ pub enum RpcSuccessResponse { Pong(Ping), /// A response to a META_DATA request. - MetaData(MetaData), + MetaData(Arc>), } /// Indicates which response is being terminated by a stream termination response. @@ -864,19 +864,3 @@ impl std::fmt::Display for DataColumnsByRootRequest { ) } } - -impl slog::KV for StatusMessage { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - use slog::Value; - serializer.emit_arguments("fork_digest", &format_args!("{:?}", self.fork_digest))?; - Value::serialize(&self.finalized_epoch, record, "finalized_epoch", serializer)?; - serializer.emit_arguments("finalized_root", &format_args!("{}", self.finalized_root))?; - Value::serialize(&self.head_slot, record, "head_slot", serializer)?; - serializer.emit_arguments("head_root", &format_args!("{}", self.head_root))?; - slog::Result::Ok(()) - } -} diff --git a/beacon_node/lighthouse_network/src/rpc/mod.rs b/beacon_node/lighthouse_network/src/rpc/mod.rs index 03f1395b8b..f5085e798c 100644 --- a/beacon_node/lighthouse_network/src/rpc/mod.rs +++ b/beacon_node/lighthouse_network/src/rpc/mod.rs @@ -13,13 +13,14 @@ use libp2p::swarm::{ }; use libp2p::swarm::{ConnectionClosed, FromSwarm, SubstreamProtocol, THandlerInEvent}; use libp2p::PeerId; +use logging::crit; use rate_limiter::{RPCRateLimiter as RateLimiter, RateLimitedErr}; -use slog::{crit, debug, o, trace}; use std::marker::PhantomData; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; +use tracing::{debug, instrument, trace}; use types::{EthSpec, ForkContext}; pub(crate) use handler::{HandlerErr, HandlerEvent}; @@ -159,8 +160,6 @@ pub struct RPC { events: Vec>, fork_context: Arc, enable_light_client_server: bool, - /// Slog logger for RPC behaviour. - log: slog::Logger, /// Networking constant values network_params: NetworkParams, /// A sequential counter indicating when data gets modified. @@ -168,25 +167,28 @@ pub struct RPC { } impl RPC { + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn new( fork_context: Arc, enable_light_client_server: bool, inbound_rate_limiter_config: Option, outbound_rate_limiter_config: Option, - log: slog::Logger, network_params: NetworkParams, seq_number: u64, ) -> Self { - let log = log.new(o!("service" => "libp2p_rpc")); - let inbound_limiter = inbound_rate_limiter_config.map(|config| { - debug!(log, "Using inbound rate limiting params"; "config" => ?config); + debug!(?config, "Using inbound rate limiting params"); RateLimiter::new_with_config(config.0, fork_context.clone()) .expect("Inbound limiter configuration parameters are valid") }); let self_limiter = outbound_rate_limiter_config.map(|config| { - SelfRateLimiter::new(config, fork_context.clone(), log.clone()) + SelfRateLimiter::new(config, fork_context.clone()) .expect("Configuration parameters are valid") }); @@ -196,7 +198,6 @@ impl RPC { events: Vec::new(), fork_context, enable_light_client_server, - log, network_params, seq_number, } @@ -205,6 +206,12 @@ impl RPC { /// Sends an RPC response. /// /// The peer must be connected for this to succeed. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn send_response( &mut self, peer_id: PeerId, @@ -222,6 +229,12 @@ impl RPC { /// Submits an RPC request. /// /// The peer must be connected for this to succeed. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn send_request(&mut self, peer_id: PeerId, request_id: Id, req: RequestType) { let event = if let Some(self_limiter) = self.self_limiter.as_mut() { match self_limiter.allows(peer_id, request_id, req) { @@ -232,18 +245,24 @@ impl RPC { } } } else { - ToSwarm::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: RPCSend::Request(request_id, req), - } + RPCSend::Request(request_id, req) }; - self.events.push(event); + self.events.push(BehaviourAction::NotifyHandler { + peer_id, + handler: NotifyHandler::Any, + event, + }); } /// Lighthouse wishes to disconnect from this peer by sending a Goodbye message. This /// gracefully terminates the RPC behaviour with a goodbye message. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn shutdown(&mut self, peer_id: PeerId, id: Id, reason: GoodbyeReason) { self.events.push(ToSwarm::NotifyHandler { peer_id, @@ -252,16 +271,28 @@ impl RPC { }); } + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn update_seq_number(&mut self, seq_number: u64) { self.seq_number = seq_number } /// Send a Ping request to the destination `PeerId` via `ConnectionId`. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p_rpc"), + name = "libp2p_rpc", + skip_all + )] pub fn ping(&mut self, peer_id: PeerId, id: Id) { let ping = Ping { data: self.seq_number, }; - trace!(self.log, "Sending Ping"; "peer_id" => %peer_id); + trace!(%peer_id, "Sending Ping"); self.send_request(peer_id, id, RequestType::Ping(ping)); } } @@ -291,14 +322,13 @@ where }, (), ); - let log = self - .log - .new(slog::o!("peer_id" => peer_id.to_string(), "connection_id" => connection_id.to_string())); + let handler = RPCHandler::new( protocol, self.fork_context.clone(), - &log, self.network_params.resp_timeout, + peer_id, + connection_id, ); Ok(handler) @@ -323,15 +353,12 @@ where (), ); - let log = self - .log - .new(slog::o!("peer_id" => peer_id.to_string(), "connection_id" => connection_id.to_string())); - let handler = RPCHandler::new( protocol, self.fork_context.clone(), - &log, self.network_params.resp_timeout, + peer_id, + connection_id, ); Ok(handler) @@ -421,10 +448,10 @@ where | Protocol::BlobsByRoot | Protocol::DataColumnsByRoot ) { - debug!(self.log, "Request too large to process"; "request" => %r#type, "protocol" => %protocol); + debug!(request = %r#type, %protocol, "Request too large to process"); } else { // Other protocols shouldn't be sending large messages, we should flag the peer kind - crit!(self.log, "Request size too large to ever be processed"; "protocol" => %protocol); + crit!(%protocol, "Request size too large to ever be processed"); } // send an error code to the peer. // the handler upon receiving the error code will send it back to the behaviour @@ -440,8 +467,7 @@ where return; } Err(RateLimitedErr::TooSoon(wait_time)) => { - debug!(self.log, "Request exceeds the rate limit"; - "request" => %r#type, "peer_id" => %peer_id, "wait_time_ms" => wait_time.as_millis()); + debug!(request = %r#type, %peer_id, wait_time_ms = wait_time.as_millis(), "Request exceeds the rate limit"); // send an error code to the peer. // the handler upon receiving the error code will send it back to the behaviour self.send_response( @@ -462,7 +488,7 @@ where // If we received a Ping, we queue a Pong response. if let RequestType::Ping(_) = r#type { - trace!(self.log, "Received Ping, queueing Pong";"connection_id" => %conn_id, "peer_id" => %peer_id); + trace!(connection_id = %conn_id, %peer_id, "Received Ping, queueing Pong"); self.send_response( peer_id, (conn_id, substream_id), @@ -526,53 +552,3 @@ where Poll::Pending } } - -impl slog::KV for RPCMessage -where - E: EthSpec, - Id: ReqId, -{ - fn serialize( - &self, - _record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - serializer.emit_arguments("peer_id", &format_args!("{}", self.peer_id))?; - match &self.message { - Ok(received) => { - let (msg_kind, protocol) = match received { - RPCReceived::Request(Request { r#type, .. }) => { - ("request", r#type.versioned_protocol().protocol()) - } - RPCReceived::Response(_, res) => ("response", res.protocol()), - RPCReceived::EndOfStream(_, end) => ( - "end_of_stream", - match end { - ResponseTermination::BlocksByRange => Protocol::BlocksByRange, - ResponseTermination::BlocksByRoot => Protocol::BlocksByRoot, - ResponseTermination::BlobsByRange => Protocol::BlobsByRange, - ResponseTermination::BlobsByRoot => Protocol::BlobsByRoot, - ResponseTermination::DataColumnsByRoot => Protocol::DataColumnsByRoot, - ResponseTermination::DataColumnsByRange => Protocol::DataColumnsByRange, - ResponseTermination::LightClientUpdatesByRange => { - Protocol::LightClientUpdatesByRange - } - }, - ), - }; - serializer.emit_str("msg_kind", msg_kind)?; - serializer.emit_arguments("protocol", &format_args!("{}", protocol))?; - } - Err(error) => { - let (msg_kind, protocol) = match &error { - HandlerErr::Inbound { proto, .. } => ("inbound_err", *proto), - HandlerErr::Outbound { proto, .. } => ("outbound_err", *proto), - }; - serializer.emit_str("msg_kind", msg_kind)?; - serializer.emit_arguments("protocol", &format_args!("{}", protocol))?; - } - }; - - slog::Result::Ok(()) - } -} diff --git a/beacon_node/lighthouse_network/src/rpc/self_limiter.rs b/beacon_node/lighthouse_network/src/rpc/self_limiter.rs index ae63e5cdb5..af6ac37d2c 100644 --- a/beacon_node/lighthouse_network/src/rpc/self_limiter.rs +++ b/beacon_node/lighthouse_network/src/rpc/self_limiter.rs @@ -7,9 +7,10 @@ use std::{ use futures::FutureExt; use libp2p::{swarm::NotifyHandler, PeerId}; -use slog::{crit, debug, Logger}; +use logging::crit; use smallvec::SmallVec; use tokio_util::time::DelayQueue; +use tracing::debug; use types::{EthSpec, ForkContext}; use super::{ @@ -35,9 +36,7 @@ pub(crate) struct SelfRateLimiter { /// Rate limiter for our own requests. limiter: RateLimiter, /// Requests that are ready to be sent. - ready_requests: SmallVec<[BehaviourAction; 3]>, - /// Slog logger. - log: Logger, + ready_requests: SmallVec<[(PeerId, RPCSend); 3]>, } /// Error returned when the rate limiter does not accept a request. @@ -54,9 +53,8 @@ impl SelfRateLimiter { pub fn new( config: OutboundRateLimiterConfig, fork_context: Arc, - log: Logger, ) -> Result { - debug!(log, "Using self rate limiting params"; "config" => ?config); + debug!(?config, "Using self rate limiting params"); let limiter = RateLimiter::new_with_config(config.0, fork_context)?; Ok(SelfRateLimiter { @@ -64,7 +62,6 @@ impl SelfRateLimiter { next_peer_request: Default::default(), limiter, ready_requests: Default::default(), - log, }) } @@ -76,7 +73,7 @@ impl SelfRateLimiter { peer_id: PeerId, request_id: Id, req: RequestType, - ) -> Result, Error> { + ) -> Result, Error> { let protocol = req.versioned_protocol().protocol(); // First check that there are not already other requests waiting to be sent. if let Some(queued_requests) = self.delayed_requests.get_mut(&(peer_id, protocol)) { @@ -84,7 +81,7 @@ impl SelfRateLimiter { return Err(Error::PendingRequests); } - match Self::try_send_request(&mut self.limiter, peer_id, request_id, req, &self.log) { + match Self::try_send_request(&mut self.limiter, peer_id, request_id, req) { Err((rate_limited_req, wait_time)) => { let key = (peer_id, protocol); self.next_peer_request.insert(key, wait_time); @@ -107,14 +104,9 @@ impl SelfRateLimiter { peer_id: PeerId, request_id: Id, req: RequestType, - log: &Logger, - ) -> Result, (QueuedRequest, Duration)> { + ) -> Result, (QueuedRequest, Duration)> { match limiter.allows(&peer_id, &req) { - Ok(()) => Ok(BehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: RPCSend::Request(request_id, req), - }), + Ok(()) => Ok(RPCSend::Request(request_id, req)), Err(e) => { let protocol = req.versioned_protocol(); match e { @@ -122,18 +114,13 @@ impl SelfRateLimiter { // this should never happen with default parameters. Let's just send the request. // Log a crit since this is a config issue. crit!( - log, - "Self rate limiting error for a batch that will never fit. Sending request anyway. Check configuration parameters."; - "protocol" => %req.versioned_protocol().protocol() + protocol = %req.versioned_protocol().protocol(), + "Self rate limiting error for a batch that will never fit. Sending request anyway. Check configuration parameters." ); - Ok(BehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: RPCSend::Request(request_id, req), - }) + Ok(RPCSend::Request(request_id, req)) } RateLimitedErr::TooSoon(wait_time) => { - debug!(log, "Self rate limiting"; "protocol" => %protocol.protocol(), "wait_time_ms" => wait_time.as_millis(), "peer_id" => %peer_id); + debug!(protocol = %protocol.protocol(), wait_time_ms = wait_time.as_millis(), %peer_id, "Self rate limiting"); Err((QueuedRequest { req, request_id }, wait_time)) } } @@ -147,8 +134,7 @@ impl SelfRateLimiter { if let Entry::Occupied(mut entry) = self.delayed_requests.entry((peer_id, protocol)) { let queued_requests = entry.get_mut(); while let Some(QueuedRequest { req, request_id }) = queued_requests.pop_front() { - match Self::try_send_request(&mut self.limiter, peer_id, request_id, req, &self.log) - { + match Self::try_send_request(&mut self.limiter, peer_id, request_id, req) { Err((rate_limited_req, wait_time)) => { let key = (peer_id, protocol); self.next_peer_request.insert(key, wait_time); @@ -156,7 +142,7 @@ impl SelfRateLimiter { // If one fails just wait for the next window that allows sending requests. return; } - Ok(event) => self.ready_requests.push(event), + Ok(event) => self.ready_requests.push((peer_id, event)), } } if queued_requests.is_empty() { @@ -203,8 +189,12 @@ impl SelfRateLimiter { let _ = self.limiter.poll_unpin(cx); // Finally return any queued events. - if !self.ready_requests.is_empty() { - return Poll::Ready(self.ready_requests.remove(0)); + if let Some((peer_id, event)) = self.ready_requests.pop() { + return Poll::Ready(BehaviourAction::NotifyHandler { + peer_id, + handler: NotifyHandler::Any, + event, + }); } Poll::Pending @@ -219,13 +209,14 @@ mod tests { use crate::rpc::{Ping, Protocol, RequestType}; use crate::service::api_types::{AppRequestId, RequestId, SingleLookupReqId, SyncRequestId}; use libp2p::PeerId; + use logging::create_test_tracing_subscriber; use std::time::Duration; use types::{EthSpec, ForkContext, Hash256, MainnetEthSpec, Slot}; /// Test that `next_peer_request_ready` correctly maintains the queue. #[tokio::test] async fn test_next_peer_request_ready() { - let log = logging::test_logger(); + create_test_tracing_subscriber(); let config = OutboundRateLimiterConfig(RateLimiterConfig { ping_quota: Quota::n_every(1, 2), ..Default::default() @@ -236,7 +227,7 @@ mod tests { &MainnetEthSpec::default_spec(), )); let mut limiter: SelfRateLimiter = - SelfRateLimiter::new(config, fork_context, log).unwrap(); + SelfRateLimiter::new(config, fork_context).unwrap(); let peer_id = PeerId::random(); let lookup_id = 0; diff --git a/beacon_node/lighthouse_network/src/service/api_types.rs b/beacon_node/lighthouse_network/src/service/api_types.rs index e69c7aa5f7..894fff5074 100644 --- a/beacon_node/lighthouse_network/src/service/api_types.rs +++ b/beacon_node/lighthouse_network/src/service/api_types.rs @@ -218,22 +218,6 @@ impl std::convert::From> for RpcResponse { } } -impl slog::Value for RequestId { - fn serialize( - &self, - record: &slog::Record, - key: slog::Key, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - match self { - RequestId::Internal => slog::Value::serialize("Behaviour", record, key, serializer), - RequestId::Application(ref id) => { - slog::Value::serialize(&format_args!("{:?}", id), record, key, serializer) - } - } - } -} - macro_rules! impl_display { ($structname: ty, $format: literal, $($field:ident),*) => { impl Display for $structname { diff --git a/beacon_node/lighthouse_network/src/service/mod.rs b/beacon_node/lighthouse_network/src/service/mod.rs index 8586fd9cd3..0bf281fd75 100644 --- a/beacon_node/lighthouse_network/src/service/mod.rs +++ b/beacon_node/lighthouse_network/src/service/mod.rs @@ -14,9 +14,8 @@ use crate::rpc::{ RequestType, ResponseTermination, RpcErrorResponse, RpcResponse, RpcSuccessResponse, RPC, }; use crate::types::{ - attestation_sync_committee_topics, fork_core_topics, subnet_from_topic_hash, GossipEncoding, - GossipKind, GossipTopic, SnappyTransform, Subnet, SubnetDiscovery, ALTAIR_CORE_TOPICS, - BASE_CORE_TOPICS, CAPELLA_CORE_TOPICS, LIGHT_CLIENT_GOSSIP_TOPICS, + all_topics_at_fork, core_topics_to_subscribe, is_fork_non_core_topic, subnet_from_topic_hash, + GossipEncoding, GossipKind, GossipTopic, SnappyTransform, Subnet, SubnetDiscovery, }; use crate::EnrExt; use crate::Eth2Enr; @@ -33,12 +32,13 @@ use libp2p::swarm::behaviour::toggle::Toggle; use libp2p::swarm::{NetworkBehaviour, Swarm, SwarmEvent}; use libp2p::upnp::tokio::Behaviour as Upnp; use libp2p::{identify, PeerId, SwarmBuilder}; -use slog::{crit, debug, info, o, trace, warn}; +use logging::crit; use std::num::{NonZeroU8, NonZeroUsize}; use std::path::PathBuf; use std::pin::Pin; use std::sync::Arc; use std::time::Duration; +use tracing::{debug, info, instrument, trace, warn}; use types::{ consts::altair::SYNC_COMMITTEE_SUBNET_COUNT, EnrForkId, EthSpec, ForkContext, Slot, SubnetId, }; @@ -161,23 +161,24 @@ pub struct Network { gossip_cache: GossipCache, /// This node's PeerId. pub local_peer_id: PeerId, - /// Logger for behaviour actions. - log: slog::Logger, } /// Implements the combined behaviour for the libp2p service. impl Network { + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub async fn new( executor: task_executor::TaskExecutor, mut ctx: ServiceContext<'_>, - log: &slog::Logger, ) -> Result<(Self, Arc>), String> { - let log = log.new(o!("service"=> "libp2p")); - let config = ctx.config.clone(); - trace!(log, "Libp2p Service starting"); + trace!("Libp2p Service starting"); // initialise the node's ID - let local_keypair = utils::load_private_key(&config, &log); + let local_keypair = utils::load_private_key(&config); // Trusted peers will also be marked as explicit in GossipSub. // Cfr. https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#explicit-peering-agreements @@ -193,7 +194,6 @@ impl Network { local_keypair.clone(), &config, &ctx.enr_fork_id, - &log, &ctx.chain_spec, )?; @@ -202,15 +202,13 @@ impl Network { ctx.chain_spec .custody_group_count(config.subscribe_all_data_column_subnets) }); - let meta_data = - utils::load_or_build_metadata(&config.network_dir, custody_group_count, &log); + let meta_data = utils::load_or_build_metadata(&config.network_dir, custody_group_count); let seq_number = *meta_data.seq_number(); let globals = NetworkGlobals::new( enr, meta_data, trusted_peers, config.disable_peer_scoring, - &log, config.clone(), ctx.chain_spec.clone(), ); @@ -275,19 +273,44 @@ impl Network { )? }; - trace!(log, "Using peer score params"; "params" => ?params); + trace!(?params, "Using peer score params"); // Set up a scoring update interval let update_gossipsub_scores = tokio::time::interval(params.decay_interval); - let max_topics = ctx.chain_spec.attestation_subnet_count as usize - + SYNC_COMMITTEE_SUBNET_COUNT as usize - + ctx.chain_spec.blob_sidecar_subnet_count_max() as usize - + ctx.chain_spec.data_column_sidecar_subnet_count as usize - + BASE_CORE_TOPICS.len() - + ALTAIR_CORE_TOPICS.len() - + CAPELLA_CORE_TOPICS.len() // 0 core deneb and electra topics - + LIGHT_CLIENT_GOSSIP_TOPICS.len(); + let current_and_future_forks = ForkName::list_all().into_iter().filter_map(|fork| { + if fork >= ctx.fork_context.current_fork() { + ctx.fork_context + .to_context_bytes(fork) + .map(|fork_digest| (fork, fork_digest)) + } else { + None + } + }); + + let all_topics_for_forks = current_and_future_forks + .map(|(fork, fork_digest)| { + all_topics_at_fork::(fork, &ctx.chain_spec) + .into_iter() + .map(|topic| { + Topic::new(GossipTopic::new( + topic, + GossipEncoding::default(), + fork_digest, + )) + .into() + }) + .collect::>() + }) + .collect::>(); + + // For simplicity find the fork with the most individual topics and assume all forks + // have the same topic count + let max_topics_at_any_fork = all_topics_for_forks + .iter() + .map(|topics| topics.len()) + .max() + .expect("each fork has at least 5 hardcoded core topics"); let possible_fork_digests = ctx.fork_context.all_fork_digests(); let filter = gossipsub::MaxCountSubscriptionFilter { @@ -297,9 +320,9 @@ impl Network { SYNC_COMMITTEE_SUBNET_COUNT, ), // during a fork we subscribe to both the old and new topics - max_subscribed_topics: max_topics * 4, + max_subscribed_topics: max_topics_at_any_fork * 4, // 424 in theory = (64 attestation + 4 sync committee + 7 core topics + 9 blob topics + 128 column topics) * 2 - max_subscriptions_per_request: max_topics * 2, + max_subscriptions_per_request: max_topics_at_any_fork * 2, }; // If metrics are enabled for libp2p build the configuration @@ -332,17 +355,9 @@ impl Network { // If we are using metrics, then register which topics we want to make sure to keep // track of if ctx.libp2p_registry.is_some() { - let topics_to_keep_metrics_for = attestation_sync_committee_topics::() - .map(|gossip_kind| { - Topic::from(GossipTopic::new( - gossip_kind, - GossipEncoding::default(), - enr_fork_id.fork_digest, - )) - .into() - }) - .collect::>(); - gossipsub.register_topics_for_metrics(topics_to_keep_metrics_for); + for topics in all_topics_for_forks { + gossipsub.register_topics_for_metrics(topics); + } } (gossipsub, update_gossipsub_scores) @@ -358,7 +373,6 @@ impl Network { config.enable_light_client_server, config.inbound_rate_limiter_config.clone(), config.outbound_rate_limiter_config.clone(), - log.clone(), network_params, seq_number, ); @@ -369,7 +383,6 @@ impl Network { local_keypair.clone(), &config, network_globals.clone(), - &log, &ctx.chain_spec, ) .await?; @@ -402,7 +415,7 @@ impl Network { target_peer_count: config.target_peers, ..Default::default() }; - PeerManager::new(peer_manager_cfg, network_globals.clone(), &log)? + PeerManager::new(peer_manager_cfg, network_globals.clone())? }; let connection_limits = { @@ -497,7 +510,6 @@ impl Network { update_gossipsub_scores, gossip_cache, local_peer_id, - log, }; network.start(&config).await?; @@ -512,10 +524,25 @@ impl Network { /// - Starts listening in the given ports. /// - Dials boot-nodes and libp2p peers. /// - Subscribes to starting gossipsub topics. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] async fn start(&mut self, config: &crate::NetworkConfig) -> Result<(), String> { let enr = self.network_globals.local_enr(); - info!(self.log, "Libp2p Starting"; "peer_id" => %enr.peer_id(), "bandwidth_config" => format!("{}-{}", config.network_load, NetworkLoad::from(config.network_load).name)); - debug!(self.log, "Attempting to open listening ports"; config.listen_addrs(), "discovery_enabled" => !config.disable_discovery, "quic_enabled" => !config.disable_quic_support); + info!( + peer_id = %enr.peer_id(), + bandwidth_config = format!("{}-{}", config.network_load, NetworkLoad::from(config.network_load).name), + "Libp2p Starting" + ); + debug!( + listen_addrs = ?config.listen_addrs(), + discovery_enabled = !config.disable_discovery, + quic_enabled = !config.disable_quic_support, + "Attempting to open listening ports" + ); for listen_multiaddr in config.listen_addrs().libp2p_addresses() { // If QUIC is disabled, ignore listening on QUIC ports @@ -529,14 +556,13 @@ impl Network { Ok(_) => { let mut log_address = listen_multiaddr; log_address.push(MProtocol::P2p(enr.peer_id())); - info!(self.log, "Listening established"; "address" => %log_address); + info!(address = %log_address, "Listening established"); } Err(err) => { crit!( - self.log, - "Unable to listen on libp2p address"; - "error" => ?err, - "listen_multiaddr" => %listen_multiaddr, + error = ?err, + %listen_multiaddr, + "Unable to listen on libp2p address" ); return Err("Libp2p was unable to listen on the given listen address.".into()); } @@ -548,9 +574,9 @@ impl Network { // strip the p2p protocol if it exists strip_peer_id(&mut multiaddr); match self.swarm.dial(multiaddr.clone()) { - Ok(()) => debug!(self.log, "Dialing libp2p peer"; "address" => %multiaddr), + Ok(()) => debug!(address = %multiaddr, "Dialing libp2p peer"), Err(err) => { - debug!(self.log, "Could not connect to peer"; "address" => %multiaddr, "error" => ?err) + debug!(address = %multiaddr, error = ?err, "Could not connect to peer") } }; }; @@ -613,12 +639,12 @@ impl Network { if self.subscribe_kind(topic_kind.clone()) { subscribed_topics.push(topic_kind.clone()); } else { - warn!(self.log, "Could not subscribe to topic"; "topic" => %topic_kind); + warn!(topic = %topic_kind, "Could not subscribe to topic"); } } if !subscribed_topics.is_empty() { - info!(self.log, "Subscribed to topics"; "topics" => ?subscribed_topics); + info!(topics = ?subscribed_topics, "Subscribed to topics"); } Ok(()) @@ -627,48 +653,114 @@ impl Network { /* Public Accessible Functions to interact with the behaviour */ /// The routing pub-sub mechanism for eth2. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn gossipsub_mut(&mut self) -> &mut Gossipsub { &mut self.swarm.behaviour_mut().gossipsub } /// The Eth2 RPC specified in the wire-0 protocol. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn eth2_rpc_mut(&mut self) -> &mut RPC { &mut self.swarm.behaviour_mut().eth2_rpc } /// Discv5 Discovery protocol. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn discovery_mut(&mut self) -> &mut Discovery { &mut self.swarm.behaviour_mut().discovery } /// Provides IP addresses and peer information. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn identify_mut(&mut self) -> &mut identify::Behaviour { &mut self.swarm.behaviour_mut().identify } /// The peer manager that keeps track of peer's reputation and status. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn peer_manager_mut(&mut self) -> &mut PeerManager { &mut self.swarm.behaviour_mut().peer_manager } /// The routing pub-sub mechanism for eth2. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn gossipsub(&self) -> &Gossipsub { &self.swarm.behaviour().gossipsub } /// The Eth2 RPC specified in the wire-0 protocol. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn eth2_rpc(&self) -> &RPC { &self.swarm.behaviour().eth2_rpc } /// Discv5 Discovery protocol. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn discovery(&self) -> &Discovery { &self.swarm.behaviour().discovery } /// Provides IP addresses and peer information. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn identify(&self) -> &identify::Behaviour { &self.swarm.behaviour().identify } /// The peer manager that keeps track of peer's reputation and status. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn peer_manager(&self) -> &PeerManager { &self.swarm.behaviour().peer_manager } /// Returns the local ENR of the node. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn local_enr(&self) -> Enr { self.network_globals.local_enr() } @@ -677,6 +769,12 @@ impl Network { /// Subscribes to a gossipsub topic kind, letting the network service determine the /// encoding and fork version. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn subscribe_kind(&mut self, kind: GossipKind) -> bool { let gossip_topic = GossipTopic::new( kind, @@ -689,6 +787,12 @@ impl Network { /// Unsubscribes from a gossipsub topic kind, letting the network service determine the /// encoding and fork version. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn unsubscribe_kind(&mut self, kind: GossipKind) -> bool { let gossip_topic = GossipTopic::new( kind, @@ -699,42 +803,42 @@ impl Network { } /// Subscribe to all required topics for the `new_fork` with the given `new_fork_digest`. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn subscribe_new_fork_topics(&mut self, new_fork: ForkName, new_fork_digest: [u8; 4]) { - // Subscribe to existing topics with new fork digest + // Re-subscribe to non-core topics with the new fork digest let subscriptions = self.network_globals.gossipsub_subscriptions.read().clone(); for mut topic in subscriptions.into_iter() { - topic.fork_digest = new_fork_digest; - self.subscribe(topic); + if is_fork_non_core_topic(&topic, new_fork) { + topic.fork_digest = new_fork_digest; + self.subscribe(topic); + } } // Subscribe to core topics for the new fork - for kind in fork_core_topics::( - &new_fork, - &self.fork_context.spec, + for kind in core_topics_to_subscribe::( + new_fork, &self.network_globals.as_topic_config(), + &self.fork_context.spec, ) { let topic = GossipTopic::new(kind, GossipEncoding::default(), new_fork_digest); self.subscribe(topic); } - // TODO(das): unsubscribe from blob topics at the Fulu fork - - // Register the new topics for metrics - let topics_to_keep_metrics_for = attestation_sync_committee_topics::() - .map(|gossip_kind| { - Topic::from(GossipTopic::new( - gossip_kind, - GossipEncoding::default(), - new_fork_digest, - )) - .into() - }) - .collect::>(); - self.gossipsub_mut() - .register_topics_for_metrics(topics_to_keep_metrics_for); + // Already registered all possible gossipsub topics for metrics } /// Unsubscribe from all topics that doesn't have the given fork_digest + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn unsubscribe_from_fork_topics_except(&mut self, except: [u8; 4]) { let subscriptions = self.network_globals.gossipsub_subscriptions.read().clone(); for topic in subscriptions @@ -747,6 +851,12 @@ impl Network { } /// Remove topic weight from all topics that don't have the given fork digest. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn remove_topic_weight_except(&mut self, except: [u8; 4]) { let new_param = TopicScoreParams { topic_weight: 0.0, @@ -762,15 +872,21 @@ impl Network { .gossipsub_mut() .set_topic_params(libp2p_topic, new_param.clone()) { - Ok(_) => debug!(self.log, "Removed topic weight"; "topic" => %topic), + Ok(_) => debug!(%topic, "Removed topic weight"), Err(e) => { - warn!(self.log, "Failed to remove topic weight"; "topic" => %topic, "error" => e) + warn!(%topic, error = e, "Failed to remove topic weight") } } } } /// Returns the scoring parameters for a topic if set. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn get_topic_params(&self, topic: GossipTopic) -> Option<&TopicScoreParams> { self.swarm .behaviour() @@ -781,6 +897,12 @@ impl Network { /// Subscribes to a gossipsub topic. /// /// Returns `true` if the subscription was successful and `false` otherwise. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn subscribe(&mut self, topic: GossipTopic) -> bool { // update the network globals self.network_globals @@ -792,17 +914,23 @@ impl Network { match self.gossipsub_mut().subscribe(&topic) { Err(e) => { - warn!(self.log, "Failed to subscribe to topic"; "topic" => %topic, "error" => ?e); + warn!(%topic, error = ?e, "Failed to subscribe to topic"); false } Ok(_) => { - debug!(self.log, "Subscribed to topic"; "topic" => %topic); + debug!(%topic, "Subscribed to topic"); true } } } /// Unsubscribe from a gossipsub topic. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn unsubscribe(&mut self, topic: GossipTopic) -> bool { // update the network globals self.network_globals @@ -813,20 +941,17 @@ impl Network { // unsubscribe from the topic let libp2p_topic: Topic = topic.clone().into(); - match self.gossipsub_mut().unsubscribe(&libp2p_topic) { - Err(_) => { - warn!(self.log, "Failed to unsubscribe from topic"; "topic" => %libp2p_topic); - false - } - Ok(v) => { - // Inform the network - debug!(self.log, "Unsubscribed to topic"; "topic" => %topic); - v - } - } + debug!(%topic, "Unsubscribed to topic"); + self.gossipsub_mut().unsubscribe(&libp2p_topic) } /// Publishes a list of messages on the pubsub (gossipsub) behaviour, choosing the encoding. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn publish(&mut self, messages: Vec>) { for message in messages { for topic in message.topics(GossipEncoding::default(), self.enr_fork_id.fork_digest) { @@ -838,17 +963,15 @@ impl Network { match e { PublishError::Duplicate => { debug!( - self.log, - "Attempted to publish duplicate message"; - "kind" => %topic.kind(), + kind = %topic.kind(), + "Attempted to publish duplicate message" ); } ref e => { warn!( - self.log, - "Could not publish message"; - "error" => ?e, - "kind" => %topic.kind(), + error = ?e, + kind = %topic.kind(), + "Could not publish message" ); } } @@ -883,6 +1006,12 @@ impl Network { /// Informs the gossipsub about the result of a message validation. /// If the message is valid it will get propagated by gossipsub. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn report_message_validation_result( &mut self, propagation_source: &PeerId, @@ -908,17 +1037,21 @@ impl Network { } } - if let Err(e) = self.gossipsub_mut().report_message_validation_result( + self.gossipsub_mut().report_message_validation_result( &message_id, propagation_source, validation_result, - ) { - warn!(self.log, "Failed to report message validation"; "message_id" => %message_id, "peer_id" => %propagation_source, "error" => ?e); - } + ); } /// Updates the current gossipsub scoring parameters based on the validator count and current /// slot. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn update_gossipsub_parameters( &mut self, active_validators: usize, @@ -933,12 +1066,12 @@ impl Network { GossipTopic::new(kind, GossipEncoding::default(), fork_digest).into() }; - debug!(self.log, "Updating gossipsub score parameters"; - "active_validators" => active_validators); - trace!(self.log, "Updated gossipsub score parameters"; - "beacon_block_params" => ?beacon_block_params, - "beacon_aggregate_proof_params" => ?beacon_aggregate_proof_params, - "beacon_attestation_subnet_params" => ?beacon_attestation_subnet_params, + debug!(active_validators, "Updating gossipsub score parameters"); + trace!( + ?beacon_block_params, + ?beacon_aggregate_proof_params, + ?beacon_attestation_subnet_params, + "Updated gossipsub score parameters" ); self.gossipsub_mut() @@ -962,6 +1095,12 @@ impl Network { /* Eth2 RPC behaviour functions */ /// Send a request to a peer over RPC. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn send_request( &mut self, peer_id: PeerId, @@ -979,6 +1118,12 @@ impl Network { } /// Send a successful response to a peer over RPC. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn send_response( &mut self, peer_id: PeerId, @@ -991,6 +1136,12 @@ impl Network { } /// Inform the peer that their request produced an error. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn send_error_response( &mut self, peer_id: PeerId, @@ -1008,11 +1159,22 @@ impl Network { } /* Peer management functions */ - + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn testing_dial(&mut self, addr: Multiaddr) -> Result<(), libp2p::swarm::DialError> { self.swarm.dial(addr) } + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn report_peer( &mut self, peer_id: &PeerId, @@ -1028,6 +1190,12 @@ impl Network { /// /// This will send a goodbye, disconnect and then ban the peer. /// This is fatal for a peer, and should be used in unrecoverable circumstances. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn goodbye_peer(&mut self, peer_id: &PeerId, reason: GoodbyeReason, source: ReportSource) { self.peer_manager_mut() .goodbye_peer(peer_id, reason, source); @@ -1035,16 +1203,34 @@ impl Network { /// Hard (ungraceful) disconnect for testing purposes only /// Use goodbye_peer for disconnections, do not use this function. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn __hard_disconnect_testing_only(&mut self, peer_id: PeerId) { let _ = self.swarm.disconnect_peer_id(peer_id); } /// Returns an iterator over all enr entries in the DHT. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn enr_entries(&self) -> Vec { self.discovery().table_entries_enr() } /// Add an ENR to the routing table of the discovery mechanism. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn add_enr(&mut self, enr: Enr) { self.discovery_mut().add_enr(enr); } @@ -1052,9 +1238,15 @@ impl Network { /// Updates a subnet value to the ENR attnets/syncnets bitfield. /// /// The `value` is `true` if a subnet is being added and false otherwise. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn update_enr_subnet(&mut self, subnet_id: Subnet, value: bool) { if let Err(e) = self.discovery_mut().update_enr_bitfield(subnet_id, value) { - crit!(self.log, "Could not update ENR bitfield"; "error" => e); + crit!(error = e, "Could not update ENR bitfield"); } // update the local meta data which informs our peers of the update during PINGS self.update_metadata_bitfields(); @@ -1062,6 +1254,12 @@ impl Network { /// Attempts to discover new peers for a given subnet. The `min_ttl` gives the time at which we /// would like to retain the peers for. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn discover_subnet_peers(&mut self, subnets_to_discover: Vec) { // If discovery is not started or disabled, ignore the request if !self.discovery().started { @@ -1092,12 +1290,11 @@ impl Network { .count(); if peers_on_subnet >= TARGET_SUBNET_PEERS { trace!( - self.log, - "Discovery query ignored"; - "subnet" => ?s.subnet, - "reason" => "Already connected to desired peers", - "connected_peers_on_subnet" => peers_on_subnet, - "target_subnet_peers" => TARGET_SUBNET_PEERS, + subnet = ?s.subnet, + reason = "Already connected to desired peers", + connected_peers_on_subnet = peers_on_subnet, + target_subnet_peers = TARGET_SUBNET_PEERS, + "Discovery query ignored" ); false // Queue an outgoing connection request to the cached peers that are on `s.subnet_id`. @@ -1117,6 +1314,12 @@ impl Network { } /// Updates the local ENR's "eth2" field with the latest EnrForkId. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub fn update_fork_version(&mut self, enr_fork_id: EnrForkId) { self.discovery_mut().update_eth2_enr(enr_fork_id.clone()); @@ -1127,6 +1330,12 @@ impl Network { /* Private internal functions */ /// Updates the current meta data of the node to match the local ENR. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn update_metadata_bitfields(&mut self) { let local_attnets = self .discovery_mut() @@ -1154,15 +1363,27 @@ impl Network { drop(meta_data_w); self.eth2_rpc_mut().update_seq_number(seq_number); // Save the updated metadata to disk - utils::save_metadata_to_disk(&self.network_dir, meta_data, &self.log); + utils::save_metadata_to_disk(&self.network_dir, meta_data); } /// Sends a Ping request to the peer. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn ping(&mut self, peer_id: PeerId) { self.eth2_rpc_mut().ping(peer_id, RequestId::Internal); } /// Sends a METADATA request to a peer. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn send_meta_data_request(&mut self, peer_id: PeerId) { let event = if self.fork_context.spec.is_peer_das_scheduled() { // Nodes with higher custody will probably start advertising it @@ -1177,6 +1398,12 @@ impl Network { } /// Sends a METADATA response to a peer. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn send_meta_data_response( &mut self, _req: MetadataRequest, @@ -1186,7 +1413,7 @@ impl Network { ) { let metadata = self.network_globals.local_metadata.read().clone(); // The encoder is responsible for sending the negotiated version of the metadata - let event = RpcResponse::Success(RpcSuccessResponse::MetaData(metadata)); + let event = RpcResponse::Success(RpcSuccessResponse::MetaData(Arc::new(metadata))); self.eth2_rpc_mut() .send_response(peer_id, id, request_id, event); } @@ -1194,6 +1421,12 @@ impl Network { // RPC Propagation methods /// Queues the response to be sent upwards as long at it was requested outside the Behaviour. #[must_use = "return the response"] + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn build_response( &mut self, id: RequestId, @@ -1212,8 +1445,14 @@ impl Network { /// Dial cached Enrs in discovery service that are in the given `subnet_id` and aren't /// in Connected, Dialing or Banned state. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn dial_cached_enrs_in_subnet(&mut self, subnet: Subnet, spec: Arc) { - let predicate = subnet_predicate::(vec![subnet], &self.log, spec); + let predicate = subnet_predicate::(vec![subnet], spec); let peers_to_dial: Vec = self .discovery() .cached_enrs() @@ -1231,7 +1470,7 @@ impl Network { self.discovery_mut().remove_cached_enr(&enr.peer_id()); let peer_id = enr.peer_id(); if self.peer_manager_mut().dial_peer(enr) { - debug!(self.log, "Added cached ENR peer to dial queue"; "peer_id" => %peer_id); + debug!(%peer_id, "Added cached ENR peer to dial queue"); } } } @@ -1239,6 +1478,12 @@ impl Network { /* Sub-behaviour event handling functions */ /// Handle a gossipsub event. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_gs_event(&mut self, event: gossipsub::Event) -> Option> { match event { gossipsub::Event::Message { @@ -1250,15 +1495,13 @@ impl Network { // peer that originally published the message. match PubsubMessage::decode(&gs_msg.topic, &gs_msg.data, &self.fork_context) { Err(e) => { - debug!(self.log, "Could not decode gossipsub message"; "topic" => ?gs_msg.topic,"error" => e); + debug!(topic = ?gs_msg.topic, error = e, "Could not decode gossipsub message"); //reject the message - if let Err(e) = self.gossipsub_mut().report_message_validation_result( + self.gossipsub_mut().report_message_validation_result( &id, &propagation_source, MessageAcceptance::Reject, - ) { - warn!(self.log, "Failed to report message validation"; "message_id" => %id, "peer_id" => %propagation_source, "error" => ?e); - } + ); } Ok(msg) => { // Notify the network @@ -1290,11 +1533,7 @@ impl Network { .publish(Topic::from(topic.clone()), data) { Ok(_) => { - debug!( - self.log, - "Gossip message published on retry"; - "topic" => topic_str - ); + debug!(topic = topic_str, "Gossip message published on retry"); metrics::inc_counter_vec( &metrics::GOSSIP_LATE_PUBLISH_PER_TOPIC_KIND, &[topic_str], @@ -1302,10 +1541,9 @@ impl Network { } Err(PublishError::Duplicate) => { debug!( - self.log, - "Gossip message publish ignored on retry"; - "reason" => "duplicate", - "topic" => topic_str + reason = "duplicate", + topic = topic_str, + "Gossip message publish ignored on retry" ); metrics::inc_counter_vec( &metrics::GOSSIP_FAILED_LATE_PUBLISH_PER_TOPIC_KIND, @@ -1314,10 +1552,9 @@ impl Network { } Err(e) => { warn!( - self.log, - "Gossip message publish failed on retry"; - "topic" => topic_str, - "error" => %e + topic = topic_str, + error = %e, + "Gossip message publish failed on retry" ); metrics::inc_counter_vec( &metrics::GOSSIP_FAILED_LATE_PUBLISH_PER_TOPIC_KIND, @@ -1338,7 +1575,7 @@ impl Network { } } gossipsub::Event::GossipsubNotSupported { peer_id } => { - debug!(self.log, "Peer does not support gossipsub"; "peer_id" => %peer_id); + debug!(%peer_id, "Peer does not support gossipsub"); self.peer_manager_mut().report_peer( &peer_id, PeerAction::Fatal, @@ -1351,10 +1588,17 @@ impl Network { peer_id, failed_messages, } => { - debug!(self.log, "Slow gossipsub peer"; "peer_id" => %peer_id, "publish" => failed_messages.publish, "forward" => failed_messages.forward, "priority" => failed_messages.priority, "non_priority" => failed_messages.non_priority); + debug!( + peer_id = %peer_id, + publish = failed_messages.publish, + forward = failed_messages.forward, + priority = failed_messages.priority, + non_priority = failed_messages.non_priority, + "Slow gossipsub peer" + ); // Punish the peer if it cannot handle priority messages - if failed_messages.total_timeout() > 10 { - debug!(self.log, "Slow gossipsub peer penalized for priority failure"; "peer_id" => %peer_id); + if failed_messages.timeout > 10 { + debug!(%peer_id, "Slow gossipsub peer penalized for priority failure"); self.peer_manager_mut().report_peer( &peer_id, PeerAction::HighToleranceError, @@ -1363,7 +1607,7 @@ impl Network { "publish_timeout_penalty", ); } else if failed_messages.total_queue_full() > 10 { - debug!(self.log, "Slow gossipsub peer penalized for send queue full"; "peer_id" => %peer_id); + debug!(%peer_id, "Slow gossipsub peer penalized for send queue full"); self.peer_manager_mut().report_peer( &peer_id, PeerAction::HighToleranceError, @@ -1378,6 +1622,12 @@ impl Network { } /// Handle an RPC event. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_rpc_event(&mut self, event: RPCMessage) -> Option> { let peer_id = event.peer_id; @@ -1387,11 +1637,7 @@ impl Network { && (matches!(event.message, Err(HandlerErr::Inbound { .. })) || matches!(event.message, Ok(RPCReceived::Request(..)))) { - debug!( - self.log, - "Ignoring rpc message of disconnecting peer"; - event - ); + debug!(?event, "Ignoring rpc message of disconnecting peer"); return None; } @@ -1454,10 +1700,10 @@ impl Network { RequestType::Goodbye(reason) => { // queue for disconnection without a goodbye message debug!( - self.log, "Peer sent Goodbye"; - "peer_id" => %peer_id, - "reason" => %reason, - "client" => %self.network_globals.client(&peer_id), + %peer_id, + %reason, + client = %self.network_globals.client(&peer_id), + "Peer sent Goodbye" ); // NOTE: We currently do not inform the application that we are // disconnecting here. The RPC handler will automatically @@ -1601,7 +1847,7 @@ impl Network { } RpcSuccessResponse::MetaData(meta_data) => { self.peer_manager_mut() - .meta_data_response(&peer_id, meta_data); + .meta_data_response(&peer_id, meta_data.as_ref().clone()); None } /* Network propagated protocols */ @@ -1668,6 +1914,12 @@ impl Network { } /// Handle an identify event. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_identify_event(&mut self, event: identify::Event) -> Option> { match event { identify::Event::Received { @@ -1676,10 +1928,7 @@ impl Network { connection_id: _, } => { if info.listen_addrs.len() > MAX_IDENTIFY_ADDRESSES { - debug!( - self.log, - "More than 10 addresses have been identified, truncating" - ); + debug!("More than 10 addresses have been identified, truncating"); info.listen_addrs.truncate(MAX_IDENTIFY_ADDRESSES); } // send peer info to the peer manager. @@ -1693,6 +1942,12 @@ impl Network { } /// Handle a peer manager event. + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_pm_event(&mut self, event: PeerManagerEvent) -> Option> { match event { PeerManagerEvent::PeerConnectedIncoming(peer_id) => { @@ -1737,8 +1992,7 @@ impl Network { None } PeerManagerEvent::DisconnectPeer(peer_id, reason) => { - debug!(self.log, "Peer Manager disconnecting peer"; - "peer_id" => %peer_id, "reason" => %reason); + debug!(%peer_id, %reason, "Peer Manager disconnecting peer"); // send one goodbye self.eth2_rpc_mut() .shutdown(peer_id, RequestId::Internal, reason); @@ -1747,10 +2001,16 @@ impl Network { } } + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn inject_upnp_event(&mut self, event: libp2p::upnp::Event) { match event { libp2p::upnp::Event::NewExternalAddr(addr) => { - info!(self.log, "UPnP route established"; "addr" => %addr); + info!(%addr, "UPnP route established"); let mut iter = addr.iter(); let is_ip6 = { let addr = iter.next(); @@ -1762,38 +2022,40 @@ impl Network { if let Err(e) = self.discovery_mut().update_enr_quic_port(udp_port, is_ip6) { - warn!(self.log, "Failed to update ENR"; "error" => e); + warn!(error = e, "Failed to update ENR"); } } _ => { - trace!(self.log, "UPnP address mapped multiaddr from unknown transport"; "addr" => %addr) + trace!(%addr, "UPnP address mapped multiaddr from unknown transport"); } }, Some(multiaddr::Protocol::Tcp(tcp_port)) => { if let Err(e) = self.discovery_mut().update_enr_tcp_port(tcp_port, is_ip6) { - warn!(self.log, "Failed to update ENR"; "error" => e); + warn!(error = e, "Failed to update ENR"); } } _ => { - trace!(self.log, "UPnP address mapped multiaddr from unknown transport"; "addr" => %addr); + trace!(%addr, "UPnP address mapped multiaddr from unknown transport"); } } } libp2p::upnp::Event::ExpiredExternalAddr(_) => {} libp2p::upnp::Event::GatewayNotFound => { - info!(self.log, "UPnP not available"); + info!("UPnP not available"); } libp2p::upnp::Event::NonRoutableGateway => { - info!( - self.log, - "UPnP is available but gateway is not exposed to public network" - ); + info!("UPnP is available but gateway is not exposed to public network"); } } } /* Networking polling */ - + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] pub async fn next_event(&mut self) -> NetworkEvent { loop { tokio::select! { @@ -1804,7 +2066,6 @@ impl Network { return event; } }, - // perform gossipsub score updates when necessary _ = self.update_gossipsub_scores.tick() => { let this = self.swarm.behaviour_mut(); @@ -1813,7 +2074,7 @@ impl Network { // poll the gossipsub cache to clear expired messages Some(result) = self.gossip_cache.next() => { match result { - Err(e) => warn!(self.log, "Gossip cache error"; "error" => e), + Err(e) => warn!(error = e, "Gossip cache error"), Ok(expired_topic) => { if let Some(v) = metrics::get_int_counter( &metrics::GOSSIP_EXPIRED_LATE_PUBLISH_PER_TOPIC_KIND, @@ -1828,6 +2089,12 @@ impl Network { } } + #[instrument(parent = None, + level = "trace", + fields(service = "libp2p"), + name = "libp2p", + skip_all + )] fn parse_swarm_event( &mut self, event: SwarmEvent>, @@ -1861,7 +2128,7 @@ impl Network { send_back_addr, connection_id: _, } => { - trace!(self.log, "Incoming connection"; "our_addr" => %local_addr, "from" => %send_back_addr); + trace!(our_addr = %local_addr, from = %send_back_addr, "Incoming connection"); None } SwarmEvent::IncomingConnectionError { @@ -1892,7 +2159,7 @@ impl Network { } }, }; - debug!(self.log, "Failed incoming connection"; "our_addr" => %local_addr, "from" => %send_back_addr, "error" => error_repr); + debug!(our_addr = %local_addr, from = %send_back_addr, error = error_repr, "Failed incoming connection"); None } SwarmEvent::OutgoingConnectionError { @@ -1907,7 +2174,7 @@ impl Network { } SwarmEvent::NewListenAddr { address, .. } => Some(NetworkEvent::NewListenAddr(address)), SwarmEvent::ExpiredListenAddr { address, .. } => { - debug!(self.log, "Listen address expired"; "address" => %address); + debug!(%address, "Listen address expired"); None } SwarmEvent::ListenerClosed { @@ -1915,10 +2182,10 @@ impl Network { } => { match reason { Ok(_) => { - debug!(self.log, "Listener gracefully closed"; "addresses" => ?addresses) + debug!(?addresses, "Listener gracefully closed") } Err(reason) => { - crit!(self.log, "Listener abruptly closed"; "addresses" => ?addresses, "reason" => ?reason) + crit!(?addresses, ?reason, "Listener abruptly closed") } }; if Swarm::listeners(&self.swarm).count() == 0 { @@ -1928,7 +2195,7 @@ impl Network { } } SwarmEvent::ListenerError { error, .. } => { - debug!(self.log, "Listener closed connection attempt"; "reason" => ?error); + debug!(reason = ?error, "Listener closed connection attempt"); None } _ => { diff --git a/beacon_node/lighthouse_network/src/service/utils.rs b/beacon_node/lighthouse_network/src/service/utils.rs index 72c2b29102..01929bcb01 100644 --- a/beacon_node/lighthouse_network/src/service/utils.rs +++ b/beacon_node/lighthouse_network/src/service/utils.rs @@ -9,7 +9,6 @@ use libp2p::core::{multiaddr::Multiaddr, muxing::StreamMuxerBox, transport::Boxe use libp2p::identity::{secp256k1, Keypair}; use libp2p::{core, noise, yamux, PeerId, Transport}; use prometheus_client::registry::Registry; -use slog::{debug, warn}; use ssz::Decode; use std::collections::HashSet; use std::fs::File; @@ -17,6 +16,7 @@ use std::io::prelude::*; use std::path::Path; use std::sync::Arc; use std::time::Duration; +use tracing::{debug, warn}; use types::{ ChainSpec, DataColumnSubnetId, EnrForkId, EthSpec, ForkContext, SubnetId, SyncSubnetId, }; @@ -107,21 +107,21 @@ fn keypair_from_bytes(mut bytes: Vec) -> Result { /// generated and is then saved to disk. /// /// Currently only secp256k1 keys are allowed, as these are the only keys supported by discv5. -pub fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair { +pub fn load_private_key(config: &NetworkConfig) -> Keypair { // check for key from disk let network_key_f = config.network_dir.join(NETWORK_KEY_FILENAME); if let Ok(mut network_key_file) = File::open(network_key_f.clone()) { let mut key_bytes: Vec = Vec::with_capacity(36); match network_key_file.read_to_end(&mut key_bytes) { - Err(_) => debug!(log, "Could not read network key file"), + Err(_) => debug!("Could not read network key file"), Ok(_) => { // only accept secp256k1 keys for now if let Ok(secret_key) = secp256k1::SecretKey::try_from_bytes(&mut key_bytes) { let kp: secp256k1::Keypair = secret_key.into(); - debug!(log, "Loaded network key from disk."); + debug!("Loaded network key from disk."); return kp.into(); } else { - debug!(log, "Network key file is not a valid secp256k1 key"); + debug!("Network key file is not a valid secp256k1 key"); } } } @@ -134,12 +134,12 @@ pub fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair { .and_then(|mut f| f.write_all(&local_private_key.secret().to_bytes())) { Ok(_) => { - debug!(log, "New network key generated and written to disk"); + debug!("New network key generated and written to disk"); } Err(e) => { warn!( - log, - "Could not write node key to file: {:?}. error: {}", network_key_f, e + "Could not write node key to file: {:?}. error: {}", + network_key_f, e ); } } @@ -166,7 +166,6 @@ pub fn strip_peer_id(addr: &mut Multiaddr) { pub fn load_or_build_metadata( network_dir: &Path, custody_group_count_opt: Option, - log: &slog::Logger, ) -> MetaData { // We load a V2 metadata version by default (regardless of current fork) // since a V2 metadata can be converted to V1. The RPC encoder is responsible @@ -192,7 +191,7 @@ pub fn load_or_build_metadata( { meta_data.seq_number += 1; } - debug!(log, "Loaded metadata from disk"); + debug!("Loaded metadata from disk"); } Err(_) => { match MetaDataV1::::from_ssz_bytes(&metadata_ssz) { @@ -200,13 +199,12 @@ pub fn load_or_build_metadata( let persisted_metadata = MetaData::V1(persisted_metadata); // Increment seq number as the persisted metadata version is updated meta_data.seq_number = *persisted_metadata.seq_number() + 1; - debug!(log, "Loaded metadata from disk"); + debug!("Loaded metadata from disk"); } Err(e) => { debug!( - log, - "Metadata from file could not be decoded"; - "error" => ?e, + error = ?e, + "Metadata from file could not be decoded" ); } } @@ -227,8 +225,8 @@ pub fn load_or_build_metadata( MetaData::V2(meta_data) }; - debug!(log, "Metadata sequence number"; "seq_num" => meta_data.seq_number()); - save_metadata_to_disk(network_dir, meta_data.clone(), log); + debug!(seq_num = meta_data.seq_number(), "Metadata sequence number"); + save_metadata_to_disk(network_dir, meta_data.clone()); meta_data } @@ -275,11 +273,7 @@ pub(crate) fn create_whitelist_filter( } /// Persist metadata to disk -pub(crate) fn save_metadata_to_disk( - dir: &Path, - metadata: MetaData, - log: &slog::Logger, -) { +pub(crate) fn save_metadata_to_disk(dir: &Path, metadata: MetaData) { let _ = std::fs::create_dir_all(dir); // We always store the metadata v2 to disk because // custody_group_count parameter doesn't need to be persisted across runs. @@ -288,14 +282,13 @@ pub(crate) fn save_metadata_to_disk( let metadata_bytes = metadata.metadata_v2().as_ssz_bytes(); match File::create(dir.join(METADATA_FILENAME)).and_then(|mut f| f.write_all(&metadata_bytes)) { Ok(_) => { - debug!(log, "Metadata written to disk"); + debug!("Metadata written to disk"); } Err(e) => { warn!( - log, - "Could not write metadata to disk"; - "file" => format!("{:?}{:?}", dir, METADATA_FILENAME), - "error" => %e + file = format!("{:?}{:?}", dir, METADATA_FILENAME), + error = %e, + "Could not write metadata to disk" ); } } diff --git a/beacon_node/lighthouse_network/src/types/globals.rs b/beacon_node/lighthouse_network/src/types/globals.rs index 2800b75133..4269a8973c 100644 --- a/beacon_node/lighthouse_network/src/types/globals.rs +++ b/beacon_node/lighthouse_network/src/types/globals.rs @@ -5,9 +5,9 @@ use crate::rpc::{MetaData, MetaDataV3}; use crate::types::{BackFillState, SyncState}; use crate::{Client, Enr, EnrExt, GossipTopic, Multiaddr, NetworkConfig, PeerId}; use parking_lot::RwLock; -use slog::error; use std::collections::HashSet; use std::sync::Arc; +use tracing::error; use types::data_column_custody_group::{ compute_columns_for_custody_group, compute_subnets_from_custody_group, get_custody_groups, }; @@ -45,7 +45,6 @@ impl NetworkGlobals { local_metadata: MetaData, trusted_peers: Vec, disable_peer_scoring: bool, - log: &slog::Logger, config: Arc, spec: Arc, ) -> Self { @@ -56,9 +55,8 @@ impl NetworkGlobals { Ok(&cgc) if cgc <= spec.number_of_custody_groups => cgc, _ => { error!( - log, - "custody_group_count from metadata is either invalid or not set. This is a bug!"; - "info" => "falling back to default custody requirement" + info = "falling back to default custody requirement", + "custody_group_count from metadata is either invalid or not set. This is a bug!" ); spec.custody_requirement } @@ -96,7 +94,7 @@ impl NetworkGlobals { peer_id: RwLock::new(enr.peer_id()), listen_multiaddrs: RwLock::new(Vec::new()), local_metadata: RwLock::new(local_metadata), - peers: RwLock::new(PeerDB::new(trusted_peers, disable_peer_scoring, log)), + peers: RwLock::new(PeerDB::new(trusted_peers, disable_peer_scoring)), gossipsub_subscriptions: RwLock::new(HashSet::new()), sync_state: RwLock::new(SyncState::Stalled), backfill_state: RwLock::new(BackFillState::Paused), @@ -187,6 +185,8 @@ impl NetworkGlobals { /// Returns the TopicConfig to compute the set of Gossip topics for a given fork pub fn as_topic_config(&self) -> TopicConfig { TopicConfig { + enable_light_client_server: self.config.enable_light_client_server, + subscribe_all_subnets: self.config.subscribe_all_subnets, subscribe_all_data_column_subnets: self.config.subscribe_all_data_column_subnets, sampling_subnets: &self.sampling_subnets, } @@ -195,7 +195,6 @@ impl NetworkGlobals { /// TESTING ONLY. Build a dummy NetworkGlobals instance. pub fn new_test_globals( trusted_peers: Vec, - log: &slog::Logger, config: Arc, spec: Arc, ) -> NetworkGlobals { @@ -205,13 +204,12 @@ impl NetworkGlobals { syncnets: Default::default(), custody_group_count: spec.custody_requirement, }); - Self::new_test_globals_with_metadata(trusted_peers, metadata, log, config, spec) + Self::new_test_globals_with_metadata(trusted_peers, metadata, config, spec) } pub(crate) fn new_test_globals_with_metadata( trusted_peers: Vec, metadata: MetaData, - log: &slog::Logger, config: Arc, spec: Arc, ) -> NetworkGlobals { @@ -219,18 +217,19 @@ impl NetworkGlobals { let keypair = libp2p::identity::secp256k1::Keypair::generate(); let enr_key: discv5::enr::CombinedKey = discv5::enr::CombinedKey::from_secp256k1(&keypair); let enr = discv5::enr::Enr::builder().build(&enr_key).unwrap(); - NetworkGlobals::new(enr, metadata, trusted_peers, false, log, config, spec) + NetworkGlobals::new(enr, metadata, trusted_peers, false, config, spec) } } #[cfg(test)] mod test { use super::*; + use logging::create_test_tracing_subscriber; use types::{Epoch, EthSpec, MainnetEthSpec as E}; #[test] fn test_sampling_subnets() { - let log = logging::test_logger(); + create_test_tracing_subscriber(); let mut spec = E::default_spec(); spec.fulu_fork_epoch = Some(Epoch::new(0)); @@ -242,7 +241,6 @@ mod test { let globals = NetworkGlobals::::new_test_globals_with_metadata( vec![], metadata, - &log, config, Arc::new(spec), ); @@ -254,7 +252,7 @@ mod test { #[test] fn test_sampling_columns() { - let log = logging::test_logger(); + create_test_tracing_subscriber(); let mut spec = E::default_spec(); spec.fulu_fork_epoch = Some(Epoch::new(0)); @@ -266,7 +264,6 @@ mod test { let globals = NetworkGlobals::::new_test_globals_with_metadata( vec![], metadata, - &log, config, Arc::new(spec), ); diff --git a/beacon_node/lighthouse_network/src/types/mod.rs b/beacon_node/lighthouse_network/src/types/mod.rs index 846cf8386d..db92f05b8f 100644 --- a/beacon_node/lighthouse_network/src/types/mod.rs +++ b/beacon_node/lighthouse_network/src/types/mod.rs @@ -1,6 +1,7 @@ mod globals; mod pubsub; mod subnet; +mod sync_state; mod topics; use types::{BitVector, EthSpec}; @@ -10,12 +11,11 @@ pub type EnrSyncCommitteeBitfield = BitVector<::SyncCommitteeSu pub type Enr = discv5::enr::Enr; -pub use eth2::lighthouse::sync_state::{BackFillState, SyncState}; pub use globals::NetworkGlobals; pub use pubsub::{PubsubMessage, SnappyTransform}; pub use subnet::{Subnet, SubnetDiscovery}; +pub use sync_state::{BackFillState, SyncState}; pub use topics::{ - attestation_sync_committee_topics, core_topics_to_subscribe, fork_core_topics, - subnet_from_topic_hash, GossipEncoding, GossipKind, GossipTopic, TopicConfig, - ALTAIR_CORE_TOPICS, BASE_CORE_TOPICS, CAPELLA_CORE_TOPICS, LIGHT_CLIENT_GOSSIP_TOPICS, + all_topics_at_fork, core_topics_to_subscribe, is_fork_non_core_topic, subnet_from_topic_hash, + GossipEncoding, GossipKind, GossipTopic, TopicConfig, }; diff --git a/common/eth2/src/lighthouse/sync_state.rs b/beacon_node/lighthouse_network/src/types/sync_state.rs similarity index 100% rename from common/eth2/src/lighthouse/sync_state.rs rename to beacon_node/lighthouse_network/src/types/sync_state.rs diff --git a/beacon_node/lighthouse_network/src/types/topics.rs b/beacon_node/lighthouse_network/src/types/topics.rs index 171dab09a3..56b97303d3 100644 --- a/beacon_node/lighthouse_network/src/types/topics.rs +++ b/beacon_node/lighthouse_network/src/types/topics.rs @@ -25,104 +25,110 @@ pub const BLS_TO_EXECUTION_CHANGE_TOPIC: &str = "bls_to_execution_change"; pub const LIGHT_CLIENT_FINALITY_UPDATE: &str = "light_client_finality_update"; pub const LIGHT_CLIENT_OPTIMISTIC_UPDATE: &str = "light_client_optimistic_update"; -pub const BASE_CORE_TOPICS: [GossipKind; 5] = [ - GossipKind::BeaconBlock, - GossipKind::BeaconAggregateAndProof, - GossipKind::VoluntaryExit, - GossipKind::ProposerSlashing, - GossipKind::AttesterSlashing, -]; - -pub const ALTAIR_CORE_TOPICS: [GossipKind; 1] = [GossipKind::SignedContributionAndProof]; - -pub const CAPELLA_CORE_TOPICS: [GossipKind; 1] = [GossipKind::BlsToExecutionChange]; - -pub const LIGHT_CLIENT_GOSSIP_TOPICS: [GossipKind; 2] = [ - GossipKind::LightClientFinalityUpdate, - GossipKind::LightClientOptimisticUpdate, -]; - #[derive(Debug)] pub struct TopicConfig<'a> { + pub enable_light_client_server: bool, + pub subscribe_all_subnets: bool, pub subscribe_all_data_column_subnets: bool, pub sampling_subnets: &'a HashSet, } -/// Returns the core topics associated with each fork that are new to the previous fork -pub fn fork_core_topics( - fork_name: &ForkName, - spec: &ChainSpec, - topic_config: &TopicConfig, -) -> Vec { - match fork_name { - ForkName::Base => BASE_CORE_TOPICS.to_vec(), - ForkName::Altair => ALTAIR_CORE_TOPICS.to_vec(), - ForkName::Bellatrix => vec![], - ForkName::Capella => CAPELLA_CORE_TOPICS.to_vec(), - ForkName::Deneb => { - // All of deneb blob topics are core topics - let mut deneb_blob_topics = Vec::new(); - for i in 0..spec.blob_sidecar_subnet_count(ForkName::Deneb) { - deneb_blob_topics.push(GossipKind::BlobSidecar(i)); - } - deneb_blob_topics - } - ForkName::Electra => { - // All of electra blob topics are core topics - let mut electra_blob_topics = Vec::new(); - for i in 0..spec.blob_sidecar_subnet_count(ForkName::Electra) { - electra_blob_topics.push(GossipKind::BlobSidecar(i)); - } - electra_blob_topics - } - ForkName::Fulu => { - let mut topics = vec![]; - if topic_config.subscribe_all_data_column_subnets { - for column_subnet in 0..spec.data_column_sidecar_subnet_count { - topics.push(GossipKind::DataColumnSidecar(DataColumnSubnetId::new( - column_subnet, - ))); - } - } else { - for column_subnet in topic_config.sampling_subnets { - topics.push(GossipKind::DataColumnSidecar(*column_subnet)); - } - } - topics - } - } -} - -/// Returns all the attestation and sync committee topics, for a given fork. -pub fn attestation_sync_committee_topics() -> impl Iterator { - (0..E::SubnetBitfieldLength::to_usize()) - .map(|subnet_id| GossipKind::Attestation(SubnetId::new(subnet_id as u64))) - .chain( - (0..E::SyncCommitteeSubnetCount::to_usize()).map(|sync_committee_id| { - GossipKind::SyncCommitteeMessage(SyncSubnetId::new(sync_committee_id as u64)) - }), - ) -} - -/// Returns all the topics that we need to subscribe to for a given fork -/// including topics from older forks and new topics for the current fork. +/// Returns all the topics the node should subscribe at `fork_name` pub fn core_topics_to_subscribe( - mut current_fork: ForkName, + fork_name: ForkName, + opts: &TopicConfig, spec: &ChainSpec, - topic_config: &TopicConfig, ) -> Vec { - let mut topics = fork_core_topics::(¤t_fork, spec, topic_config); - while let Some(previous_fork) = current_fork.previous_fork() { - let previous_fork_topics = fork_core_topics::(&previous_fork, spec, topic_config); - topics.extend(previous_fork_topics); - current_fork = previous_fork; + let mut topics = vec![ + GossipKind::BeaconBlock, + GossipKind::BeaconAggregateAndProof, + GossipKind::VoluntaryExit, + GossipKind::ProposerSlashing, + GossipKind::AttesterSlashing, + ]; + + if opts.subscribe_all_subnets { + for i in 0..spec.attestation_subnet_count { + topics.push(GossipKind::Attestation(i.into())); + } } - // Remove duplicates + + if fork_name.altair_enabled() { + topics.push(GossipKind::SignedContributionAndProof); + + if opts.subscribe_all_subnets { + for i in 0..E::SyncCommitteeSubnetCount::to_u64() { + topics.push(GossipKind::SyncCommitteeMessage(i.into())); + } + } + + if opts.enable_light_client_server { + topics.push(GossipKind::LightClientFinalityUpdate); + topics.push(GossipKind::LightClientOptimisticUpdate); + } + } + + if fork_name.capella_enabled() { + topics.push(GossipKind::BlsToExecutionChange); + } + + if fork_name.deneb_enabled() && !fork_name.fulu_enabled() { + // All of deneb blob topics are core topics + for i in 0..spec.blob_sidecar_subnet_count(fork_name) { + topics.push(GossipKind::BlobSidecar(i)); + } + } + + if fork_name.fulu_enabled() { + if opts.subscribe_all_data_column_subnets { + for i in 0..spec.data_column_sidecar_subnet_count { + topics.push(GossipKind::DataColumnSidecar(i.into())); + } + } else { + for subnet in opts.sampling_subnets { + topics.push(GossipKind::DataColumnSidecar(*subnet)); + } + } + } + topics - .into_iter() - .collect::>() - .into_iter() - .collect() +} + +/// Returns true if a given non-core `GossipTopic` MAY be subscribe at this fork. +/// +/// For example: the `Attestation` topic is not subscribed as a core topic if +/// subscribe_all_subnets = false` but we may subscribe to it outside of a fork +/// boundary if the node is an aggregator. +pub fn is_fork_non_core_topic(topic: &GossipTopic, _fork_name: ForkName) -> bool { + match topic.kind() { + // Node may be aggregator of attestation and sync_committee_message topics for all known + // forks + GossipKind::Attestation(_) | GossipKind::SyncCommitteeMessage(_) => true, + // All these topics are core-only + GossipKind::BeaconBlock + | GossipKind::BeaconAggregateAndProof + | GossipKind::BlobSidecar(_) + | GossipKind::DataColumnSidecar(_) + | GossipKind::VoluntaryExit + | GossipKind::ProposerSlashing + | GossipKind::AttesterSlashing + | GossipKind::SignedContributionAndProof + | GossipKind::BlsToExecutionChange + | GossipKind::LightClientFinalityUpdate + | GossipKind::LightClientOptimisticUpdate => false, + } +} + +pub fn all_topics_at_fork(fork: ForkName, spec: &ChainSpec) -> Vec { + // Compute the worst case of all forks + let sampling_subnets = HashSet::from_iter(spec.all_data_column_sidecar_subnets()); + let opts = TopicConfig { + enable_light_client_server: true, + subscribe_all_subnets: true, + subscribe_all_data_column_subnets: true, + sampling_subnets: &sampling_subnets, + }; + core_topics_to_subscribe::(fork, &opts, spec) } /// A gossipsub topic which encapsulates the type of messages that should be sent and received over @@ -368,10 +374,9 @@ fn subnet_topic_index(topic: &str) -> Option { #[cfg(test)] mod tests { - use types::MainnetEthSpec; - use super::GossipKind::*; use super::*; + use types::{Epoch, MainnetEthSpec as E}; const GOOD_FORK_DIGEST: &str = "e1925f3b"; const BAD_PREFIX: &str = "tezos"; @@ -496,31 +501,94 @@ mod tests { assert_eq!("attester_slashing", AttesterSlashing.as_ref()); } + fn get_spec() -> ChainSpec { + let mut spec = E::default_spec(); + spec.altair_fork_epoch = Some(Epoch::new(1)); + spec.bellatrix_fork_epoch = Some(Epoch::new(2)); + spec.capella_fork_epoch = Some(Epoch::new(3)); + spec.deneb_fork_epoch = Some(Epoch::new(4)); + spec.electra_fork_epoch = Some(Epoch::new(5)); + spec.fulu_fork_epoch = Some(Epoch::new(6)); + spec + } + + fn get_sampling_subnets() -> HashSet { + HashSet::new() + } + + fn get_topic_config(sampling_subnets: &HashSet) -> TopicConfig { + TopicConfig { + enable_light_client_server: false, + subscribe_all_subnets: false, + subscribe_all_data_column_subnets: false, + sampling_subnets, + } + } + + #[test] + fn base_topics_are_always_active() { + let spec = get_spec(); + let s = get_sampling_subnets(); + let topic_config = get_topic_config(&s); + for fork in ForkName::list_all() { + assert!(core_topics_to_subscribe::(fork, &topic_config, &spec,) + .contains(&GossipKind::BeaconBlock)); + } + } + + #[test] + fn blobs_are_not_subscribed_in_peerdas() { + let spec = get_spec(); + let s = get_sampling_subnets(); + let topic_config = get_topic_config(&s); + assert!( + !core_topics_to_subscribe::(ForkName::Fulu, &topic_config, &spec,) + .contains(&GossipKind::BlobSidecar(0)) + ); + } + + #[test] + fn columns_are_subscribed_in_peerdas() { + let spec = get_spec(); + let s = get_sampling_subnets(); + let mut topic_config = get_topic_config(&s); + topic_config.subscribe_all_data_column_subnets = true; + assert!( + core_topics_to_subscribe::(ForkName::Fulu, &topic_config, &spec) + .contains(&GossipKind::DataColumnSidecar(0.into())) + ); + } + #[test] fn test_core_topics_to_subscribe() { - type E = MainnetEthSpec; - let spec = E::default_spec(); - let mut all_topics = Vec::new(); - let topic_config = TopicConfig { - subscribe_all_data_column_subnets: false, - sampling_subnets: &HashSet::from_iter([1, 2].map(DataColumnSubnetId::new)), - }; - let mut fulu_core_topics = fork_core_topics::(&ForkName::Fulu, &spec, &topic_config); - let mut electra_core_topics = - fork_core_topics::(&ForkName::Electra, &spec, &topic_config); - let mut deneb_core_topics = fork_core_topics::(&ForkName::Deneb, &spec, &topic_config); - all_topics.append(&mut fulu_core_topics); - all_topics.append(&mut electra_core_topics); - all_topics.append(&mut deneb_core_topics); - all_topics.extend(CAPELLA_CORE_TOPICS); - all_topics.extend(ALTAIR_CORE_TOPICS); - all_topics.extend(BASE_CORE_TOPICS); - + let spec = get_spec(); + let s = HashSet::from_iter([1, 2].map(DataColumnSubnetId::new)); + let mut topic_config = get_topic_config(&s); + topic_config.enable_light_client_server = true; let latest_fork = *ForkName::list_all().last().unwrap(); - let core_topics = core_topics_to_subscribe::(latest_fork, &spec, &topic_config); + let topics = core_topics_to_subscribe::(latest_fork, &topic_config, &spec); + + let mut expected_topics = vec![ + GossipKind::BeaconBlock, + GossipKind::BeaconAggregateAndProof, + GossipKind::VoluntaryExit, + GossipKind::ProposerSlashing, + GossipKind::AttesterSlashing, + GossipKind::SignedContributionAndProof, + GossipKind::LightClientFinalityUpdate, + GossipKind::LightClientOptimisticUpdate, + GossipKind::BlsToExecutionChange, + ]; + for subnet in s { + expected_topics.push(GossipKind::DataColumnSidecar(subnet)); + } // Need to check all the topics exist in an order independent manner - for topic in all_topics { - assert!(core_topics.contains(&topic)); + for expected_topic in expected_topics { + assert!( + topics.contains(&expected_topic), + "Should contain {:?}", + expected_topic + ); } } } diff --git a/beacon_node/lighthouse_network/tests/common.rs b/beacon_node/lighthouse_network/tests/common.rs index 6a3ec6dd32..d686885ff7 100644 --- a/beacon_node/lighthouse_network/tests/common.rs +++ b/beacon_node/lighthouse_network/tests/common.rs @@ -4,10 +4,11 @@ use lighthouse_network::Enr; use lighthouse_network::EnrExt; use lighthouse_network::Multiaddr; use lighthouse_network::{NetworkConfig, NetworkEvent}; -use slog::{debug, error, o, Drain}; use std::sync::Arc; use std::sync::Weak; use tokio::runtime::Runtime; +use tracing::{debug, error, info_span, Instrument}; +use tracing_subscriber::EnvFilter; use types::{ ChainSpec, EnrForkId, Epoch, EthSpec, FixedBytesExtended, ForkContext, ForkName, Hash256, MinimalEthSpec, Slot, @@ -67,15 +68,12 @@ impl std::ops::DerefMut for Libp2pInstance { } #[allow(unused)] -pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - +pub fn build_tracing_subscriber(level: &str, enabled: bool) { if enabled { - slog::Logger::root(drain.filter_level(level).fuse(), o!()) - } else { - slog::Logger::root(drain.filter(|_| false).fuse(), o!()) + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::try_new(level).unwrap()) + .try_init() + .unwrap(); } } @@ -101,16 +99,16 @@ pub fn build_config(mut boot_nodes: Vec) -> Arc { pub async fn build_libp2p_instance( rt: Weak, boot_nodes: Vec, - log: slog::Logger, fork_name: ForkName, chain_spec: Arc, + service_name: String, ) -> Libp2pInstance { let config = build_config(boot_nodes); // launch libp2p service let (signal, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let executor = task_executor::TaskExecutor::new(rt, exit, log.clone(), shutdown_tx); + let executor = task_executor::TaskExecutor::new(rt, exit, shutdown_tx, service_name); let libp2p_context = lighthouse_network::Context { config, enr_fork_id: EnrForkId::default(), @@ -119,7 +117,7 @@ pub async fn build_libp2p_instance( libp2p_registry: None, }; Libp2pInstance( - LibP2PService::new(executor, libp2p_context, &log) + LibP2PService::new(executor, libp2p_context) .await .expect("should build libp2p instance") .0, @@ -143,18 +141,20 @@ pub enum Protocol { #[allow(dead_code)] pub async fn build_node_pair( rt: Weak, - log: &slog::Logger, fork_name: ForkName, spec: Arc, protocol: Protocol, ) -> (Libp2pInstance, Libp2pInstance) { - let sender_log = log.new(o!("who" => "sender")); - let receiver_log = log.new(o!("who" => "receiver")); - - let mut sender = - build_libp2p_instance(rt.clone(), vec![], sender_log, fork_name, spec.clone()).await; + let mut sender = build_libp2p_instance( + rt.clone(), + vec![], + fork_name, + spec.clone(), + "sender".to_string(), + ) + .await; let mut receiver = - build_libp2p_instance(rt, vec![], receiver_log, fork_name, spec.clone()).await; + build_libp2p_instance(rt, vec![], fork_name, spec.clone(), "receiver".to_string()).await; // let the two nodes set up listeners let sender_fut = async { @@ -179,7 +179,8 @@ pub async fn build_node_pair( } } } - }; + } + .instrument(info_span!("Sender", who = "sender")); let receiver_fut = async { loop { if let NetworkEvent::NewListenAddr(addr) = receiver.next_event().await { @@ -201,7 +202,8 @@ pub async fn build_node_pair( } } } - }; + } + .instrument(info_span!("Receiver", who = "receiver")); let joined = futures::future::join(sender_fut, receiver_fut); @@ -209,9 +211,9 @@ pub async fn build_node_pair( match sender.testing_dial(receiver_multiaddr.clone()) { Ok(()) => { - debug!(log, "Sender dialed receiver"; "address" => format!("{:?}", receiver_multiaddr)) + debug!(address = ?receiver_multiaddr, "Sender dialed receiver") } - Err(_) => error!(log, "Dialing failed"), + Err(_) => error!("Dialing failed"), }; (sender, receiver) } @@ -220,7 +222,6 @@ pub async fn build_node_pair( #[allow(dead_code)] pub async fn build_linear( rt: Weak, - log: slog::Logger, n: usize, fork_name: ForkName, spec: Arc, @@ -228,7 +229,14 @@ pub async fn build_linear( let mut nodes = Vec::with_capacity(n); for _ in 0..n { nodes.push( - build_libp2p_instance(rt.clone(), vec![], log.clone(), fork_name, spec.clone()).await, + build_libp2p_instance( + rt.clone(), + vec![], + fork_name, + spec.clone(), + "linear".to_string(), + ) + .await, ); } @@ -238,8 +246,8 @@ pub async fn build_linear( .collect(); for i in 0..n - 1 { match nodes[i].testing_dial(multiaddrs[i + 1].clone()) { - Ok(()) => debug!(log, "Connected"), - Err(_) => error!(log, "Failed to connect"), + Ok(()) => debug!("Connected"), + Err(_) => error!("Failed to connect"), }; } nodes diff --git a/beacon_node/lighthouse_network/tests/rpc_tests.rs b/beacon_node/lighthouse_network/tests/rpc_tests.rs index 4b54a24ddc..d736fefa5f 100644 --- a/beacon_node/lighthouse_network/tests/rpc_tests.rs +++ b/beacon_node/lighthouse_network/tests/rpc_tests.rs @@ -2,17 +2,17 @@ mod common; -use common::Protocol; +use common::{build_tracing_subscriber, Protocol}; use lighthouse_network::rpc::{methods::*, RequestType}; use lighthouse_network::service::api_types::AppRequestId; use lighthouse_network::{rpc::max_rpc_size, NetworkEvent, ReportSource, Response}; -use slog::{debug, warn, Level}; use ssz::Encode; use ssz_types::VariableList; use std::sync::Arc; use std::time::Duration; use tokio::runtime::Runtime; use tokio::time::sleep; +use tracing::{debug, warn}; use types::{ BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockBellatrix, BlobSidecar, ChainSpec, EmptyBlock, Epoch, EthSpec, FixedBytesExtended, ForkContext, ForkName, Hash256, MinimalEthSpec, @@ -53,26 +53,19 @@ fn bellatrix_block_large(fork_context: &ForkContext, spec: &ChainSpec) -> Beacon #[test] #[allow(clippy::single_match)] fn test_tcp_status_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let rt = Arc::new(Runtime::new().unwrap()); - let log = common::build_log(log_level, enable_logging); - let spec = Arc::new(E::default_spec()); rt.block_on(async { // get sender/receiver - let (mut sender, mut receiver) = common::build_node_pair( - Arc::downgrade(&rt), - &log, - ForkName::Base, - spec, - Protocol::Tcp, - ) - .await; + let (mut sender, mut receiver) = + common::build_node_pair(Arc::downgrade(&rt), ForkName::Base, spec, Protocol::Tcp).await; // Dummy STATUS RPC message let rpc_request = RequestType::Status(StatusMessage { @@ -98,7 +91,7 @@ fn test_tcp_status_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -109,9 +102,9 @@ fn test_tcp_status_rpc() { response, } => { // Should receive the RPC response - debug!(log, "Sender Received"); + debug!("Sender Received"); assert_eq!(response, rpc_response.clone()); - debug!(log, "Sender Completed"); + debug!("Sender Completed"); return; } _ => {} @@ -130,7 +123,7 @@ fn test_tcp_status_rpc() { } => { if request.r#type == rpc_request { // send the response - debug!(log, "Receiver Received"); + debug!("Receiver Received"); receiver.send_response(peer_id, id, request.id, rpc_response.clone()); } } @@ -153,14 +146,13 @@ fn test_tcp_status_rpc() { #[test] #[allow(clippy::single_match)] fn test_tcp_blocks_by_range_chunked_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send = 6; - let log = common::build_log(log_level, enable_logging); - let rt = Arc::new(Runtime::new().unwrap()); let spec = Arc::new(E::default_spec()); @@ -169,7 +161,6 @@ fn test_tcp_blocks_by_range_chunked_rpc() { // get sender/receiver let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Bellatrix, spec.clone(), Protocol::Tcp, @@ -206,7 +197,7 @@ fn test_tcp_blocks_by_range_chunked_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -216,7 +207,7 @@ fn test_tcp_blocks_by_range_chunked_rpc() { id: _, response, } => { - warn!(log, "Sender received a response"); + warn!("Sender received a response"); match response { Response::BlocksByRange(Some(_)) => { if messages_received < 2 { @@ -227,7 +218,7 @@ fn test_tcp_blocks_by_range_chunked_rpc() { assert_eq!(response, rpc_response_bellatrix_small.clone()); } messages_received += 1; - warn!(log, "Chunk received"); + warn!("Chunk received"); } Response::BlocksByRange(None) => { // should be exactly `messages_to_send` messages before terminating @@ -254,7 +245,7 @@ fn test_tcp_blocks_by_range_chunked_rpc() { } => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); for i in 0..messages_to_send { // Send first third of responses as base blocks, // second as altair and third as bellatrix. @@ -300,15 +291,14 @@ fn test_tcp_blocks_by_range_chunked_rpc() { #[test] #[allow(clippy::single_match)] fn test_blobs_by_range_chunked_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let slot_count = 32; let messages_to_send = 34; - let log = common::build_log(log_level, enable_logging); - let rt = Arc::new(Runtime::new().unwrap()); rt.block_on(async { @@ -316,7 +306,6 @@ fn test_blobs_by_range_chunked_rpc() { let spec = Arc::new(E::default_spec()); let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Deneb, spec.clone(), Protocol::Tcp, @@ -342,7 +331,7 @@ fn test_blobs_by_range_chunked_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -352,12 +341,12 @@ fn test_blobs_by_range_chunked_rpc() { id: _, response, } => { - warn!(log, "Sender received a response"); + warn!("Sender received a response"); match response { Response::BlobsByRange(Some(_)) => { assert_eq!(response, rpc_response.clone()); messages_received += 1; - warn!(log, "Chunk received"); + warn!("Chunk received"); } Response::BlobsByRange(None) => { // should be exactly `messages_to_send` messages before terminating @@ -384,7 +373,7 @@ fn test_blobs_by_range_chunked_rpc() { } => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); for _ in 0..messages_to_send { // Send first third of responses as base blocks, // second as altair and third as bellatrix. @@ -423,14 +412,13 @@ fn test_blobs_by_range_chunked_rpc() { #[test] #[allow(clippy::single_match)] fn test_tcp_blocks_by_range_over_limit() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send = 5; - let log = common::build_log(log_level, enable_logging); - let rt = Arc::new(Runtime::new().unwrap()); let spec = Arc::new(E::default_spec()); @@ -439,7 +427,6 @@ fn test_tcp_blocks_by_range_over_limit() { // get sender/receiver let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Bellatrix, spec.clone(), Protocol::Tcp, @@ -466,7 +453,7 @@ fn test_tcp_blocks_by_range_over_limit() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -492,7 +479,7 @@ fn test_tcp_blocks_by_range_over_limit() { } => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); for _ in 0..messages_to_send { let rpc_response = rpc_response_bellatrix_large.clone(); receiver.send_response( @@ -529,15 +516,14 @@ fn test_tcp_blocks_by_range_over_limit() { // Tests that a streamed BlocksByRange RPC Message terminates when all expected chunks were received #[test] fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send = 10; let extra_messages_to_send = 10; - let log = common::build_log(log_level, enable_logging); - let rt = Arc::new(Runtime::new().unwrap()); let spec = Arc::new(E::default_spec()); @@ -546,7 +532,6 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { // get sender/receiver let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Base, spec.clone(), Protocol::Tcp, @@ -574,7 +559,7 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -586,7 +571,7 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { } => // Should receive the RPC response { - debug!(log, "Sender received a response"); + debug!("Sender received a response"); match response { Response::BlocksByRange(Some(_)) => { assert_eq!(response, rpc_response.clone()); @@ -630,7 +615,7 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { )) => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); message_info = Some((peer_id, id, request.id)); } } @@ -643,7 +628,7 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { messages_sent += 1; let (peer_id, stream_id, request_id) = message_info.as_ref().unwrap(); receiver.send_response(*peer_id, *stream_id, *request_id, rpc_response.clone()); - debug!(log, "Sending message {}", messages_sent); + debug!("Sending message {}", messages_sent); if messages_sent == messages_to_send + extra_messages_to_send { // stop sending messages return; @@ -666,11 +651,11 @@ fn test_tcp_blocks_by_range_chunked_rpc_terminates_correctly() { #[test] #[allow(clippy::single_match)] fn test_tcp_blocks_by_range_single_empty_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Trace; + // Set up the logging. + let log_level = "trace"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); - let log = common::build_log(log_level, enable_logging); let rt = Arc::new(Runtime::new().unwrap()); let spec = Arc::new(E::default_spec()); @@ -679,7 +664,6 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { // get sender/receiver let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Base, spec.clone(), Protocol::Tcp, @@ -709,7 +693,7 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -722,7 +706,7 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { Response::BlocksByRange(Some(_)) => { assert_eq!(response, rpc_response.clone()); messages_received += 1; - warn!(log, "Chunk received"); + warn!("Chunk received"); } Response::BlocksByRange(None) => { // should be exactly 10 messages before terminating @@ -748,7 +732,7 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { } => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); for _ in 1..=messages_to_send { receiver.send_response( @@ -788,13 +772,13 @@ fn test_tcp_blocks_by_range_single_empty_rpc() { #[test] #[allow(clippy::single_match)] fn test_tcp_blocks_by_root_chunked_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send = 6; - let log = common::build_log(log_level, enable_logging); let spec = Arc::new(E::default_spec()); let rt = Arc::new(Runtime::new().unwrap()); @@ -802,7 +786,6 @@ fn test_tcp_blocks_by_root_chunked_rpc() { rt.block_on(async { let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Bellatrix, spec.clone(), Protocol::Tcp, @@ -847,7 +830,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -866,7 +849,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { assert_eq!(response, rpc_response_bellatrix_small.clone()); } messages_received += 1; - debug!(log, "Chunk received"); + debug!("Chunk received"); } Response::BlocksByRoot(None) => { // should be exactly messages_to_send @@ -892,7 +875,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { } => { if request.r#type == rpc_request { // send the response - debug!(log, "Receiver got request"); + debug!("Receiver got request"); for i in 0..messages_to_send { // Send equal base, altair and bellatrix blocks @@ -904,7 +887,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { rpc_response_bellatrix_small.clone() }; receiver.send_response(peer_id, id, request.id, rpc_response); - debug!(log, "Sending message"); + debug!("Sending message"); } // send the stream termination receiver.send_response( @@ -913,7 +896,7 @@ fn test_tcp_blocks_by_root_chunked_rpc() { request.id, Response::BlocksByRange(None), ); - debug!(log, "Send stream term"); + debug!("Send stream term"); } } _ => {} // Ignore other events @@ -933,14 +916,14 @@ fn test_tcp_blocks_by_root_chunked_rpc() { // Tests a streamed, chunked BlocksByRoot RPC Message terminates when all expected reponses have been received #[test] fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; + // Set up the logging. + let log_level = "debug"; let enable_logging = false; + build_tracing_subscriber(log_level, enable_logging); let messages_to_send: u64 = 10; let extra_messages_to_send: u64 = 10; - let log = common::build_log(log_level, enable_logging); let spec = Arc::new(E::default_spec()); let rt = Arc::new(Runtime::new().unwrap()); @@ -948,7 +931,6 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { rt.block_on(async { let (mut sender, mut receiver) = common::build_node_pair( Arc::downgrade(&rt), - &log, ForkName::Base, spec.clone(), Protocol::Tcp, @@ -988,7 +970,7 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a STATUS message - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender .send_request(peer_id, AppRequestId::Router, rpc_request.clone()) .unwrap(); @@ -998,12 +980,12 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { id: AppRequestId::Router, response, } => { - debug!(log, "Sender received a response"); + debug!("Sender received a response"); match response { Response::BlocksByRoot(Some(_)) => { assert_eq!(response, rpc_response.clone()); messages_received += 1; - debug!(log, "Chunk received"); + debug!("Chunk received"); } Response::BlocksByRoot(None) => { // should be exactly messages_to_send @@ -1044,7 +1026,7 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { )) => { if request.r#type == rpc_request { // send the response - warn!(log, "Receiver got request"); + warn!("Receiver got request"); message_info = Some((peer_id, id, request.id)); } } @@ -1057,7 +1039,7 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { messages_sent += 1; let (peer_id, stream_id, request_id) = message_info.as_ref().unwrap(); receiver.send_response(*peer_id, *stream_id, *request_id, rpc_response.clone()); - debug!(log, "Sending message {}", messages_sent); + debug!("Sending message {}", messages_sent); if messages_sent == messages_to_send + extra_messages_to_send { // stop sending messages return; @@ -1078,8 +1060,9 @@ fn test_tcp_blocks_by_root_chunked_rpc_terminates_correctly() { /// Establishes a pair of nodes and disconnects the pair based on the selected protocol via an RPC /// Goodbye message. -fn goodbye_test(log_level: Level, enable_logging: bool, protocol: Protocol) { - let log = common::build_log(log_level, enable_logging); +fn goodbye_test(log_level: &str, enable_logging: bool, protocol: Protocol) { + // Set up the logging. + build_tracing_subscriber(log_level, enable_logging); let rt = Arc::new(Runtime::new().unwrap()); @@ -1088,8 +1071,7 @@ fn goodbye_test(log_level: Level, enable_logging: bool, protocol: Protocol) { // get sender/receiver rt.block_on(async { let (mut sender, mut receiver) = - common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base, spec, protocol) - .await; + common::build_node_pair(Arc::downgrade(&rt), ForkName::Base, spec, protocol).await; // build the sender future let sender_future = async { @@ -1097,7 +1079,7 @@ fn goodbye_test(log_level: Level, enable_logging: bool, protocol: Protocol) { match sender.next_event().await { NetworkEvent::PeerConnectedOutgoing(peer_id) => { // Send a goodbye and disconnect - debug!(log, "Sending RPC"); + debug!("Sending RPC"); sender.goodbye_peer( &peer_id, GoodbyeReason::IrrelevantNetwork, @@ -1137,18 +1119,16 @@ fn goodbye_test(log_level: Level, enable_logging: bool, protocol: Protocol) { #[test] #[allow(clippy::single_match)] fn tcp_test_goodbye_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; - let enable_logging = false; - goodbye_test(log_level, enable_logging, Protocol::Tcp); + let log_level = "debug"; + let enabled_logging = false; + goodbye_test(log_level, enabled_logging, Protocol::Tcp); } // Tests a Goodbye RPC message #[test] #[allow(clippy::single_match)] fn quic_test_goodbye_rpc() { - // set up the logging. The level and enabled logging or not - let log_level = Level::Debug; - let enable_logging = false; - goodbye_test(log_level, enable_logging, Protocol::Quic); + let log_level = "debug"; + let enabled_logging = false; + goodbye_test(log_level, enabled_logging, Protocol::Quic); } diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index 09179c4a51..4250f8f8bb 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -9,13 +9,12 @@ bls = { workspace = true } eth2 = { workspace = true } eth2_network_config = { workspace = true } genesis = { workspace = true } -gossipsub = { workspace = true } +gossipsub = { package = "libp2p-gossipsub", git = "https://github.com/sigp/rust-libp2p.git", branch = "sigp-gossipsub" } +k256 = "0.13.4" kzg = { workspace = true } matches = "0.1.8" +rand_chacha = "0.3.1" serde_json = { workspace = true } -slog-async = { workspace = true } -slog-term = { workspace = true } -sloggers = { workspace = true } [dependencies] alloy-primitives = { workspace = true } @@ -40,7 +39,6 @@ metrics = { workspace = true } operation_pool = { workspace = true } parking_lot = { workspace = true } rand = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } smallvec = { workspace = true } ssz_types = { workspace = true } @@ -49,6 +47,8 @@ strum = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } [features] diff --git a/beacon_node/network/src/metrics.rs b/beacon_node/network/src/metrics.rs index 154a59eade..7c38ae9d75 100644 --- a/beacon_node/network/src/metrics.rs +++ b/beacon_node/network/src/metrics.rs @@ -14,6 +14,7 @@ pub use metrics::*; use std::sync::{Arc, LazyLock}; use strum::AsRefStr; use strum::IntoEnumIterator; +use types::DataColumnSubnetId; use types::EthSpec; pub const SUCCESS: &str = "SUCCESS"; @@ -374,11 +375,18 @@ pub static PEERS_PER_SYNC_TYPE: LazyLock> = LazyLock::new(|| }); pub static PEERS_PER_COLUMN_SUBNET: LazyLock> = LazyLock::new(|| { try_create_int_gauge_vec( - "peers_per_column_subnet", + "sync_peers_per_column_subnet", "Number of connected peers per column subnet", &["subnet_id"], ) }); +pub static PEERS_PER_CUSTODY_COLUMN_SUBNET: LazyLock> = LazyLock::new(|| { + try_create_int_gauge_vec( + "sync_peers_per_custody_column_subnet", + "Number of connected peers per custody column subnet", + &["subnet_id"], + ) +}); pub static SYNCING_CHAINS_COUNT: LazyLock> = LazyLock::new(|| { try_create_int_gauge_vec( "sync_range_chains", @@ -746,16 +754,42 @@ pub fn update_sync_metrics(network_globals: &Arc>) // count per sync status, the number of connected peers let mut peers_per_sync_type = FnvHashMap::default(); - for sync_type in network_globals - .peers - .read() - .connected_peers() - .map(|(_peer_id, info)| info.sync_status().as_str()) - { + let mut peers_per_column_subnet = FnvHashMap::default(); + + for (_, info) in network_globals.peers.read().connected_peers() { + let sync_type = info.sync_status().as_str(); *peers_per_sync_type.entry(sync_type).or_default() += 1; + + for subnet in info.custody_subnets_iter() { + *peers_per_column_subnet.entry(*subnet).or_default() += 1; + } } for (sync_type, peer_count) in peers_per_sync_type { set_gauge_entry(&PEERS_PER_SYNC_TYPE, &[sync_type], peer_count); } + + let all_column_subnets = + (0..network_globals.spec.data_column_sidecar_subnet_count).map(DataColumnSubnetId::new); + let custody_column_subnets = network_globals.sampling_subnets.iter(); + + // Iterate all subnet values to set to zero the empty entries in peers_per_column_subnet + for subnet in all_column_subnets { + set_gauge_entry( + &PEERS_PER_COLUMN_SUBNET, + &[&format!("{subnet}")], + peers_per_column_subnet.get(&subnet).copied().unwrap_or(0), + ); + } + + // Registering this metric is a duplicate for supernodes but helpful for fullnodes. This way + // operators can monitor the health of only the subnets of their interest without complex + // Grafana queries. + for subnet in custody_column_subnets { + set_gauge_entry( + &PEERS_PER_CUSTODY_COLUMN_SUBNET, + &[&format!("{subnet}")], + peers_per_column_subnet.get(subnet).copied().unwrap_or(0), + ); + } } diff --git a/beacon_node/network/src/nat.rs b/beacon_node/network/src/nat.rs index e63ff55039..ce9d241d43 100644 --- a/beacon_node/network/src/nat.rs +++ b/beacon_node/network/src/nat.rs @@ -5,10 +5,10 @@ use anyhow::{bail, Context, Error}; use igd_next::{aio::tokio as igd, PortMappingProtocol}; -use slog::debug; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::Duration; use tokio::time::sleep; +use tracing::debug; /// The duration in seconds of a port mapping on the gateway. const MAPPING_DURATION: u32 = 3600; @@ -17,11 +17,7 @@ const MAPPING_DURATION: u32 = 3600; const MAPPING_TIMEOUT: u64 = MAPPING_DURATION as u64 / 2; /// Attempts to map Discovery external port mappings with UPnP. -pub async fn construct_upnp_mappings( - addr: Ipv4Addr, - port: u16, - log: slog::Logger, -) -> Result<(), Error> { +pub async fn construct_upnp_mappings(addr: Ipv4Addr, port: u16) -> Result<(), Error> { let gateway = igd::search_gateway(Default::default()) .await .context("Gateway does not support UPnP")?; @@ -54,7 +50,7 @@ pub async fn construct_upnp_mappings( ) .await .with_context(|| format!("Could not UPnP map port: {} on the gateway", port))?; - debug!(log, "Discovery UPnP port mapped"; "port" => %port); + debug!(%port,"Discovery UPnP port mapped"); sleep(Duration::from_secs(MAPPING_TIMEOUT)).await; } } diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 090b963cbc..afab2d178c 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -21,8 +21,8 @@ use beacon_chain::{ GossipVerifiedBlock, NotifyExecutionLayer, }; use lighthouse_network::{Client, MessageAcceptance, MessageId, PeerAction, PeerId, ReportSource}; +use logging::crit; use operation_pool::ReceivedPreCapella; -use slog::{crit, debug, error, info, trace, warn, Logger}; use slot_clock::SlotClock; use ssz::Encode; use std::fs; @@ -32,6 +32,7 @@ use std::sync::Arc; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use store::hot_cold_store::HotColdDBError; use tokio::sync::mpsc; +use tracing::{debug, error, info, trace, warn}; use types::{ beacon_block::BlockImportSource, Attestation, AttestationData, AttestationRef, AttesterSlashing, BlobSidecar, DataColumnSidecar, DataColumnSubnetId, EthSpec, Hash256, @@ -251,9 +252,8 @@ impl NetworkBeaconProcessor { Ok(results) => results, Err(e) => { error!( - self.log, - "Batch unagg. attn verification failed"; - "error" => ?e + error = ?e, + "Batch unagg. attn verification failed" ); return; } @@ -264,10 +264,9 @@ impl NetworkBeaconProcessor { // The log is `crit` since in this scenario we might be penalizing/rewarding the wrong // peer. crit!( - self.log, - "Batch attestation result mismatch"; - "results" => results.len(), - "packages" => packages.len(), + results = results.len(), + packages = packages.len(), + "Batch attestation result mismatch" ) } @@ -355,19 +354,17 @@ impl NetworkBeaconProcessor { e, )) => { debug!( - self.log, - "Attestation invalid for fork choice"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Attestation invalid for fork choice" ) } e => error!( - self.log, - "Error applying attestation to fork choice"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Error applying attestation to fork choice" ), } } @@ -377,11 +374,10 @@ impl NetworkBeaconProcessor { .add_to_naive_aggregation_pool(&verified_attestation) { debug!( - self.log, - "Attestation invalid for agg pool"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Attestation invalid for agg pool" ) } @@ -459,10 +455,9 @@ impl NetworkBeaconProcessor { seen_timestamp, ) { error!( - &self.log, - "Unable to queue converted SingleAttestation"; - "error" => %e, - "slot" => slot, + error = %e, + %slot, + "Unable to queue converted SingleAttestation" ); self.propagate_validation_result( message_id, @@ -486,11 +481,7 @@ impl NetworkBeaconProcessor { beacon_block_root, )) .unwrap_or_else(|_| { - warn!( - self.log, - "Failed to send to sync service"; - "msg" => "UnknownBlockHash" - ) + warn!(msg = "UnknownBlockHash", "Failed to send to sync service") }); let processor = self.clone(); // Do not allow this attestation to be re-processed beyond this point. @@ -510,10 +501,7 @@ impl NetworkBeaconProcessor { }), }); if sender.try_send(reprocess_msg).is_err() { - error!( - self.log, - "Failed to send attestation for re-processing"; - ) + error!("Failed to send attestation for re-processing") } } else { // We shouldn't make any further attempts to process this attestation. @@ -611,9 +599,8 @@ impl NetworkBeaconProcessor { Ok(results) => results, Err(e) => { error!( - self.log, - "Batch agg. attn verification failed"; - "error" => ?e + error = ?e, + "Batch agg. attn verification failed" ); return; } @@ -624,10 +611,9 @@ impl NetworkBeaconProcessor { // The log is `crit` since in this scenario we might be penalizing/rewarding the wrong // peer. crit!( - self.log, - "Batch agg. attestation result mismatch"; - "results" => results.len(), - "packages" => packages.len(), + results = results.len(), + packages = packages.len(), + "Batch agg. attestation result mismatch" ) } @@ -707,30 +693,27 @@ impl NetworkBeaconProcessor { e, )) => { debug!( - self.log, - "Aggregate invalid for fork choice"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Aggregate invalid for fork choice" ) } e => error!( - self.log, - "Error applying aggregate to fork choice"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Error applying aggregate to fork choice" ), } } if let Err(e) = self.chain.add_to_block_inclusion_pool(verified_aggregate) { debug!( - self.log, - "Attestation invalid for op pool"; - "reason" => ?e, - "peer" => %peer_id, - "beacon_block_root" => ?beacon_block_root + reason = ?e, + %peer_id, + ?beacon_block_root, + "Attestation invalid for op pool" ) } @@ -786,11 +769,10 @@ impl NetworkBeaconProcessor { ); debug!( - self.log, - "Successfully verified gossip data column sidecar"; - "slot" => %slot, - "block_root" => %block_root, - "index" => %index, + %slot, + %block_root, + %index, + "Successfully verified gossip data column sidecar" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); @@ -817,11 +799,10 @@ impl NetworkBeaconProcessor { match err { GossipDataColumnError::ParentUnknown { parent_root } => { debug!( - self.log, - "Unknown parent hash for column"; - "action" => "requesting parent", - "block_root" => %block_root, - "parent_root" => %parent_root, + action = "requesting parent", + %block_root, + %parent_root, + "Unknown parent hash for column" ); self.send_sync_message(SyncMessage::UnknownParentDataColumn( peer_id, @@ -831,9 +812,8 @@ impl NetworkBeaconProcessor { GossipDataColumnError::PubkeyCacheTimeout | GossipDataColumnError::BeaconChainError(_) => { crit!( - self.log, - "Internal error when verifying column sidecar"; - "error" => ?err, + error = ?err, + "Internal error when verifying column sidecar" ) } GossipDataColumnError::ProposalSignatureInvalid @@ -848,12 +828,11 @@ impl NetworkBeaconProcessor { | GossipDataColumnError::InconsistentCommitmentsOrProofLength | GossipDataColumnError::NotFinalizedDescendant { .. } => { debug!( - self.log, - "Could not verify column sidecar for gossip. Rejecting the column sidecar"; - "error" => ?err, - "slot" => %slot, - "block_root" => %block_root, - "index" => %index, + error = ?err, + %slot, + %block_root, + %index, + "Could not verify column sidecar for gossip. Rejecting the column sidecar" ); // Prevent recurring behaviour by penalizing the peer slightly. self.gossip_penalize_peer( @@ -872,22 +851,20 @@ impl NetworkBeaconProcessor { // Do not penalise the peer. // Gossip filter should filter any duplicates received after this. debug!( - self.log, - "Received already available column sidecar. Ignoring the column sidecar"; - "slot" => %slot, - "block_root" => %block_root, - "index" => %index, + %slot, + %block_root, + %index, + "Received already available column sidecar. Ignoring the column sidecar" ) } GossipDataColumnError::FutureSlot { .. } | GossipDataColumnError::PastFinalizedSlot { .. } => { debug!( - self.log, - "Could not verify column sidecar for gossip. Ignoring the column sidecar"; - "error" => ?err, - "slot" => %slot, - "block_root" => %block_root, - "index" => %index, + error = ?err, + %slot, + %block_root, + %index, + "Could not verify column sidecar for gossip. Ignoring the column sidecar" ); // Prevent recurring behaviour by penalizing the peer slightly. self.gossip_penalize_peer( @@ -933,23 +910,21 @@ impl NetworkBeaconProcessor { if delay >= self.chain.slot_clock.unagg_attestation_production_delay() { metrics::inc_counter(&metrics::BEACON_BLOB_GOSSIP_ARRIVED_LATE_TOTAL); debug!( - self.log, - "Gossip blob arrived late"; - "block_root" => ?gossip_verified_blob.block_root(), - "proposer_index" => gossip_verified_blob.block_proposer_index(), - "slot" => gossip_verified_blob.slot(), - "delay" => ?delay, - "commitment" => %gossip_verified_blob.kzg_commitment(), + block_root = ?gossip_verified_blob.block_root(), + proposer_index = gossip_verified_blob.block_proposer_index(), + slot = %gossip_verified_blob.slot(), + delay = ?delay, + commitment = %gossip_verified_blob.kzg_commitment(), + "Gossip blob arrived late" ); } debug!( - self.log, - "Successfully verified gossip blob"; - "slot" => %slot, - "root" => %root, - "index" => %index, - "commitment" => %gossip_verified_blob.kzg_commitment(), + %slot, + %root, + %index, + commitment = %gossip_verified_blob.kzg_commitment(), + "Successfully verified gossip blob" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); @@ -972,12 +947,11 @@ impl NetworkBeaconProcessor { match err { GossipBlobError::BlobParentUnknown { parent_root } => { debug!( - self.log, - "Unknown parent hash for blob"; - "action" => "requesting parent", - "block_root" => %root, - "parent_root" => %parent_root, - "commitment" => %commitment, + action = "requesting parent", + block_root = %root, + parent_root = %parent_root, + %commitment, + "Unknown parent hash for blob" ); self.send_sync_message(SyncMessage::UnknownParentBlob( peer_id, @@ -986,9 +960,8 @@ impl NetworkBeaconProcessor { } GossipBlobError::PubkeyCacheTimeout | GossipBlobError::BeaconChainError(_) => { crit!( - self.log, - "Internal error when verifying blob sidecar"; - "error" => ?err, + error = ?err, + "Internal error when verifying blob sidecar" ) } GossipBlobError::ProposalSignatureInvalid @@ -1000,13 +973,12 @@ impl NetworkBeaconProcessor { | GossipBlobError::KzgError(_) | GossipBlobError::NotFinalizedDescendant { .. } => { warn!( - self.log, - "Could not verify blob sidecar for gossip. Rejecting the blob sidecar"; - "error" => ?err, - "slot" => %slot, - "root" => %root, - "index" => %index, - "commitment" => %commitment, + error = ?err, + %slot, + %root, + %index, + %commitment, + "Could not verify blob sidecar for gossip. Rejecting the blob sidecar" ); // Prevent recurring behaviour by penalizing the peer. self.gossip_penalize_peer( @@ -1024,22 +996,20 @@ impl NetworkBeaconProcessor { // We may have received the blob from the EL. Do not penalise the peer. // Gossip filter should filter any duplicates received after this. debug!( - self.log, - "Received already available blob sidecar. Ignoring the blob sidecar"; - "slot" => %slot, - "root" => %root, - "index" => %index, + %slot, + %root, + %index, + "Received already available blob sidecar. Ignoring the blob sidecar" ) } GossipBlobError::FutureSlot { .. } => { debug!( - self.log, - "Could not verify blob sidecar for gossip. Ignoring the blob sidecar"; - "error" => ?err, - "slot" => %slot, - "root" => %root, - "index" => %index, - "commitment" => %commitment, + error = ?err, + %slot, + %root, + %index, + %commitment, + "Could not verify blob sidecar for gossip. Ignoring the blob sidecar" ); // Prevent recurring behaviour by penalizing the peer slightly. self.gossip_penalize_peer( @@ -1055,13 +1025,12 @@ impl NetworkBeaconProcessor { } GossipBlobError::PastFinalizedSlot { .. } => { debug!( - self.log, - "Could not verify blob sidecar for gossip. Ignoring the blob sidecar"; - "error" => ?err, - "slot" => %slot, - "root" => %root, - "index" => %index, - "commitment" => %commitment, + error = ?err, + %slot, + %root, + %index, + %commitment, + "Could not verify blob sidecar for gossip. Ignoring the blob sidecar" ); // Prevent recurring behaviour by penalizing the peer. A low-tolerance // error is fine because there's no reason for peers to be propagating old @@ -1099,9 +1068,8 @@ impl NetworkBeaconProcessor { match &result { Ok(AvailabilityProcessingStatus::Imported(block_root)) => { info!( - self.log, - "Gossipsub blob processed - imported fully available block"; - "block_root" => %block_root + %block_root, + "Gossipsub blob processed - imported fully available block" ); self.chain.recompute_head_at_current_slot().await; @@ -1112,29 +1080,25 @@ impl NetworkBeaconProcessor { } Ok(AvailabilityProcessingStatus::MissingComponents(slot, block_root)) => { debug!( - self.log, - "Processed gossip blob - waiting for other components"; - "slot" => %slot, - "blob_index" => %blob_index, - "block_root" => %block_root, + %slot, + %blob_index, + %block_root, + "Processed gossip blob - waiting for other components" ); } Err(BlockError::DuplicateFullyImported(_)) => { debug!( - self.log, - "Ignoring gossip blob already imported"; - "block_root" => ?block_root, - "blob_index" => blob_index, + ?block_root, + blob_index, "Ignoring gossip blob already imported" ); } Err(err) => { debug!( - self.log, - "Invalid gossip blob"; - "outcome" => ?err, - "block_root" => ?block_root, - "block_slot" => blob_slot, - "blob_index" => blob_index, + outcome = ?err, + ?block_root, + %blob_slot, + blob_index, + "Invalid gossip blob" ); self.gossip_penalize_peer( peer_id, @@ -1177,9 +1141,8 @@ impl NetworkBeaconProcessor { Ok(availability) => match availability { AvailabilityProcessingStatus::Imported(block_root) => { info!( - self.log, - "Gossipsub data column processed, imported fully available block"; - "block_root" => %block_root + %block_root, + "Gossipsub data column processed, imported fully available block" ); self.chain.recompute_head_at_current_slot().await; @@ -1190,11 +1153,10 @@ impl NetworkBeaconProcessor { } AvailabilityProcessingStatus::MissingComponents(slot, block_root) => { trace!( - self.log, - "Processed data column, waiting for other components"; - "slot" => %slot, - "data_column_index" => %data_column_index, - "block_root" => %block_root, + %slot, + %data_column_index, + %block_root, + "Processed data column, waiting for other components" ); self.attempt_data_column_reconstruction(block_root).await; @@ -1202,20 +1164,17 @@ impl NetworkBeaconProcessor { }, Err(BlockError::DuplicateFullyImported(_)) => { debug!( - self.log, - "Ignoring gossip column already imported"; - "block_root" => ?block_root, - "data_column_index" => data_column_index, + ?block_root, + data_column_index, "Ignoring gossip column already imported" ); } Err(err) => { debug!( - self.log, - "Invalid gossip data column"; - "outcome" => ?err, - "block root" => ?block_root, - "block slot" => data_column_slot, - "data column index" => data_column_index, + outcome = ?err, + ?block_root, + block_slot = %data_column_slot, + data_column_index, + "Invalid gossip data column" ); self.gossip_penalize_peer( peer_id, @@ -1271,9 +1230,8 @@ impl NetworkBeaconProcessor { drop(handle); } else { debug!( - self.log, - "RPC block is being imported"; - "block_root" => %block_root, + %block_root, + "RPC block is being imported" ); } } @@ -1329,20 +1287,18 @@ impl NetworkBeaconProcessor { if block_delay >= self.chain.slot_clock.unagg_attestation_production_delay() { metrics::inc_counter(&metrics::BEACON_BLOCK_DELAY_GOSSIP_ARRIVED_LATE_TOTAL); debug!( - self.log, - "Gossip block arrived late"; - "block_root" => ?verified_block.block_root, - "proposer_index" => verified_block.block.message().proposer_index(), - "slot" => verified_block.block.slot(), - "block_delay" => ?block_delay, + block_root = ?verified_block.block_root, + proposer_index = verified_block.block.message().proposer_index(), + slot = ?verified_block.block.slot(), + ?block_delay, + "Gossip block arrived late" ); } info!( - self.log, - "New block received"; - "slot" => verified_block.block.slot(), - "root" => ?verified_block.block_root + slot = %verified_block.block.slot(), + root = ?verified_block.block_root, + "New block received" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); @@ -1362,9 +1318,8 @@ impl NetworkBeaconProcessor { } Err(e @ BlockError::Slashable) => { warn!( - self.log, - "Received equivocating block from peer"; - "error" => ?e + error = ?e, + "Received equivocating block from peer" ); /* punish peer for submitting an equivocation, but not too harshly as honest peers may conceivably forward equivocating blocks to us from time to time */ self.gossip_penalize_peer( @@ -1375,19 +1330,14 @@ impl NetworkBeaconProcessor { return None; } Err(BlockError::ParentUnknown { .. }) => { - debug!( - self.log, - "Unknown parent for gossip block"; - "root" => ?block_root - ); + debug!(?block_root, "Unknown parent for gossip block"); self.send_sync_message(SyncMessage::UnknownParentBlock(peer_id, block, block_root)); return None; } Err(e @ BlockError::BeaconChainError(_)) => { debug!( - self.log, - "Gossip block beacon chain error"; - "error" => ?e, + error = ?e, + "Gossip block beacon chain error" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return None; @@ -1397,18 +1347,16 @@ impl NetworkBeaconProcessor { | BlockError::DuplicateImportStatusUnknown(..), ) => { debug!( - self.log, - "Gossip block is already known"; - "block_root" => %block_root, + %block_root, + "Gossip block is already known" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return None; } Err(e @ BlockError::FutureSlot { .. }) => { debug!( - self.log, - "Could not verify block for gossip. Ignoring the block"; - "error" => %e + error = %e, + "Could not verify block for gossip. Ignoring the block" ); // Prevent recurring behaviour by penalizing the peer slightly. self.gossip_penalize_peer( @@ -1422,9 +1370,8 @@ impl NetworkBeaconProcessor { Err(e @ BlockError::WouldRevertFinalizedSlot { .. }) | Err(e @ BlockError::NotFinalizedDescendant { .. }) => { debug!( - self.log, - "Could not verify block for gossip. Ignoring the block"; - "error" => %e + error = %e, + "Could not verify block for gossip. Ignoring the block" ); // The spec says we must IGNORE these blocks but there's no reason for an honest // and non-buggy client to be gossiping blocks that blatantly conflict with @@ -1439,8 +1386,7 @@ impl NetworkBeaconProcessor { return None; } Err(ref e @ BlockError::ExecutionPayloadError(ref epe)) if !epe.penalize_peer() => { - debug!(self.log, "Could not verify block for gossip. Ignoring the block"; - "error" => %e); + debug!(error = %e, "Could not verify block for gossip. Ignoring the block"); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return None; } @@ -1458,8 +1404,7 @@ impl NetworkBeaconProcessor { | Err(e @ BlockError::ExecutionPayloadError(_)) | Err(e @ BlockError::ParentExecutionPayloadInvalid { .. }) | Err(e @ BlockError::GenesisBlock) => { - warn!(self.log, "Could not verify block for gossip. Rejecting the block"; - "error" => %e); + warn!(error = %e, "Could not verify block for gossip. Rejecting the block"); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( peer_id, @@ -1471,26 +1416,12 @@ impl NetworkBeaconProcessor { // Note: This error variant cannot be reached when doing gossip validation // as we do not do availability checks here. Err(e @ BlockError::AvailabilityCheck(_)) => { - crit!(self.log, "Internal block gossip validation error. Availability check during - gossip validation"; - "error" => %e - ); + crit!(error = %e, "Internal block gossip validation error. Availability check during gossip validation"); return None; } - Err(e @ BlockError::InternalError(_)) => { - error!(self.log, "Internal block gossip validation error"; - "error" => %e - ); - return None; - } - Err(e @ BlockError::BlobNotRequired(_)) => { - // TODO(das): penalty not implemented yet as other clients may still send us blobs - // during early stage of implementation. - debug!(self.log, "Received blobs for slot after PeerDAS epoch from peer"; - "error" => %e, - "peer_id" => %peer_id, - ); - self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); + // BlobNotRequired is unreachable. Only constructed in `process_gossip_blob` + Err(e @ BlockError::InternalError(_)) | Err(e @ BlockError::BlobNotRequired(_)) => { + error!(error = %e, "Internal block gossip validation error"); return None; } }; @@ -1519,11 +1450,10 @@ impl NetworkBeaconProcessor { // tolerance for block imports. Ok(current_slot) if block_slot > current_slot => { warn!( - self.log, - "Block arrived early"; - "block_slot" => %block_slot, - "block_root" => ?block_root, - "msg" => "if this happens consistently, check system clock" + %block_slot, + ?block_root, + msg = "if this happens consistently, check system clock", + "Block arrived early" ); // Take note of how early this block arrived. @@ -1564,11 +1494,10 @@ impl NetworkBeaconProcessor { .is_err() { error!( - self.log, - "Failed to defer block import"; - "block_slot" => %block_slot, - "block_root" => ?block_root, - "location" => "block gossip" + %block_slot, + ?block_root, + location = "block gossip", + "Failed to defer block import" ) } None @@ -1576,12 +1505,11 @@ impl NetworkBeaconProcessor { Ok(_) => Some(verified_block), Err(e) => { error!( - self.log, - "Failed to defer block import"; - "error" => ?e, - "block_slot" => %block_slot, - "block_root" => ?block_root, - "location" => "block gossip" + error = ?e, + %block_slot, + ?block_root, + location = "block gossip", + "Failed to defer block import" ); None } @@ -1603,9 +1531,10 @@ impl NetworkBeaconProcessor { let block = verified_block.block.block_cloned(); let block_root = verified_block.block_root; - // TODO(das) Might be too early to issue a request here. We haven't checked that the block - // actually includes blob transactions and thus has data. A peer could send a block is - // garbage commitments, and make us trigger sampling for a block that does not have data. + // Note: okay to issue sampling request before the block is execution verified. If the + // proposer sends us a block with invalid blob transactions it can trigger us to issue + // sampling queries that will never resolve. This attack is equivalent to withholding data. + // Dismissed proposal to move this block to post-execution: https://github.com/sigp/lighthouse/pull/6492 if block.num_expected_blobs() > 0 { // Trigger sampling for block not yet execution valid. At this point column custodials are // unlikely to have received their columns. Triggering sampling so early is only viable with @@ -1652,18 +1581,16 @@ impl NetworkBeaconProcessor { .is_err() { error!( - self.log, - "Failed to inform block import"; - "source" => "gossip", - "block_root" => ?block_root, + source = "gossip", + ?block_root, + "Failed to inform block import" ) }; debug!( - self.log, - "Gossipsub block processed"; - "block" => ?block_root, - "peer_id" => %peer_id + ?block_root, + %peer_id, + "Gossipsub block processed" ); self.chain.recompute_head_at_current_slot().await; @@ -1675,10 +1602,9 @@ impl NetworkBeaconProcessor { } Ok(AvailabilityProcessingStatus::MissingComponents(slot, block_root)) => { trace!( - self.log, - "Processed block, waiting for other components"; - "slot" => slot, - "block_root" => %block_root, + %slot, + %block_root, + "Processed block, waiting for other components" ); } Err(BlockError::ParentUnknown { .. }) => { @@ -1688,26 +1614,23 @@ impl NetworkBeaconProcessor { // can recover by receiving another block / blob / attestation referencing the // chain that includes this block. error!( - self.log, - "Block with unknown parent attempted to be processed"; - "block_root" => %block_root, - "peer_id" => %peer_id + %block_root, + %peer_id, + "Block with unknown parent attempted to be processed" ); } Err(ref e @ BlockError::ExecutionPayloadError(ref epe)) if !epe.penalize_peer() => { debug!( - self.log, - "Failed to verify execution payload"; - "error" => %e + error = %e, + "Failed to verify execution payload" ); } Err(BlockError::AvailabilityCheck(err)) => { match err.category() { AvailabilityCheckErrorCategory::Internal => { warn!( - self.log, - "Internal availability check error"; - "error" => ?err, + error = ?err, + "Internal availability check error" ); } AvailabilityCheckErrorCategory::Malicious => { @@ -1719,20 +1642,18 @@ impl NetworkBeaconProcessor { // 2. The proposer being malicious and sending inconsistent // blocks and blobs. warn!( - self.log, - "Received invalid blob or malicious proposer"; - "error" => ?err + error = ?err, + "Received invalid blob or malicious proposer" ); } } } other => { debug!( - self.log, - "Invalid gossip beacon block"; - "outcome" => ?other, - "block root" => ?block_root, - "block slot" => block.slot() + outcome = ?other, + ?block_root, + block_slot = %block.slot(), + "Invalid gossip beacon block" ); self.gossip_penalize_peer( peer_id, @@ -1740,21 +1661,14 @@ impl NetworkBeaconProcessor { "bad_gossip_block_ssz", ); trace!( - self.log, - "Invalid gossip beacon block ssz"; - "ssz" => format_args!("0x{}", hex::encode(block.as_ssz_bytes())), + ssz = format_args!("0x{}", hex::encode(block.as_ssz_bytes())), + "Invalid gossip beacon block ssz" ); } }; if let Err(e) = &result { - self.maybe_store_invalid_block( - &invalid_block_storage, - block_root, - &block, - e, - &self.log, - ); + self.maybe_store_invalid_block(&invalid_block_storage, block_root, &block, e); } self.send_sync_message(SyncMessage::GossipBlockProcessResult { @@ -1776,20 +1690,18 @@ impl NetworkBeaconProcessor { Ok(ObservationOutcome::AlreadyKnown) => { self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); debug!( - self.log, - "Dropping exit for already exiting validator"; - "validator_index" => validator_index, - "peer" => %peer_id + validator_index, + peer = %peer_id, + "Dropping exit for already exiting validator" ); return; } Err(e) => { debug!( - self.log, - "Dropping invalid exit"; - "validator_index" => validator_index, - "peer" => %peer_id, - "error" => ?e + validator_index, + %peer_id, + error = ?e, + "Dropping invalid exit" ); // These errors occur due to a fault in the beacon chain. It is not necessarily // the fault on the peer. @@ -1816,7 +1728,7 @@ impl NetworkBeaconProcessor { self.chain.import_voluntary_exit(exit); - debug!(self.log, "Successfully imported voluntary exit"); + debug!("Successfully imported voluntary exit"); metrics::inc_counter(&metrics::BEACON_PROCESSOR_EXIT_IMPORTED_TOTAL); } @@ -1836,11 +1748,10 @@ impl NetworkBeaconProcessor { Ok(ObservationOutcome::New(slashing)) => slashing, Ok(ObservationOutcome::AlreadyKnown) => { debug!( - self.log, - "Dropping proposer slashing"; - "reason" => "Already seen a proposer slashing for that validator", - "validator_index" => validator_index, - "peer" => %peer_id + reason = "Already seen a proposer slashing for that validator", + validator_index, + peer = %peer_id, + "Dropping proposer slashing" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return; @@ -1849,11 +1760,10 @@ impl NetworkBeaconProcessor { // This is likely a fault with the beacon chain and not necessarily a // malicious message from the peer. debug!( - self.log, - "Dropping invalid proposer slashing"; - "validator_index" => validator_index, - "peer" => %peer_id, - "error" => ?e + validator_index, + %peer_id, + error = ?e, + "Dropping invalid proposer slashing" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -1878,7 +1788,7 @@ impl NetworkBeaconProcessor { .register_gossip_proposer_slashing(slashing.as_inner()); self.chain.import_proposer_slashing(slashing); - debug!(self.log, "Successfully imported proposer slashing"); + debug!("Successfully imported proposer slashing"); metrics::inc_counter(&metrics::BEACON_PROCESSOR_PROPOSER_SLASHING_IMPORTED_TOTAL); } @@ -1896,20 +1806,18 @@ impl NetworkBeaconProcessor { Ok(ObservationOutcome::New(slashing)) => slashing, Ok(ObservationOutcome::AlreadyKnown) => { debug!( - self.log, - "Dropping attester slashing"; - "reason" => "Slashings already known for all slashed validators", - "peer" => %peer_id + reason = "Slashings already known for all slashed validators", + peer = %peer_id, + "Dropping attester slashing" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return; } Err(e) => { debug!( - self.log, - "Dropping invalid attester slashing"; - "peer" => %peer_id, - "error" => ?e + %peer_id, + error = ?e, + "Dropping invalid attester slashing" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); // Penalize peer slightly for invalids. @@ -1933,7 +1841,7 @@ impl NetworkBeaconProcessor { .register_gossip_attester_slashing(slashing.as_inner().to_ref()); self.chain.import_attester_slashing(slashing); - debug!(self.log, "Successfully imported attester slashing"); + debug!("Successfully imported attester slashing"); metrics::inc_counter(&metrics::BEACON_PROCESSOR_ATTESTER_SLASHING_IMPORTED_TOTAL); } @@ -1954,20 +1862,18 @@ impl NetworkBeaconProcessor { Ok(ObservationOutcome::AlreadyKnown) => { self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); debug!( - self.log, - "Dropping BLS to execution change"; - "validator_index" => validator_index, - "peer" => %peer_id + validator_index, + peer = %peer_id, + "Dropping BLS to execution change" ); return; } Err(e) => { debug!( - self.log, - "Dropping invalid BLS to execution change"; - "validator_index" => validator_index, - "peer" => %peer_id, - "error" => ?e + validator_index, + %peer_id, + error = ?e, + "Dropping invalid BLS to execution change" ); // We ignore pre-capella messages without penalizing peers. if matches!(e, BeaconChainError::BlsToExecutionPriorToCapella) { @@ -2005,10 +1911,9 @@ impl NetworkBeaconProcessor { .import_bls_to_execution_change(change, received_pre_capella); debug!( - self.log, - "Successfully imported BLS to execution change"; - "validator_index" => validator_index, - "address" => ?address, + validator_index, + ?address, + "Successfully imported BLS to execution change" ); metrics::inc_counter(&metrics::BEACON_PROCESSOR_BLS_TO_EXECUTION_CHANGE_IMPORTED_TOTAL); @@ -2067,10 +1972,9 @@ impl NetworkBeaconProcessor { .add_to_naive_sync_aggregation_pool(sync_signature) { debug!( - self.log, - "Sync committee signature invalid for agg pool"; - "reason" => ?e, - "peer" => %peer_id, + reason = ?e, + %peer_id, + "Sync committee signature invalid for agg pool" ) } @@ -2129,10 +2033,9 @@ impl NetworkBeaconProcessor { .add_contribution_to_block_inclusion_pool(sync_contribution) { debug!( - self.log, - "Sync contribution invalid for op pool"; - "reason" => ?e, - "peer" => %peer_id, + reason = ?e, + %peer_id, + "Sync contribution invalid for op pool" ) } metrics::inc_counter(&metrics::BEACON_PROCESSOR_SYNC_CONTRIBUTION_IMPORTED_TOTAL); @@ -2157,10 +2060,9 @@ impl NetworkBeaconProcessor { match e { LightClientFinalityUpdateError::InvalidLightClientFinalityUpdate => { debug!( - self.log, - "Light client invalid finality update"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client invalid finality update" ); self.gossip_penalize_peer( @@ -2171,10 +2073,9 @@ impl NetworkBeaconProcessor { } LightClientFinalityUpdateError::TooEarly => { debug!( - self.log, - "Light client finality update too early"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client finality update too early" ); self.gossip_penalize_peer( @@ -2185,10 +2086,9 @@ impl NetworkBeaconProcessor { } LightClientFinalityUpdateError::SigSlotStartIsNone | LightClientFinalityUpdateError::FailedConstructingUpdate => debug!( - self.log, - "Light client error constructing finality update"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client error constructing finality update" ), } self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -2210,10 +2110,9 @@ impl NetworkBeaconProcessor { ) { Ok(verified_light_client_optimistic_update) => { debug!( - self.log, - "Light client successful optimistic update"; - "peer" => %peer_id, - "parent_root" => %verified_light_client_optimistic_update.parent_root, + %peer_id, + parent_root = %verified_light_client_optimistic_update.parent_root, + "Light client successful optimistic update" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); @@ -2225,10 +2124,9 @@ impl NetworkBeaconProcessor { &metrics::BEACON_PROCESSOR_REPROCESSING_QUEUE_SENT_OPTIMISTIC_UPDATES, ); debug!( - self.log, - "Optimistic update for unknown block"; - "peer_id" => %peer_id, - "parent_root" => ?parent_root + %peer_id, + ?parent_root, + "Optimistic update for unknown block" ); if let Some(sender) = reprocess_tx { @@ -2249,17 +2147,13 @@ impl NetworkBeaconProcessor { ); if sender.try_send(msg).is_err() { - error!( - self.log, - "Failed to send optimistic update for re-processing"; - ) + error!("Failed to send optimistic update for re-processing") } } else { debug!( - self.log, - "Not sending light client update because it had been reprocessed"; - "peer_id" => %peer_id, - "parent_root" => ?parent_root + %peer_id, + ?parent_root, + "Not sending light client update because it had been reprocessed" ); self.propagate_validation_result( @@ -2274,10 +2168,9 @@ impl NetworkBeaconProcessor { metrics::register_optimistic_update_error(&e); debug!( - self.log, - "Light client invalid optimistic update"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client invalid optimistic update" ); self.gossip_penalize_peer( @@ -2289,10 +2182,9 @@ impl NetworkBeaconProcessor { LightClientOptimisticUpdateError::TooEarly => { metrics::register_optimistic_update_error(&e); debug!( - self.log, - "Light client optimistic update too early"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client optimistic update too early" ); self.gossip_penalize_peer( @@ -2306,10 +2198,9 @@ impl NetworkBeaconProcessor { metrics::register_optimistic_update_error(&e); debug!( - self.log, - "Light client error constructing optimistic update"; - "peer" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Light client error constructing optimistic update" ) } } @@ -2341,11 +2232,10 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message, _only_ if we trust our own clock. */ trace!( - self.log, - "Attestation is not within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + "Attestation is not within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots" ); // Peers that are slow or not to spec can spam us with these messages draining our @@ -2472,11 +2362,10 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ trace!( - self.log, - "Attestation already known"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + "Attestation already known" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return; @@ -2489,11 +2378,10 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ trace!( - self.log, - "Aggregator already known"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + "Aggregator already known" ); // This is an allowed behaviour. self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -2510,13 +2398,12 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ debug!( - self.log, - "Prior attestation known"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "epoch" => %epoch, - "validator_index" => validator_index, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + %epoch, + validator_index, + ?attestation_type, + "Prior attestation known" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -2531,11 +2418,10 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message. */ debug!( - self.log, - "Validation Index too high"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + "Validation Index too high" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -2551,12 +2437,11 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message. */ debug!( - self.log, - "Committee index non zero"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root, - "type" => ?attestation_type, - "committee_index" => index, + %peer_id, + block = ?beacon_block_root, + ?attestation_type, + committee_index = index, + "Committee index non zero" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -2567,10 +2452,9 @@ impl NetworkBeaconProcessor { } AttnError::UnknownHeadBlock { beacon_block_root } => { trace!( - self.log, - "Attestation for unknown block"; - "peer_id" => %peer_id, - "block" => ?beacon_block_root + %peer_id, + block = ?beacon_block_root, + "Attestation for unknown block" ); if let Some(sender) = reprocess_tx { // We don't know the block, get the sync manager to handle the block lookup, and @@ -2581,11 +2465,7 @@ impl NetworkBeaconProcessor { *beacon_block_root, )) .unwrap_or_else(|_| { - warn!( - self.log, - "Failed to send to sync service"; - "msg" => "UnknownBlockHash" - ) + warn!(msg = "UnknownBlockHash", "Failed to send to sync service") }); let msg = match failed_att { FailedAtt::Aggregate { @@ -2614,9 +2494,8 @@ impl NetworkBeaconProcessor { // for `SingleAttestation`s separately and should not be able to hit // an `UnknownHeadBlock` error. error!( - self.log, - "Dropping SingleAttestation instead of requeueing"; - "block_root" => ?beacon_block_root, + block_root = ?beacon_block_root, + "Dropping SingleAttestation instead of requeueing" ); return; } @@ -2648,10 +2527,7 @@ impl NetworkBeaconProcessor { }; if sender.try_send(msg).is_err() { - error!( - self.log, - "Failed to send attestation for re-processing"; - ) + error!("Failed to send attestation for re-processing") } } else { // We shouldn't make any further attempts to process this attestation. @@ -2762,10 +2638,9 @@ impl NetworkBeaconProcessor { * The attestation was received on an incorrect subnet id. */ debug!( - self.log, - "Received attestation on incorrect subnet"; - "expected" => ?expected, - "received" => ?received, + ?expected, + ?received, + "Received attestation on incorrect subnet" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -2823,10 +2698,9 @@ impl NetworkBeaconProcessor { * The message is not necessarily invalid, but we choose to ignore it. */ debug!( - self.log, - "Rejected long skip slot attestation"; - "head_block_slot" => head_block_slot, - "attestation_slot" => attestation_slot, + %head_block_slot, + %attestation_slot, + "Rejected long skip slot attestation" ); // In this case we wish to penalize gossipsub peers that do this to avoid future // attestations that have too many skip slots. @@ -2839,10 +2713,9 @@ impl NetworkBeaconProcessor { } AttnError::HeadBlockFinalized { beacon_block_root } => { debug!( - self.log, - "Ignored attestation to finalized block"; - "block_root" => ?beacon_block_root, - "attestation_slot" => failed_att.attestation_data().slot, + block_root = ?beacon_block_root, + attestation_slot = %failed_att.attestation_data().slot, + "Ignored attestation to finalized block" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); @@ -2858,19 +2731,18 @@ impl NetworkBeaconProcessor { AttnError::BeaconChainError(BeaconChainError::DBError(Error::HotColdDBError( HotColdDBError::FinalizedStateNotInHotDatabase { .. }, ))) => { - debug!(self.log, "Attestation for finalized state"; "peer_id" => % peer_id); + debug!(%peer_id, "Attestation for finalized state"); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); } e @ AttnError::BeaconChainError(BeaconChainError::MaxCommitteePromises(_)) => { debug!( - self.log, - "Dropping attestation"; - "target_root" => ?failed_att.attestation_data().target.root, - "beacon_block_root" => ?beacon_block_root, - "slot" => ?failed_att.attestation_data().slot, - "type" => ?attestation_type, - "error" => ?e, - "peer_id" => % peer_id + target_root = ?failed_att.attestation_data().target.root, + ?beacon_block_root, + slot = ?failed_att.attestation_data().slot, + ?attestation_type, + error = ?e, + %peer_id, + "Dropping attestation" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); } @@ -2883,25 +2755,23 @@ impl NetworkBeaconProcessor { * It's not clear if the message is invalid/malicious. */ error!( - self.log, - "Unable to validate attestation"; - "beacon_block_root" => ?beacon_block_root, - "slot" => ?failed_att.attestation_data().slot, - "type" => ?attestation_type, - "peer_id" => %peer_id, - "error" => ?e, + ?beacon_block_root, + slot = ?failed_att.attestation_data().slot, + ?attestation_type, + %peer_id, + error = ?e, + "Unable to validate attestation" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); } } debug!( - self.log, - "Invalid attestation from network"; - "reason" => ?error, - "block" => ?beacon_block_root, - "peer_id" => %peer_id, - "type" => ?attestation_type, + reason = ?error, + block = ?beacon_block_root, + %peer_id, + ?attestation_type, + "Invalid attestation from network" ); } @@ -2927,10 +2797,9 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message, _only_ if we trust our own clock. */ trace!( - self.log, - "Sync committee message is not within the last MAXIMUM_GOSSIP_CLOCK_DISPARITY slots"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Sync committee message is not within the last MAXIMUM_GOSSIP_CLOCK_DISPARITY slots" ); // Unlike attestations, we have a zero slot buffer in case of sync committee messages, @@ -2952,10 +2821,9 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message, _only_ if we trust our own clock. */ trace!( - self.log, - "Sync committee message is not within the last MAXIMUM_GOSSIP_CLOCK_DISPARITY slots"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Sync committee message is not within the last MAXIMUM_GOSSIP_CLOCK_DISPARITY slots" ); // Compute the slot when we received the message. @@ -3045,10 +2913,9 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ trace!( - self.log, - "Sync committee message is already known"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Sync committee message is already known" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); return; @@ -3061,10 +2928,9 @@ impl NetworkBeaconProcessor { * The peer has published an invalid consensus message. */ debug!( - self.log, - "Validation Index too high"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Validation Index too high" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -3075,10 +2941,9 @@ impl NetworkBeaconProcessor { } SyncCommitteeError::UnknownValidatorPubkey(_) => { debug!( - self.log, - "Validator pubkey is unknown"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Validator pubkey is unknown" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -3092,10 +2957,9 @@ impl NetworkBeaconProcessor { * The sync committee message was received on an incorrect subnet id. */ debug!( - self.log, - "Received sync committee message on incorrect subnet"; - "expected" => ?expected, - "received" => ?received, + ?expected, + ?received, + "Received sync committee message on incorrect subnet" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.gossip_penalize_peer( @@ -3124,10 +2988,9 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ debug!( - self.log, - "Prior sync committee message known"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Prior sync committee message known" ); // Do not penalize the peer. @@ -3143,10 +3006,9 @@ impl NetworkBeaconProcessor { * The peer is not necessarily faulty. */ debug!( - self.log, - "Prior sync contribution message known"; - "peer_id" => %peer_id, - "type" => ?message_type, + %peer_id, + ?message_type, + "Prior sync contribution message known" ); // We still penalize the peer slightly. We don't want this to be a recurring // behaviour. @@ -3169,10 +3031,9 @@ impl NetworkBeaconProcessor { * It's not clear if the message is invalid/malicious. */ error!( - self.log, - "Unable to validate sync committee message"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Unable to validate sync committee message" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); } @@ -3185,10 +3046,9 @@ impl NetworkBeaconProcessor { * It's not clear if the message is invalid/malicious. */ error!( - self.log, - "Unable to validate sync committee message"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Unable to validate sync committee message" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); // Penalize the peer slightly @@ -3200,10 +3060,9 @@ impl NetworkBeaconProcessor { } SyncCommitteeError::ContributionError(e) => { error!( - self.log, - "Error while processing sync contribution"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Error while processing sync contribution" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); // Penalize the peer slightly @@ -3215,10 +3074,9 @@ impl NetworkBeaconProcessor { } SyncCommitteeError::SyncCommitteeError(e) => { error!( - self.log, - "Error while processing sync committee message"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Error while processing sync committee message" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); // Penalize the peer slightly @@ -3233,10 +3091,9 @@ impl NetworkBeaconProcessor { This would most likely imply incompatible configs or an invalid message. */ error!( - self.log, - "Arithematic error while processing sync committee message"; - "peer_id" => %peer_id, - "error" => ?e, + %peer_id, + error = ?e, + "Arithematic error while processing sync committee message" ); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); self.gossip_penalize_peer( @@ -3259,11 +3116,10 @@ impl NetworkBeaconProcessor { } } debug!( - self.log, - "Invalid sync committee message from network"; - "reason" => ?error, - "peer_id" => %peer_id, - "type" => ?message_type, + reason = ?error, + %peer_id, + ?message_type, + "Invalid sync committee message from network" ); } @@ -3321,7 +3177,6 @@ impl NetworkBeaconProcessor { block_root: Hash256, block: &SignedBeaconBlock, error: &BlockError, - log: &Logger, ) { if let InvalidBlockStorage::Enabled(base_dir) = invalid_block_storage { let block_path = base_dir.join(format!("{}_{:?}.ssz", block.slot(), block_root)); @@ -3349,20 +3204,18 @@ impl NetworkBeaconProcessor { }); if let Err(e) = write_result { error!( - log, - "Failed to store invalid block/error"; - "error" => e, - "path" => ?path, - "root" => ?block_root, - "slot" => block.slot(), + error = e, + ?path, + ?block_root, + slot = %block.slot(), + "Failed to store invalid block/error" ) } else { info!( - log, - "Stored invalid block/error "; - "path" => ?path, - "root" => ?block_root, - "slot" => block.slot(), + ?path, + ?block_root, + slot = %block.slot(), + "Stored invalid block/error" ) } }; diff --git a/beacon_node/network/src/network_beacon_processor/mod.rs b/beacon_node/network/src/network_beacon_processor/mod.rs index c06a1f6ee3..b99e71bcea 100644 --- a/beacon_node/network/src/network_beacon_processor/mod.rs +++ b/beacon_node/network/src/network_beacon_processor/mod.rs @@ -9,13 +9,11 @@ use beacon_chain::fetch_blobs::{ }; use beacon_chain::observed_data_sidecars::DoNotObserve; use beacon_chain::{ - builder::Witness, eth1_chain::CachingEth1Backend, AvailabilityProcessingStatus, BeaconChain, - BeaconChainTypes, BlockError, NotifyExecutionLayer, + AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, BlockError, NotifyExecutionLayer, }; use beacon_processor::{ - work_reprocessing_queue::ReprocessQueueMessage, BeaconProcessorChannels, BeaconProcessorSend, - DuplicateCache, GossipAggregatePackage, GossipAttestationPackage, Work, - WorkEvent as BeaconWorkEvent, + work_reprocessing_queue::ReprocessQueueMessage, BeaconProcessorSend, DuplicateCache, + GossipAggregatePackage, GossipAttestationPackage, Work, WorkEvent as BeaconWorkEvent, }; use lighthouse_network::discovery::ConnectionId; use lighthouse_network::rpc::methods::{ @@ -28,15 +26,12 @@ use lighthouse_network::{ Client, MessageId, NetworkGlobals, PeerId, PubsubMessage, }; use rand::prelude::SliceRandom; -use slog::{debug, error, trace, warn, Logger}; -use slot_clock::ManualSlotClock; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; -use store::MemoryStore; use task_executor::TaskExecutor; -use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::{self, error::TrySendError}; +use tracing::{debug, error, trace, warn, Instrument}; use types::*; pub use sync_methods::ChainSegmentProcessId; @@ -71,7 +66,6 @@ pub struct NetworkBeaconProcessor { pub network_globals: Arc>, pub invalid_block_storage: InvalidBlockStorage, pub executor: TaskExecutor, - pub log: Logger, } // Publish blobs in batches of exponentially increasing size. @@ -79,9 +73,7 @@ const BLOB_PUBLICATION_EXP_FACTOR: usize = 2; impl NetworkBeaconProcessor { fn try_send(&self, event: BeaconWorkEvent) -> Result<(), Error> { - self.beacon_processor_send - .try_send(event) - .map_err(Into::into) + self.beacon_processor_send.try_send(event) } /// Create a new `Work` event for some `SingleAttestation`. @@ -602,10 +594,7 @@ impl NetworkBeaconProcessor { blocks: Vec>, ) -> Result<(), Error> { let is_backfill = matches!(&process_id, ChainSegmentProcessId::BackSyncBatchId { .. }); - debug!(self.log, "Batch sending for process"; - "blocks" => blocks.len(), - "id" => ?process_id, - ); + debug!(blocks = blocks.len(), id = ?process_id, "Batch sending for process"); let processor = self.clone(); let process_fn = async move { @@ -918,10 +907,9 @@ impl NetworkBeaconProcessor { /// /// Creates a log if there is an internal error. pub(crate) fn send_sync_message(&self, message: SyncMessage) { - self.sync_tx.send(message).unwrap_or_else(|e| { - debug!(self.log, "Could not send message to the sync service"; - "error" => %e) - }); + self.sync_tx + .send(message) + .unwrap_or_else(|e| debug!(error = %e, "Could not send message to the sync service")); } /// Send a message to `network_tx`. @@ -929,8 +917,7 @@ impl NetworkBeaconProcessor { /// Creates a log if there is an internal error. fn send_network_message(&self, message: NetworkMessage) { self.network_tx.send(message).unwrap_or_else(|e| { - debug!(self.log, "Could not send message to the network service. Likely shutdown"; - "error" => %e) + debug!(error = %e, "Could not send message to the network service. Likely shutdown") }); } @@ -960,48 +947,48 @@ impl NetworkBeaconProcessor { block.clone(), publish_fn, ) + .instrument(tracing::info_span!( + "", + service = "fetch_engine_blobs", + block_root = format!("{:?}", block_root) + )) .await { Ok(Some(availability)) => match availability { AvailabilityProcessingStatus::Imported(_) => { debug!( - self.log, - "Block components retrieved from EL"; - "result" => "imported block and custody columns", - "block_root" => %block_root, + result = "imported block and custody columns", + %block_root, + "Block components retrieved from EL" ); self.chain.recompute_head_at_current_slot().await; } AvailabilityProcessingStatus::MissingComponents(_, _) => { debug!( - self.log, - "Still missing blobs after engine blobs processed successfully"; - "block_root" => %block_root, + %block_root, + "Still missing blobs after engine blobs processed successfully" ); } }, Ok(None) => { debug!( - self.log, - "Fetch blobs completed without import"; - "block_root" => %block_root, + %block_root, + "Fetch blobs completed without import" ); } Err(FetchEngineBlobError::BlobProcessingError(BlockError::DuplicateFullyImported( .., ))) => { debug!( - self.log, - "Fetch blobs duplicate import"; - "block_root" => %block_root, + %block_root, + "Fetch blobs duplicate import" ); } Err(e) => { error!( - self.log, - "Error fetching or processing blobs from EL"; - "error" => ?e, - "block_root" => %block_root, + error = ?e, + %block_root, + "Error fetching or processing blobs from EL" ); } } @@ -1024,19 +1011,17 @@ impl NetworkBeaconProcessor { match &availability_processing_status { AvailabilityProcessingStatus::Imported(hash) => { debug!( - self.log, - "Block components available via reconstruction"; - "result" => "imported block and custody columns", - "block_hash" => %hash, + result = "imported block and custody columns", + block_hash = %hash, + "Block components available via reconstruction" ); self.chain.recompute_head_at_current_slot().await; } AvailabilityProcessingStatus::MissingComponents(_, _) => { debug!( - self.log, - "Block components still missing block after reconstruction"; - "result" => "imported all custody columns", - "block_hash" => %block_root, + result = "imported all custody columns", + block_hash = %block_root, + "Block components still missing block after reconstruction" ); } } @@ -1046,18 +1031,16 @@ impl NetworkBeaconProcessor { Ok(None) => { // reason is tracked via the `KZG_DATA_COLUMN_RECONSTRUCTION_INCOMPLETE_TOTAL` metric trace!( - self.log, - "Reconstruction not required for block"; - "block_hash" => %block_root, + block_hash = %block_root, + "Reconstruction not required for block" ); None } Err(e) => { error!( - self.log, - "Error during data column reconstruction"; - "block_root" => %block_root, - "error" => ?e + %block_root, + error = ?e, + "Error during data column reconstruction" ); None } @@ -1080,7 +1063,6 @@ impl NetworkBeaconProcessor { self.executor.spawn( async move { let chain = self_clone.chain.clone(); - let log = self_clone.chain.logger(); let publish_fn = |blobs: Vec>>| { self_clone.send_network_message(NetworkMessage::Publish { messages: blobs @@ -1109,9 +1091,8 @@ impl NetworkBeaconProcessor { Err(GossipBlobError::RepeatBlob { .. }) => None, Err(e) => { warn!( - log, - "Previously verified blob is invalid"; - "error" => ?e + error = ?e, + "Previously verified blob is invalid" ); None } @@ -1120,10 +1101,9 @@ impl NetworkBeaconProcessor { if !publishable.is_empty() { debug!( - log, - "Publishing blob batch"; - "publish_count" => publishable.len(), - "block_root" => ?block_root, + publish_count = publishable.len(), + ?block_root, + "Publishing blob batch" ); publish_count += publishable.len(); publish_fn(publishable); @@ -1134,12 +1114,11 @@ impl NetworkBeaconProcessor { } debug!( - log, - "Batch blob publication complete"; - "batch_interval" => blob_publication_batch_interval.as_millis(), - "blob_count" => blob_count, - "published_count" => publish_count, - "block_root" => ?block_root, + batch_interval = blob_publication_batch_interval.as_millis(), + blob_count, + publish_count, + ?block_root, + "Batch blob publication complete" ) }, "gradual_blob_publication", @@ -1162,7 +1141,6 @@ impl NetworkBeaconProcessor { self.executor.spawn( async move { let chain = self_clone.chain.clone(); - let log = self_clone.chain.logger(); let publish_fn = |columns: DataColumnSidecarList| { self_clone.send_network_message(NetworkMessage::Publish { messages: columns @@ -1195,9 +1173,8 @@ impl NetworkBeaconProcessor { Err(GossipDataColumnError::PriorKnown { .. }) => None, Err(e) => { warn!( - log, - "Previously verified data column is invalid"; - "error" => ?e + error = ?e, + "Previously verified data column is invalid" ); None } @@ -1206,10 +1183,9 @@ impl NetworkBeaconProcessor { if !publishable.is_empty() { debug!( - log, - "Publishing data column batch"; - "publish_count" => publishable.len(), - "block_root" => ?block_root, + publish_count = publishable.len(), + ?block_root, + "Publishing data column batch" ); publish_count += publishable.len(); publish_fn(publishable); @@ -1219,13 +1195,12 @@ impl NetworkBeaconProcessor { } debug!( - log, - "Batch data column publishing complete"; - "batch_size" => batch_size, - "batch_interval" => blob_publication_batch_interval.as_millis(), - "data_columns_to_publish_count" => data_columns_to_publish.len(), - "published_count" => publish_count, - "block_root" => ?block_root, + batch_size, + batch_interval = blob_publication_batch_interval.as_millis(), + data_columns_to_publish_count = data_columns_to_publish.len(), + publish_count, + ?block_root, + "Batch data column publishing complete" ) }, "gradual_data_column_publication", @@ -1233,9 +1208,20 @@ impl NetworkBeaconProcessor { } } +#[cfg(test)] +use { + beacon_chain::{builder::Witness, eth1_chain::CachingEth1Backend}, + beacon_processor::BeaconProcessorChannels, + slot_clock::ManualSlotClock, + store::MemoryStore, + tokio::sync::mpsc::UnboundedSender, +}; + +#[cfg(test)] type TestBeaconChainType = Witness, E, MemoryStore, MemoryStore>; +#[cfg(test)] impl NetworkBeaconProcessor> { // Instantiates a mostly non-functional version of `Self` and returns the // event receiver that would normally go to the beacon processor. This is @@ -1246,7 +1232,6 @@ impl NetworkBeaconProcessor> { sync_tx: UnboundedSender>, chain: Arc>>, executor: TaskExecutor, - log: Logger, ) -> (Self, mpsc::Receiver>) { let BeaconProcessorChannels { beacon_processor_tx, @@ -1267,7 +1252,6 @@ impl NetworkBeaconProcessor> { network_globals, invalid_block_storage: InvalidBlockStorage::Disabled, executor, - log, }; (network_beacon_processor, beacon_processor_rx) diff --git a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs index 67a1570275..da8e595ddc 100644 --- a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs @@ -11,11 +11,11 @@ use lighthouse_network::rpc::methods::{ use lighthouse_network::rpc::*; use lighthouse_network::{PeerId, PeerRequestId, ReportSource, Response, SyncInfo}; use methods::LightClientUpdatesByRangeRequest; -use slog::{debug, error, warn}; use slot_clock::SlotClock; use std::collections::{hash_map::Entry, HashMap}; use std::sync::Arc; use tokio_stream::StreamExt; +use tracing::{debug, error, warn}; use types::blob_sidecar::BlobIdentifier; use types::{Epoch, EthSpec, FixedBytesExtended, Hash256, Slot}; @@ -115,7 +115,7 @@ impl NetworkBeaconProcessor { pub fn process_status(&self, peer_id: PeerId, status: StatusMessage) { match self.check_peer_relevance(&status) { Ok(Some(irrelevant_reason)) => { - debug!(self.log, "Handshake Failure"; "peer" => %peer_id, "reason" => irrelevant_reason); + debug!(%peer_id, reason = irrelevant_reason, "Handshake Failure"); self.goodbye_peer(peer_id, GoodbyeReason::IrrelevantNetwork); } Ok(None) => { @@ -127,9 +127,10 @@ impl NetworkBeaconProcessor { }; self.send_sync_message(SyncMessage::AddPeer(peer_id, info)); } - Err(e) => error!(self.log, "Could not process status message"; - "peer" => %peer_id, - "error" => ?e + Err(e) => error!( + %peer_id, + error = ?e, + "Could not process status message" ), } } @@ -172,11 +173,10 @@ impl NetworkBeaconProcessor { ) -> Result<(), (RpcErrorResponse, &'static str)> { let log_results = |peer_id, requested_blocks, send_block_count| { debug!( - self.log, - "BlocksByRoot outgoing response processed"; - "peer" => %peer_id, - "requested" => requested_blocks, - "returned" => %send_block_count + %peer_id, + requested = requested_blocks, + returned = %send_block_count, + "BlocksByRoot outgoing response processed" ); }; @@ -187,7 +187,7 @@ impl NetworkBeaconProcessor { { Ok(block_stream) => block_stream, Err(e) => { - error!(self.log, "Error getting block stream"; "error" => ?e); + error!( error = ?e, "Error getting block stream"); return Err((RpcErrorResponse::ServerError, "Error getting block stream")); } }; @@ -207,18 +207,16 @@ impl NetworkBeaconProcessor { } Ok(None) => { debug!( - self.log, - "Peer requested unknown block"; - "peer" => %peer_id, - "request_root" => ?root + %peer_id, + request_root = ?root, + "Peer requested unknown block" ); } Err(BeaconChainError::BlockHashMissingFromExecutionLayer(_)) => { debug!( - self.log, - "Failed to fetch execution payload for blocks by root request"; - "block_root" => ?root, - "reason" => "execution layer not synced", + block_root = ?root, + reason = "execution layer not synced", + "Failed to fetch execution payload for blocks by root request" ); log_results(peer_id, requested_blocks, send_block_count); return Err(( @@ -228,11 +226,10 @@ impl NetworkBeaconProcessor { } Err(e) => { debug!( - self.log, - "Error fetching block for peer"; - "peer" => %peer_id, - "request_root" => ?root, - "error" => ?e, + ?peer_id, + request_root = ?root, + error = ?e, + "Error fetching block for peer" ); } } @@ -332,23 +329,21 @@ impl NetworkBeaconProcessor { } Err(e) => { debug!( - self.log, - "Error fetching blob for peer"; - "peer" => %peer_id, - "request_root" => ?root, - "error" => ?e, + ?peer_id, + request_root = ?root, + error = ?e, + "Error fetching blob for peer" ); } } } } debug!( - self.log, - "BlobsByRoot outgoing response processed"; - "peer" => %peer_id, - "request_root" => %requested_root, - "request_indices" => ?requested_indices, - "returned" => send_blob_count + %peer_id, + %requested_root, + ?requested_indices, + returned = send_blob_count, + "BlobsByRoot outgoing response processed" ); Ok(()) @@ -408,10 +403,11 @@ impl NetworkBeaconProcessor { Ok(None) => {} // no-op Err(e) => { // TODO(das): lower log level when feature is stabilized - error!(self.log, "Error getting data column"; - "block_root" => ?data_column_id.block_root, - "peer" => %peer_id, - "error" => ?e + error!( + block_root = ?data_column_id.block_root, + %peer_id, + error = ?e, + "Error getting data column" ); return Err((RpcErrorResponse::ServerError, "Error getting data column")); } @@ -419,11 +415,10 @@ impl NetworkBeaconProcessor { } debug!( - self.log, - "Received DataColumnsByRoot Request"; - "peer" => %peer_id, - "request" => ?request.group_by_ordered_block_root(), - "returned" => send_data_column_count + %peer_id, + request = ?request.group_by_ordered_block_root(), + returned = send_data_column_count, + "Received DataColumnsByRoot Request" ); Ok(()) @@ -463,10 +458,11 @@ impl NetworkBeaconProcessor { request_id: RequestId, req: LightClientUpdatesByRangeRequest, ) -> Result<(), (RpcErrorResponse, &'static str)> { - debug!(self.log, "Received LightClientUpdatesByRange Request"; - "peer_id" => %peer_id, - "count" => req.count, - "start_period" => req.start_period, + debug!( + %peer_id, + count = req.count, + start_period = req.start_period, + "Received LightClientUpdatesByRange Request" ); // Should not send more than max light client updates @@ -484,10 +480,11 @@ impl NetworkBeaconProcessor { { Ok(lc_updates) => lc_updates, Err(e) => { - error!(self.log, "Unable to obtain light client updates"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + peer = %peer_id, + error = ?e, + "Unable to obtain light client updates" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -506,22 +503,20 @@ impl NetworkBeaconProcessor { if lc_updates_sent < req.count as usize { debug!( - self.log, - "LightClientUpdatesByRange outgoing response processed"; - "peer" => %peer_id, - "info" => "Failed to return all requested light client updates. The peer may have requested data ahead of whats currently available", - "start_period" => req.start_period, - "requested" => req.count, - "returned" => lc_updates_sent + peer = %peer_id, + info = "Failed to return all requested light client updates. The peer may have requested data ahead of whats currently available", + start_period = req.start_period, + requested = req.count, + returned = lc_updates_sent, + "LightClientUpdatesByRange outgoing response processed" ); } else { debug!( - self.log, - "LightClientUpdatesByRange outgoing response processed"; - "peer" => %peer_id, - "start_period" => req.start_period, - "requested" => req.count, - "returned" => lc_updates_sent + peer = %peer_id, + start_period = req.start_period, + requested = req.count, + returned = lc_updates_sent, + "LightClientUpdatesByRange outgoing response processed" ); } @@ -549,10 +544,11 @@ impl NetworkBeaconProcessor { "Bootstrap not available".to_string(), )), Err(e) => { - error!(self.log, "Error getting LightClientBootstrap instance"; - "block_root" => ?request.root, - "peer" => %peer_id, - "error" => ?e + error!( + block_root = ?request.root, + %peer_id, + error = ?e, + "Error getting LightClientBootstrap instance" ); Err((RpcErrorResponse::ResourceUnavailable, format!("{:?}", e))) } @@ -653,10 +649,11 @@ impl NetworkBeaconProcessor { request_id: RequestId, req: BlocksByRangeRequest, ) -> Result<(), (RpcErrorResponse, &'static str)> { - debug!(self.log, "Received BlocksByRange Request"; - "peer_id" => %peer_id, - "count" => req.count(), - "start_slot" => req.start_slot(), + debug!( + %peer_id, + count = req.count(), + start_slot = %req.start_slot(), + "Received BlocksByRange Request" ); let forwards_block_root_iter = match self @@ -668,17 +665,19 @@ impl NetworkBeaconProcessor { slot, oldest_block_slot, }) => { - debug!(self.log, "Range request failed during backfill"; - "requested_slot" => slot, - "oldest_known_slot" => oldest_block_slot + debug!( + requested_slot = %slot, + oldest_known_slot = %oldest_block_slot, + "Range request failed during backfill" ); return Err((RpcErrorResponse::ResourceUnavailable, "Backfilling")); } Err(e) => { - error!(self.log, "Unable to obtain root iter"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Unable to obtain root iter" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -706,10 +705,11 @@ impl NetworkBeaconProcessor { let block_roots = match maybe_block_roots { Ok(block_roots) => block_roots, Err(e) => { - error!(self.log, "Error during iteration over blocks"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Error during iteration over blocks" ); return Err((RpcErrorResponse::ServerError, "Iteration error")); } @@ -726,24 +726,22 @@ impl NetworkBeaconProcessor { let log_results = |req: BlocksByRangeRequest, peer_id, blocks_sent| { if blocks_sent < (*req.count() as usize) { debug!( - self.log, - "BlocksByRange outgoing response processed"; - "peer" => %peer_id, - "msg" => "Failed to return all requested blocks", - "start_slot" => req.start_slot(), - "current_slot" => current_slot, - "requested" => req.count(), - "returned" => blocks_sent + %peer_id, + msg = "Failed to return all requested blocks", + start_slot = %req.start_slot(), + %current_slot, + requested = req.count(), + returned = blocks_sent, + "BlocksByRange outgoing response processed" ); } else { debug!( - self.log, - "BlocksByRange outgoing response processed"; - "peer" => %peer_id, - "start_slot" => req.start_slot(), - "current_slot" => current_slot, - "requested" => req.count(), - "returned" => blocks_sent + %peer_id, + start_slot = %req.start_slot(), + %current_slot, + requested = req.count(), + returned = blocks_sent, + "BlocksByRange outgoing response processed" ); } }; @@ -751,7 +749,7 @@ impl NetworkBeaconProcessor { let mut block_stream = match self.chain.get_blocks(block_roots) { Ok(block_stream) => block_stream, Err(e) => { - error!(self.log, "Error getting block stream"; "error" => ?e); + error!(error = ?e, "Error getting block stream"); return Err((RpcErrorResponse::ServerError, "Iterator error")); } }; @@ -777,21 +775,19 @@ impl NetworkBeaconProcessor { } Ok(None) => { error!( - self.log, - "Block in the chain is not in the store"; - "request" => ?req, - "peer" => %peer_id, - "request_root" => ?root + request = ?req, + %peer_id, + request_root = ?root, + "Block in the chain is not in the store" ); log_results(req, peer_id, blocks_sent); return Err((RpcErrorResponse::ServerError, "Database inconsistency")); } Err(BeaconChainError::BlockHashMissingFromExecutionLayer(_)) => { debug!( - self.log, - "Failed to fetch execution payload for blocks by range request"; - "block_root" => ?root, - "reason" => "execution layer not synced", + block_root = ?root, + reason = "execution layer not synced", + "Failed to fetch execution payload for blocks by range request" ); log_results(req, peer_id, blocks_sent); // send the stream terminator @@ -807,18 +803,16 @@ impl NetworkBeaconProcessor { if matches!(**boxed_error, execution_layer::Error::EngineError(_)) ) { warn!( - self.log, - "Error rebuilding payload for peer"; - "info" => "this may occur occasionally when the EE is busy", - "block_root" => ?root, - "error" => ?e, + info = "this may occur occasionally when the EE is busy", + block_root = ?root, + error = ?e, + "Error rebuilding payload for peer" ); } else { error!( - self.log, - "Error fetching block for peer"; - "block_root" => ?root, - "error" => ?e + block_root = ?root, + error = ?e, + "Error fetching block for peer" ); } log_results(req, peer_id, blocks_sent); @@ -866,10 +860,11 @@ impl NetworkBeaconProcessor { request_id: RequestId, req: BlobsByRangeRequest, ) -> Result<(), (RpcErrorResponse, &'static str)> { - debug!(self.log, "Received BlobsByRange Request"; - "peer_id" => %peer_id, - "count" => req.count, - "start_slot" => req.start_slot, + debug!( + ?peer_id, + count = req.count, + start_slot = req.start_slot, + "Received BlobsByRange Request" ); let request_start_slot = Slot::from(req.start_slot); @@ -877,7 +872,7 @@ impl NetworkBeaconProcessor { let data_availability_boundary_slot = match self.chain.data_availability_boundary() { Some(boundary) => boundary.start_slot(T::EthSpec::slots_per_epoch()), None => { - debug!(self.log, "Deneb fork is disabled"); + debug!("Deneb fork is disabled"); return Err((RpcErrorResponse::InvalidRequest, "Deneb fork is disabled")); } }; @@ -890,11 +885,10 @@ impl NetworkBeaconProcessor { .unwrap_or(data_availability_boundary_slot); if request_start_slot < oldest_blob_slot { debug!( - self.log, - "Range request start slot is older than data availability boundary."; - "requested_slot" => request_start_slot, - "oldest_blob_slot" => oldest_blob_slot, - "data_availability_boundary" => data_availability_boundary_slot + %request_start_slot, + %oldest_blob_slot, + %data_availability_boundary_slot, + "Range request start slot is older than data availability boundary." ); return if data_availability_boundary_slot < oldest_blob_slot { @@ -917,17 +911,19 @@ impl NetworkBeaconProcessor { slot, oldest_block_slot, }) => { - debug!(self.log, "Range request failed during backfill"; - "requested_slot" => slot, - "oldest_known_slot" => oldest_block_slot + debug!( + requested_slot = %slot, + oldest_known_slot = %oldest_block_slot, + "Range request failed during backfill" ); return Err((RpcErrorResponse::ResourceUnavailable, "Backfilling")); } Err(e) => { - error!(self.log, "Unable to obtain root iter"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Unable to obtain root iter" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -961,10 +957,11 @@ impl NetworkBeaconProcessor { let block_roots = match maybe_block_roots { Ok(block_roots) => block_roots, Err(e) => { - error!(self.log, "Error during iteration over blocks"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Error during iteration over blocks" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -977,13 +974,12 @@ impl NetworkBeaconProcessor { let log_results = |peer_id, req: BlobsByRangeRequest, blobs_sent| { debug!( - self.log, - "BlobsByRange outgoing response processed"; - "peer" => %peer_id, - "start_slot" => req.start_slot, - "current_slot" => current_slot, - "requested" => req.count, - "returned" => blobs_sent + %peer_id, + start_slot = req.start_slot, + %current_slot, + requested = req.count, + returned = blobs_sent, + "BlobsByRange outgoing response processed" ); }; @@ -1006,12 +1002,11 @@ impl NetworkBeaconProcessor { } Err(e) => { error!( - self.log, - "Error fetching blobs block root"; - "request" => ?req, - "peer" => %peer_id, - "block_root" => ?root, - "error" => ?e + request = ?req, + %peer_id, + block_root = ?root, + error = ?e, + "Error fetching blobs block root" ); log_results(peer_id, req, blobs_sent); @@ -1061,10 +1056,11 @@ impl NetworkBeaconProcessor { request_id: RequestId, req: DataColumnsByRangeRequest, ) -> Result<(), (RpcErrorResponse, &'static str)> { - debug!(self.log, "Received DataColumnsByRange Request"; - "peer_id" => %peer_id, - "count" => req.count, - "start_slot" => req.start_slot, + debug!( + %peer_id, + count = req.count, + start_slot = req.start_slot, + "Received DataColumnsByRange Request" ); // Should not send more than max request data columns @@ -1080,7 +1076,7 @@ impl NetworkBeaconProcessor { let data_availability_boundary_slot = match self.chain.data_availability_boundary() { Some(boundary) => boundary.start_slot(T::EthSpec::slots_per_epoch()), None => { - debug!(self.log, "Deneb fork is disabled"); + debug!("Deneb fork is disabled"); return Err((RpcErrorResponse::InvalidRequest, "Deneb fork is disabled")); } }; @@ -1094,11 +1090,10 @@ impl NetworkBeaconProcessor { if request_start_slot < oldest_data_column_slot { debug!( - self.log, - "Range request start slot is older than data availability boundary."; - "requested_slot" => request_start_slot, - "oldest_data_column_slot" => oldest_data_column_slot, - "data_availability_boundary" => data_availability_boundary_slot + %request_start_slot, + %oldest_data_column_slot, + %data_availability_boundary_slot, + "Range request start slot is older than data availability boundary." ); return if data_availability_boundary_slot < oldest_data_column_slot { @@ -1121,17 +1116,19 @@ impl NetworkBeaconProcessor { slot, oldest_block_slot, }) => { - debug!(self.log, "Range request failed during backfill"; - "requested_slot" => slot, - "oldest_known_slot" => oldest_block_slot + debug!( + requested_slot = %slot, + oldest_known_slot = %oldest_block_slot, + "Range request failed during backfill" ); return Err((RpcErrorResponse::ResourceUnavailable, "Backfilling")); } Err(e) => { - error!(self.log, "Unable to obtain root iter"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Unable to obtain root iter" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -1165,10 +1162,11 @@ impl NetworkBeaconProcessor { let block_roots = match maybe_block_roots { Ok(block_roots) => block_roots, Err(e) => { - error!(self.log, "Error during iteration over blocks"; - "request" => ?req, - "peer" => %peer_id, - "error" => ?e + error!( + request = ?req, + %peer_id, + error = ?e, + "Error during iteration over blocks" ); return Err((RpcErrorResponse::ServerError, "Database error")); } @@ -1195,12 +1193,11 @@ impl NetworkBeaconProcessor { Ok(None) => {} // no-op Err(e) => { error!( - self.log, - "Error fetching data columns block root"; - "request" => ?req, - "peer" => %peer_id, - "block_root" => ?root, - "error" => ?e + request = ?req, + %peer_id, + block_root = ?root, + error = ?e, + "Error fetching data columns block root" ); return Err(( RpcErrorResponse::ServerError, @@ -1217,13 +1214,12 @@ impl NetworkBeaconProcessor { .unwrap_or_else(|_| self.chain.slot_clock.genesis_slot()); debug!( - self.log, - "DataColumnsByRange Response processed"; - "peer" => %peer_id, - "start_slot" => req.start_slot, - "current_slot" => current_slot, - "requested" => req.count, - "returned" => data_columns_sent + %peer_id, + start_slot = req.start_slot, + %current_slot, + requested = req.count, + returned = data_columns_sent, + "DataColumnsByRange Response processed" ); Ok(()) diff --git a/beacon_node/network/src/network_beacon_processor/sync_methods.rs b/beacon_node/network/src/network_beacon_processor/sync_methods.rs index 338f2bc4c8..65097da0c6 100644 --- a/beacon_node/network/src/network_beacon_processor/sync_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/sync_methods.rs @@ -18,11 +18,11 @@ use beacon_processor::{ AsyncFn, BlockingFn, DuplicateCache, }; use lighthouse_network::PeerAction; -use slog::{debug, error, info, warn}; use std::sync::Arc; use std::time::Duration; use store::KzgCommitment; use tokio::sync::mpsc; +use tracing::{debug, error, info, warn}; use types::beacon_block_body::format_kzg_commitments; use types::blob_sidecar::FixedBlobSidecarList; use types::{BlockImportSource, DataColumnSidecar, DataColumnSidecarList, Epoch, Hash256}; @@ -112,11 +112,10 @@ impl NetworkBeaconProcessor { // Check if the block is already being imported through another source let Some(handle) = duplicate_cache.check_and_insert(block_root) else { debug!( - self.log, - "Gossip block is being processed"; - "action" => "sending rpc block to reprocessing queue", - "block_root" => %block_root, - "process_type" => ?process_type, + action = "sending rpc block to reprocessing queue", + %block_root, + ?process_type, + "Gossip block is being processed" ); // Send message to work reprocess queue to retry the block @@ -133,7 +132,7 @@ impl NetworkBeaconProcessor { }); if reprocess_tx.try_send(reprocess_msg).is_err() { - error!(self.log, "Failed to inform block import"; "source" => "rpc", "block_root" => %block_root) + error!(source = "rpc", %block_root,"Failed to inform block import") }; return; }; @@ -144,13 +143,12 @@ impl NetworkBeaconProcessor { let commitments_formatted = block.as_block().commitments_formatted(); debug!( - self.log, - "Processing RPC block"; - "block_root" => ?block_root, - "proposer" => block.message().proposer_index(), - "slot" => block.slot(), - "commitments" => commitments_formatted, - "process_type" => ?process_type, + ?block_root, + proposer = block.message().proposer_index(), + slot = %block.slot(), + commitments_formatted, + ?process_type, + "Processing RPC block" ); let signed_beacon_block = block.block_cloned(); @@ -168,15 +166,22 @@ impl NetworkBeaconProcessor { // RPC block imported, regardless of process type match result.as_ref() { Ok(AvailabilityProcessingStatus::Imported(hash)) => { - info!(self.log, "New RPC block received"; "slot" => slot, "hash" => %hash); - + info!( + %slot, + %hash, + "New RPC block received", + ); // Trigger processing for work referencing this block. let reprocess_msg = ReprocessQueueMessage::BlockImported { block_root: *hash, parent_root, }; if reprocess_tx.try_send(reprocess_msg).is_err() { - error!(self.log, "Failed to inform block import"; "source" => "rpc", "block_root" => %hash) + error!( + source = "rpc", + block_root = %hash, + "Failed to inform block import" + ); }; self.chain.block_times_cache.write().set_time_observed( *hash, @@ -265,12 +270,11 @@ impl NetworkBeaconProcessor { let commitments = format_kzg_commitments(&commitments); debug!( - self.log, - "RPC blobs received"; - "indices" => ?indices, - "block_root" => %block_root, - "slot" => %slot, - "commitments" => commitments, + ?indices, + %block_root, + %slot, + commitments, + "RPC blobs received" ); if let Ok(current_slot) = self.chain.slot() { @@ -290,37 +294,33 @@ impl NetworkBeaconProcessor { match &result { Ok(AvailabilityProcessingStatus::Imported(hash)) => { debug!( - self.log, - "Block components retrieved"; - "result" => "imported block and blobs", - "slot" => %slot, - "block_hash" => %hash, + result = "imported block and blobs", + %slot, + block_hash = %hash, + "Block components retrieved" ); self.chain.recompute_head_at_current_slot().await; } Ok(AvailabilityProcessingStatus::MissingComponents(_, _)) => { debug!( - self.log, - "Missing components over rpc"; - "block_hash" => %block_root, - "slot" => %slot, + block_hash = %block_root, + %slot, + "Missing components over rpc" ); } Err(BlockError::DuplicateFullyImported(_)) => { debug!( - self.log, - "Blobs have already been imported"; - "block_hash" => %block_root, - "slot" => %slot, + block_hash = %block_root, + %slot, + "Blobs have already been imported" ); } Err(e) => { warn!( - self.log, - "Error when importing rpc blobs"; - "error" => ?e, - "block_hash" => %block_root, - "slot" => %slot, + error = ?e, + block_hash = %block_root, + %slot, + "Error when importing rpc blobs" ); } } @@ -336,9 +336,30 @@ impl NetworkBeaconProcessor { self: Arc>, block_root: Hash256, custody_columns: DataColumnSidecarList, - _seen_timestamp: Duration, + seen_timestamp: Duration, process_type: BlockProcessType, ) { + // custody_columns must always have at least one element + let Some(slot) = custody_columns.first().map(|d| d.slot()) else { + return; + }; + + if let Ok(current_slot) = self.chain.slot() { + if current_slot == slot { + let delay = get_slot_delay_ms(seen_timestamp, slot, &self.chain.slot_clock); + metrics::observe_duration(&metrics::BEACON_BLOB_RPC_SLOT_START_DELAY_TIME, delay); + } + } + + let mut indices = custody_columns.iter().map(|d| d.index).collect::>(); + indices.sort_unstable(); + debug!( + ?indices, + %block_root, + %slot, + "RPC custody data columns received" + ); + let mut result = self .chain .process_rpc_custody_columns(custody_columns) @@ -349,18 +370,16 @@ impl NetworkBeaconProcessor { Ok(availability) => match availability { AvailabilityProcessingStatus::Imported(hash) => { debug!( - self.log, - "Block components retrieved"; - "result" => "imported block and custody columns", - "block_hash" => %hash, + result = "imported block and custody columns", + block_hash = %hash, + "Block components retrieved" ); self.chain.recompute_head_at_current_slot().await; } AvailabilityProcessingStatus::MissingComponents(_, _) => { debug!( - self.log, - "Missing components over rpc"; - "block_hash" => %block_root, + block_hash = %block_root, + "Missing components over rpc" ); // Attempt reconstruction here before notifying sync, to avoid sending out more requests // that we may no longer need. @@ -373,17 +392,15 @@ impl NetworkBeaconProcessor { }, Err(BlockError::DuplicateFullyImported(_)) => { debug!( - self.log, - "Custody columns have already been imported"; - "block_hash" => %block_root, + block_hash = %block_root, + "Custody columns have already been imported" ); } Err(e) => { warn!( - self.log, - "Error when importing rpc custody columns"; - "error" => ?e, - "block_hash" => %block_root, + error = ?e, + block_hash = %block_root, + "Error when importing rpc custody columns" ); } } @@ -433,27 +450,29 @@ impl NetworkBeaconProcessor { .await { (imported_blocks, Ok(_)) => { - debug!(self.log, "Batch processed"; - "batch_epoch" => epoch, - "first_block_slot" => start_slot, - "chain" => chain_id, - "last_block_slot" => end_slot, - "processed_blocks" => sent_blocks, - "service"=> "sync"); + debug!( + batch_epoch = %epoch, + first_block_slot = start_slot, + chain = chain_id, + last_block_slot = end_slot, + processed_blocks = sent_blocks, + service= "sync", + "Batch processed"); BatchProcessResult::Success { sent_blocks, imported_blocks, } } (imported_blocks, Err(e)) => { - debug!(self.log, "Batch processing failed"; - "batch_epoch" => epoch, - "first_block_slot" => start_slot, - "chain" => chain_id, - "last_block_slot" => end_slot, - "imported_blocks" => imported_blocks, - "error" => %e.message, - "service" => "sync"); + debug!( + batch_epoch = %epoch, + first_block_slot = start_slot, + chain = chain_id, + last_block_slot = end_slot, + imported_blocks, + error = %e.message, + service = "sync", + "Batch processing failed"); match e.peer_action { Some(penalty) => BatchProcessResult::FaultyFailure { imported_blocks, @@ -480,28 +499,31 @@ impl NetworkBeaconProcessor { match self.process_backfill_blocks(downloaded_blocks) { (imported_blocks, Ok(_)) => { - debug!(self.log, "Backfill batch processed"; - "batch_epoch" => epoch, - "first_block_slot" => start_slot, - "keep_execution_payload" => !self.chain.store.get_config().prune_payloads, - "last_block_slot" => end_slot, - "processed_blocks" => sent_blocks, - "processed_blobs" => n_blobs, - "processed_data_columns" => n_data_columns, - "service"=> "sync"); + debug!( + batch_epoch = %epoch, + first_block_slot = start_slot, + keep_execution_payload = !self.chain.store.get_config().prune_payloads, + last_block_slot = end_slot, + processed_blocks = sent_blocks, + processed_blobs = n_blobs, + processed_data_columns = n_data_columns, + service= "sync", + "Backfill batch processed"); BatchProcessResult::Success { sent_blocks, imported_blocks, } } (_, Err(e)) => { - debug!(self.log, "Backfill batch processing failed"; - "batch_epoch" => epoch, - "first_block_slot" => start_slot, - "last_block_slot" => end_slot, - "processed_blobs" => n_blobs, - "error" => %e.message, - "service" => "sync"); + debug!( + batch_epoch = %epoch, + first_block_slot = start_slot, + last_block_slot = end_slot, + processed_blobs = n_blobs, + error = %e.message, + service = "sync", + "Backfill batch processing failed" + ); match e.peer_action { Some(penalty) => BatchProcessResult::FaultyFailure { imported_blocks: 0, @@ -630,11 +652,10 @@ impl NetworkBeaconProcessor { expected_block_root, } => { debug!( - self.log, - "Backfill batch processing error"; - "error" => "mismatched_block_root", - "block_root" => ?block_root, - "expected_root" => ?expected_block_root + error = "mismatched_block_root", + ?block_root, + expected_root = ?expected_block_root, + "Backfill batch processing error" ); // The peer is faulty if they send blocks with bad roots. Some(PeerAction::LowToleranceError) @@ -642,33 +663,30 @@ impl NetworkBeaconProcessor { HistoricalBlockError::InvalidSignature | HistoricalBlockError::SignatureSet(_) => { warn!( - self.log, - "Backfill batch processing error"; - "error" => ?e + error = ?e, + "Backfill batch processing error" ); // The peer is faulty if they bad signatures. Some(PeerAction::LowToleranceError) } HistoricalBlockError::ValidatorPubkeyCacheTimeout => { warn!( - self.log, - "Backfill batch processing error"; - "error" => "pubkey_cache_timeout" + error = "pubkey_cache_timeout", + "Backfill batch processing error" ); // This is an internal error, do not penalize the peer. None } HistoricalBlockError::IndexOutOfBounds => { error!( - self.log, - "Backfill batch OOB error"; - "error" => ?e, + error = ?e, + "Backfill batch OOB error" ); // This should never occur, don't penalize the peer. None } HistoricalBlockError::StoreError(e) => { - warn!(self.log, "Backfill batch processing error"; "error" => ?e); + warn!(error = ?e, "Backfill batch processing error"); // This is an internal error, don't penalize the peer. None } // @@ -711,19 +729,19 @@ impl NetworkBeaconProcessor { if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot { // The block is too far in the future, drop it. warn!( - self.log, "Block is ahead of our slot clock"; - "msg" => "block for future slot rejected, check your time", - "present_slot" => present_slot, - "block_slot" => block_slot, - "FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE, + msg = "block for future slot rejected, check your time", + %present_slot, + %block_slot, + FUTURE_SLOT_TOLERANCE, + "Block is ahead of our slot clock" ); } else { // The block is in the future, but not too far. debug!( - self.log, "Block is slightly ahead of our slot clock. Ignoring."; - "present_slot" => present_slot, - "block_slot" => block_slot, - "FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE, + %present_slot, + %block_slot, + FUTURE_SLOT_TOLERANCE, + "Block is slightly ahead of our slot clock. Ignoring." ); } @@ -737,18 +755,18 @@ impl NetworkBeaconProcessor { }) } BlockError::WouldRevertFinalizedSlot { .. } => { - debug!(self.log, "Finalized or earlier block processed";); + debug!("Finalized or earlier block processed"); Ok(()) } BlockError::GenesisBlock => { - debug!(self.log, "Genesis block was processed"); + debug!("Genesis block was processed"); Ok(()) } BlockError::BeaconChainError(e) => { warn!( - self.log, "BlockProcessingFailure"; - "msg" => "unexpected condition in processing block.", - "outcome" => ?e, + msg = "unexpected condition in processing block.", + outcome = ?e, + "BlockProcessingFailure" ); Err(ChainSegmentFailed { @@ -761,10 +779,10 @@ impl NetworkBeaconProcessor { if !epe.penalize_peer() { // These errors indicate an issue with the EL and not the `ChainSegment`. // Pause the syncing while the EL recovers - debug!(self.log, - "Execution layer verification failed"; - "outcome" => "pausing sync", - "err" => ?err + debug!( + outcome = "pausing sync", + ?err, + "Execution layer verification failed" ); Err(ChainSegmentFailed { message: format!("Execution layer offline. Reason: {:?}", err), @@ -772,9 +790,9 @@ impl NetworkBeaconProcessor { peer_action: None, }) } else { - debug!(self.log, - "Invalid execution payload"; - "error" => ?err + debug!( + error = ?err, + "Invalid execution payload" ); Err(ChainSegmentFailed { message: format!( @@ -787,10 +805,9 @@ impl NetworkBeaconProcessor { } ref err @ BlockError::ParentExecutionPayloadInvalid { ref parent_root } => { warn!( - self.log, - "Failed to sync chain built on invalid parent"; - "parent_root" => ?parent_root, - "advice" => "check execution node for corruption then restart it and Lighthouse", + ?parent_root, + advice = "check execution node for corruption then restart it and Lighthouse", + "Failed to sync chain built on invalid parent" ); Err(ChainSegmentFailed { message: format!("Peer sent invalid block. Reason: {err:?}"), @@ -802,9 +819,9 @@ impl NetworkBeaconProcessor { } other => { debug!( - self.log, "Invalid block received"; - "msg" => "peer sent invalid block", - "outcome" => %other, + msg = "peer sent invalid block", + outcome = %other, + "Invalid block received" ); Err(ChainSegmentFailed { diff --git a/beacon_node/network/src/network_beacon_processor/tests.rs b/beacon_node/network/src/network_beacon_processor/tests.rs index 8415ece638..69ba5c1dbd 100644 --- a/beacon_node/network/src/network_beacon_processor/tests.rs +++ b/beacon_node/network/src/network_beacon_processor/tests.rs @@ -182,8 +182,6 @@ impl TestRig { let (network_tx, _network_rx) = mpsc::unbounded_channel(); - let log = harness.logger().clone(); - let beacon_processor_config = BeaconProcessorConfig { enable_backfill_rate_limiting, ..Default::default() @@ -221,7 +219,6 @@ impl TestRig { meta_data, vec![], false, - &log, network_config, spec, )); @@ -241,7 +238,6 @@ impl TestRig { network_globals: network_globals.clone(), invalid_block_storage: InvalidBlockStorage::Disabled, executor: executor.clone(), - log: log.clone(), }; let network_beacon_processor = Arc::new(network_beacon_processor); @@ -250,7 +246,6 @@ impl TestRig { executor, current_workers: 0, config: beacon_processor_config, - log: log.clone(), } .spawn_manager( beacon_processor_rx, diff --git a/beacon_node/network/src/persisted_dht.rs b/beacon_node/network/src/persisted_dht.rs index 1e1420883e..9c112dba86 100644 --- a/beacon_node/network/src/persisted_dht.rs +++ b/beacon_node/network/src/persisted_dht.rs @@ -69,20 +69,17 @@ impl StoreItem for PersistedDht { #[cfg(test)] mod tests { use super::*; - use sloggers::{null::NullLoggerBuilder, Build}; use std::str::FromStr; use store::config::StoreConfig; use store::MemoryStore; use types::{ChainSpec, MinimalEthSpec}; #[test] fn test_persisted_dht() { - let log = NullLoggerBuilder.build().unwrap(); let store: HotColdDB< MinimalEthSpec, MemoryStore, MemoryStore, - > = HotColdDB::open_ephemeral(StoreConfig::default(), ChainSpec::minimal().into(), log) - .unwrap(); + > = HotColdDB::open_ephemeral(StoreConfig::default(), ChainSpec::minimal().into()).unwrap(); let enrs = vec![Enr::from_str("enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8").unwrap()]; store .put_item(&DHT_DB_KEY, &PersistedDht { enrs: enrs.clone() }) diff --git a/beacon_node/network/src/router.rs b/beacon_node/network/src/router.rs index 36e5c391e9..7376244501 100644 --- a/beacon_node/network/src/router.rs +++ b/beacon_node/network/src/router.rs @@ -21,13 +21,13 @@ use lighthouse_network::{ service::api_types::{AppRequestId, SyncRequestId}, MessageId, NetworkGlobals, PeerId, PeerRequestId, PubsubMessage, Response, }; +use logging::crit; use logging::TimeLatch; -use slog::{crit, debug, o, trace}; -use slog::{error, warn}; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; +use tracing::{debug, error, info_span, trace, warn, Instrument}; use types::{BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, SignedBeaconBlock}; /// Handles messages from the network and routes them to the appropriate service to be handled. @@ -42,8 +42,6 @@ pub struct Router { network: HandlerNetworkContext, /// A multi-threaded, non-blocking processor for applying messages to the beacon chain. network_beacon_processor: Arc>, - /// The `Router` logger. - log: slog::Logger, /// Provides de-bounce functionality for logging. logger_debounce: TimeLatch, } @@ -91,14 +89,11 @@ impl Router { beacon_processor_send: BeaconProcessorSend, beacon_processor_reprocess_tx: mpsc::Sender, fork_context: Arc, - log: slog::Logger, ) -> Result>, String> { - let message_handler_log = log.new(o!("service"=> "router")); - trace!(message_handler_log, "Service starting"); + trace!("Service starting"); let (handler_send, handler_recv) = mpsc::unbounded_channel(); - let sync_logger = log.new(o!("service"=> "sync")); // generate the message channel let (sync_send, sync_recv) = mpsc::unbounded_channel::>(); @@ -112,7 +107,6 @@ impl Router { network_globals: network_globals.clone(), invalid_block_storage, executor: executor.clone(), - log: log.clone(), }; let network_beacon_processor = Arc::new(network_beacon_processor); @@ -124,7 +118,6 @@ impl Router { network_beacon_processor.clone(), sync_recv, fork_context, - sync_logger, ); // generate the Message handler @@ -132,18 +125,18 @@ impl Router { network_globals, chain: beacon_chain, sync_send, - network: HandlerNetworkContext::new(network_send, log.clone()), + network: HandlerNetworkContext::new(network_send), network_beacon_processor, - log: message_handler_log, logger_debounce: TimeLatch::default(), }; // spawn handler task and move the message handler instance into the spawned thread executor.spawn( async move { - debug!(log, "Network message router started"); + debug!("Network message router started"); UnboundedReceiverStream::new(handler_recv) .for_each(move |msg| future::ready(handler.handle_message(msg))) + .instrument(info_span!("", service = "router")) .await; }, "router", @@ -201,7 +194,7 @@ impl Router { rpc_request: rpc::Request, ) { if !self.network_globals.peers.read().is_connected(&peer_id) { - debug!(self.log, "Dropping request of disconnected peer"; "peer_id" => %peer_id, "request" => ?rpc_request); + debug!( %peer_id, request = ?rpc_request, "Dropping request of disconnected peer"); return; } match rpc_request.r#type { @@ -336,7 +329,7 @@ impl Router { ) { match response { Response::Status(status_message) => { - debug!(self.log, "Received Status Response"; "peer_id" => %peer_id, &status_message); + debug!(%peer_id, ?status_message,"Received Status Response"); self.handle_beacon_processor_send_result( self.network_beacon_processor .send_status_message(peer_id, status_message), @@ -448,7 +441,7 @@ impl Router { ) } PubsubMessage::VoluntaryExit(exit) => { - debug!(self.log, "Received a voluntary exit"; "peer_id" => %peer_id); + debug!(%peer_id, "Received a voluntary exit"); self.handle_beacon_processor_send_result( self.network_beacon_processor .send_gossip_voluntary_exit(message_id, peer_id, exit), @@ -456,9 +449,8 @@ impl Router { } PubsubMessage::ProposerSlashing(proposer_slashing) => { debug!( - self.log, - "Received a proposer slashing"; - "peer_id" => %peer_id + %peer_id, + "Received a proposer slashing" ); self.handle_beacon_processor_send_result( self.network_beacon_processor.send_gossip_proposer_slashing( @@ -470,9 +462,8 @@ impl Router { } PubsubMessage::AttesterSlashing(attester_slashing) => { debug!( - self.log, - "Received a attester slashing"; - "peer_id" => %peer_id + %peer_id, + "Received a attester slashing" ); self.handle_beacon_processor_send_result( self.network_beacon_processor.send_gossip_attester_slashing( @@ -484,9 +475,8 @@ impl Router { } PubsubMessage::SignedContributionAndProof(contribution_and_proof) => { trace!( - self.log, - "Received sync committee aggregate"; - "peer_id" => %peer_id + %peer_id, + "Received sync committee aggregate" ); self.handle_beacon_processor_send_result( self.network_beacon_processor.send_gossip_sync_contribution( @@ -499,9 +489,8 @@ impl Router { } PubsubMessage::SyncCommitteeMessage(sync_committtee_msg) => { trace!( - self.log, - "Received sync committee signature"; - "peer_id" => %peer_id + %peer_id, + "Received sync committee signature" ); self.handle_beacon_processor_send_result( self.network_beacon_processor.send_gossip_sync_signature( @@ -515,9 +504,8 @@ impl Router { } PubsubMessage::LightClientFinalityUpdate(light_client_finality_update) => { trace!( - self.log, - "Received light client finality update"; - "peer_id" => %peer_id + %peer_id, + "Received light client finality update" ); self.handle_beacon_processor_send_result( self.network_beacon_processor @@ -531,9 +519,9 @@ impl Router { } PubsubMessage::LightClientOptimisticUpdate(light_client_optimistic_update) => { trace!( - self.log, - "Received light client optimistic update"; - "peer_id" => %peer_id + %peer_id, + "Received light client optimistic update" + ); self.handle_beacon_processor_send_result( self.network_beacon_processor @@ -559,7 +547,7 @@ impl Router { fn send_status(&mut self, peer_id: PeerId) { let status_message = status_message(&self.chain); - debug!(self.log, "Sending Status Request"; "peer" => %peer_id, &status_message); + debug!(%peer_id, ?status_message, "Sending Status Request"); self.network .send_processor_request(peer_id, RequestType::Status(status_message)); } @@ -567,9 +555,8 @@ impl Router { fn send_to_sync(&mut self, message: SyncMessage) { self.sync_send.send(message).unwrap_or_else(|e| { warn!( - self.log, - "Could not send message to the sync service"; - "error" => %e, + error = %e, + "Could not send message to the sync service" ) }); } @@ -598,7 +585,7 @@ impl Router { request_id: RequestId, status: StatusMessage, ) { - debug!(self.log, "Received Status Request"; "peer_id" => %peer_id, &status); + debug!(%peer_id, ?status, "Received Status Request"); // Say status back. self.network.send_response( @@ -626,20 +613,20 @@ impl Router { AppRequestId::Sync(sync_id) => match sync_id { id @ SyncRequestId::BlocksByRange { .. } => id, other => { - crit!(self.log, "BlocksByRange response on incorrect request"; "request" => ?other); + crit!(request = ?other, "BlocksByRange response on incorrect request"); return; } }, AppRequestId::Router => { - crit!(self.log, "All BBRange requests belong to sync"; "peer_id" => %peer_id); + crit!(%peer_id, "All BBRange requests belong to sync"); return; } }; trace!( - self.log, - "Received BlocksByRange Response"; - "peer" => %peer_id, + %peer_id, + "Received BlocksByRange Response" + ); self.send_to_sync(SyncMessage::RpcBlock { @@ -657,9 +644,8 @@ impl Router { blob_sidecar: Option>>, ) { trace!( - self.log, - "Received BlobsByRange Response"; - "peer" => %peer_id, + %peer_id, + "Received BlobsByRange Response" ); if let AppRequestId::Sync(id) = request_id { @@ -670,10 +656,7 @@ impl Router { seen_timestamp: timestamp_now(), }); } else { - crit!( - self.log, - "All blobs by range responses should belong to sync" - ); + crit!("All blobs by range responses should belong to sync"); } } @@ -688,20 +671,19 @@ impl Router { AppRequestId::Sync(sync_id) => match sync_id { id @ SyncRequestId::SingleBlock { .. } => id, other => { - crit!(self.log, "BlocksByRoot response on incorrect request"; "request" => ?other); + crit!(request = ?other, "BlocksByRoot response on incorrect request"); return; } }, AppRequestId::Router => { - crit!(self.log, "All BBRoot requests belong to sync"; "peer_id" => %peer_id); + crit!(%peer_id, "All BBRoot requests belong to sync"); return; } }; trace!( - self.log, - "Received BlocksByRoot Response"; - "peer" => %peer_id, + %peer_id, + "Received BlocksByRoot Response" ); self.send_to_sync(SyncMessage::RpcBlock { peer_id, @@ -722,20 +704,19 @@ impl Router { AppRequestId::Sync(sync_id) => match sync_id { id @ SyncRequestId::SingleBlob { .. } => id, other => { - crit!(self.log, "BlobsByRoot response on incorrect request"; "request" => ?other); + crit!(request = ?other, "BlobsByRoot response on incorrect request"); return; } }, AppRequestId::Router => { - crit!(self.log, "All BlobsByRoot requests belong to sync"; "peer_id" => %peer_id); + crit!(%peer_id, "All BlobsByRoot requests belong to sync"); return; } }; trace!( - self.log, - "Received BlobsByRoot Response"; - "peer" => %peer_id, + %peer_id, + "Received BlobsByRoot Response" ); self.send_to_sync(SyncMessage::RpcBlob { request_id, @@ -756,20 +737,19 @@ impl Router { AppRequestId::Sync(sync_id) => match sync_id { id @ SyncRequestId::DataColumnsByRoot { .. } => id, other => { - crit!(self.log, "DataColumnsByRoot response on incorrect request"; "request" => ?other); + crit!(request = ?other, "DataColumnsByRoot response on incorrect request"); return; } }, AppRequestId::Router => { - crit!(self.log, "All DataColumnsByRoot requests belong to sync"; "peer_id" => %peer_id); + crit!(%peer_id, "All DataColumnsByRoot requests belong to sync"); return; } }; trace!( - self.log, - "Received DataColumnsByRoot Response"; - "peer" => %peer_id, + %peer_id, + "Received DataColumnsByRoot Response" ); self.send_to_sync(SyncMessage::RpcDataColumn { request_id, @@ -786,9 +766,8 @@ impl Router { data_column: Option>>, ) { trace!( - self.log, - "Received DataColumnsByRange Response"; - "peer" => %peer_id, + %peer_id, + "Received DataColumnsByRange Response" ); if let AppRequestId::Sync(id) = request_id { @@ -799,10 +778,7 @@ impl Router { seen_timestamp: timestamp_now(), }); } else { - crit!( - self.log, - "All data columns by range responses should belong to sync" - ); + crit!("All data columns by range responses should belong to sync"); } } @@ -818,8 +794,7 @@ impl Router { }; if self.logger_debounce.elapsed() { - error!(&self.log, "Unable to send message to the beacon processor"; - "error" => %e, "type" => work_type) + error!(error = %e, work_type, "Unable to send message to the beacon processor") } } } @@ -831,20 +806,18 @@ impl Router { pub struct HandlerNetworkContext { /// The network channel to relay messages to the Network service. network_send: mpsc::UnboundedSender>, - /// Logger for the `NetworkContext`. - log: slog::Logger, } impl HandlerNetworkContext { - pub fn new(network_send: mpsc::UnboundedSender>, log: slog::Logger) -> Self { - Self { network_send, log } + pub fn new(network_send: mpsc::UnboundedSender>) -> Self { + Self { network_send } } /// Sends a message to the network task. fn inform_network(&mut self, msg: NetworkMessage) { - self.network_send.send(msg).unwrap_or_else( - |e| warn!(self.log, "Could not send message to the network service"; "error" => %e), - ) + self.network_send + .send(msg) + .unwrap_or_else(|e| warn!(error = %e,"Could not send message to the network service")) } /// Sends a request to the network task. diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index 1b2a681c64..d25e8509a4 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -10,7 +10,6 @@ use beacon_processor::{work_reprocessing_queue::ReprocessQueueMessage, BeaconPro use futures::channel::mpsc::Sender; use futures::future::OptionFuture; use futures::prelude::*; -use futures::StreamExt; use lighthouse_network::rpc::{RequestId, RequestType}; use lighthouse_network::service::Network; use lighthouse_network::types::GossipKind; @@ -24,7 +23,7 @@ use lighthouse_network::{ types::{core_topics_to_subscribe, GossipEncoding, GossipTopic}, MessageId, NetworkEvent, NetworkGlobals, PeerId, }; -use slog::{crit, debug, error, info, o, trace, warn}; +use logging::crit; use std::collections::BTreeSet; use std::{collections::HashSet, pin::Pin, sync::Arc, time::Duration}; use store::HotColdDB; @@ -32,6 +31,7 @@ use strum::IntoStaticStr; use task_executor::ShutdownReason; use tokio::sync::mpsc; use tokio::time::Sleep; +use tracing::{debug, error, info, info_span, trace, warn, Instrument}; use types::{ ChainSpec, EthSpec, ForkContext, Slot, SubnetId, SyncCommitteeSubscription, SyncSubnetId, Unsigned, ValidatorSubscription, @@ -181,8 +181,6 @@ pub struct NetworkService { next_fork_subscriptions: Pin>>, /// A delay that expires when we need to unsubscribe from old fork topics. next_unsubscribe: Pin>>, - /// Subscribe to all the subnets once synced. - subscribe_all_subnets: bool, /// Shutdown beacon node after sync is complete. shutdown_after_sync: bool, /// Whether metrics are enabled or not. @@ -191,11 +189,8 @@ pub struct NetworkService { metrics_update: tokio::time::Interval, /// gossipsub_parameter_update timer gossipsub_parameter_update: tokio::time::Interval, - /// enable_light_client_server indicator - enable_light_client_server: bool, - /// The logger for the network service. + /// Provides fork specific info. fork_context: Arc, - log: slog::Logger, } impl NetworkService { @@ -214,30 +209,23 @@ impl NetworkService { ), String, > { - let network_log = executor.log().clone(); // build the channels for external comms let (network_senders, network_receivers) = NetworkSenders::new(); #[cfg(feature = "disable-backfill")] - warn!( - network_log, - "Backfill is disabled. DO NOT RUN IN PRODUCTION" - ); + warn!("Backfill is disabled. DO NOT RUN IN PRODUCTION"); if let (true, false, Some(v4)) = ( config.upnp_enabled, config.disable_discovery, config.listen_addrs().v4(), ) { - let nw = network_log.clone(); let v4 = v4.clone(); executor.spawn( async move { - info!(nw, "UPnP Attempting to initialise routes"); - if let Err(e) = - nat::construct_upnp_mappings(v4.addr, v4.disc_port, nw.clone()).await - { - info!(nw, "Could not UPnP map Discovery port"; "error" => %e); + info!("UPnP Attempting to initialise routes"); + if let Err(e) = nat::construct_upnp_mappings(v4.addr, v4.disc_port).await { + info!(error = %e, "Could not UPnP map Discovery port"); } }, "UPnP", @@ -266,7 +254,7 @@ impl NetworkService { &beacon_chain.spec, )); - debug!(network_log, "Current fork"; "fork_name" => ?fork_context.current_fork()); + debug!(fork_name = ?fork_context.current_fork(), "Current fork"); // construct the libp2p service context let service_context = Context { @@ -278,15 +266,14 @@ impl NetworkService { }; // launch libp2p service - let (mut libp2p, network_globals) = - Network::new(executor.clone(), service_context, &network_log).await?; + let (mut libp2p, network_globals) = Network::new(executor.clone(), service_context).await?; // Repopulate the DHT with stored ENR's if discovery is not disabled. if !config.disable_discovery { let enrs_to_load = load_dht::(store.clone()); debug!( - network_log, - "Loading peers into the routing table"; "peers" => enrs_to_load.len() + peers = enrs_to_load.len(), + "Loading peers into the routing table" ); for enr in enrs_to_load { libp2p.add_enr(enr.clone()); @@ -311,7 +298,6 @@ impl NetworkService { beacon_processor_send, beacon_processor_reprocess_tx, fork_context.clone(), - network_log.clone(), )?; // attestation and sync committee subnet service @@ -319,7 +305,6 @@ impl NetworkService { beacon_chain.clone(), network_globals.local_enr().node_id(), &config, - &network_log, ); // create a timer for updating network metrics @@ -334,7 +319,6 @@ impl NetworkService { } = network_receivers; // create the network service and spawn the task - let network_log = network_log.new(o!("service" => "network")); let network_service = NetworkService { beacon_chain, libp2p, @@ -347,14 +331,11 @@ impl NetworkService { next_fork_update, next_fork_subscriptions, next_unsubscribe, - subscribe_all_subnets: config.subscribe_all_subnets, shutdown_after_sync: config.shutdown_after_sync, metrics_enabled: config.metrics_enabled, metrics_update, gossipsub_parameter_update, fork_context, - log: network_log, - enable_light_client_server: config.enable_light_client_server, }; Ok((network_service, network_globals, network_senders)) @@ -423,7 +404,7 @@ impl NetworkService { fn send_to_router(&mut self, msg: RouterMessage) { if let Err(mpsc::error::SendError(msg)) = self.router_send.send(msg) { - debug!(self.log, "Failed to send msg to router"; "msg" => ?msg); + debug!(?msg, "Failed to send msg to router"); } } @@ -462,7 +443,7 @@ impl NetworkService { Some(_) = &mut self.next_unsubscribe => { let new_enr_fork_id = self.beacon_chain.enr_fork_id(); self.libp2p.unsubscribe_from_fork_topics_except(new_enr_fork_id.fork_digest); - info!(self.log, "Unsubscribed from old fork topics"); + info!("Unsubscribed from old fork topics"); self.next_unsubscribe = Box::pin(None.into()); } @@ -470,17 +451,17 @@ impl NetworkService { if let Some((fork_name, _)) = self.beacon_chain.duration_to_next_fork() { let fork_version = self.beacon_chain.spec.fork_version_for_name(fork_name); let fork_digest = ChainSpec::compute_fork_digest(fork_version, self.beacon_chain.genesis_validators_root); - info!(self.log, "Subscribing to new fork topics"); + info!("Subscribing to new fork topics"); self.libp2p.subscribe_new_fork_topics(fork_name, fork_digest); self.next_fork_subscriptions = Box::pin(None.into()); } else { - error!(self.log, "Fork subscription scheduled but no fork scheduled"); + error!( "Fork subscription scheduled but no fork scheduled"); } } } } - }; + }.instrument(info_span!("", service = "network")); executor.spawn(service_fut, "network"); } @@ -594,9 +575,8 @@ impl NetworkService { .await .map_err(|e| { warn!( - self.log, - "failed to send a shutdown signal"; - "error" => %e + error = %e, + "failed to send a shutdown signal" ) }); } @@ -651,10 +631,9 @@ impl NetworkService { message_id, validation_result, } => { - trace!(self.log, "Propagating gossipsub message"; - "propagation_peer" => ?propagation_source, - "message_id" => %message_id, - "validation_result" => ?validation_result + trace!( propagation_peer = ?propagation_source, + %message_id, + ?validation_result, "Propagating gossipsub message" ); self.libp2p.report_message_validation_result( &propagation_source, @@ -670,10 +649,9 @@ impl NetworkService { } } debug!( - self.log, - "Sending pubsub messages"; - "count" => messages.len(), - "topics" => ?topic_kinds + count = messages.len(), + topics = ?topic_kinds, + "Sending pubsub messages" ); self.libp2p.publish(messages); } @@ -702,9 +680,8 @@ impl NetworkService { .await { warn!( - self.log, - "failed to send a shutdown signal"; - "error" => %e + error = %e, + "failed to send a shutdown signal" ) } return; @@ -713,8 +690,8 @@ impl NetworkService { let mut subscribed_topics: Vec = vec![]; for topic_kind in core_topics_to_subscribe::( self.fork_context.current_fork(), - &self.fork_context.spec, &self.network_globals.as_topic_config(), + &self.fork_context.spec, ) { for fork_digest in self.required_gossip_fork_digests() { let topic = GossipTopic::new( @@ -725,70 +702,30 @@ impl NetworkService { if self.libp2p.subscribe(topic.clone()) { subscribed_topics.push(topic); } else { - warn!(self.log, "Could not subscribe to topic"; "topic" => %topic); - } - } - } - - if self.enable_light_client_server { - for light_client_topic_kind in - lighthouse_network::types::LIGHT_CLIENT_GOSSIP_TOPICS.iter() - { - for fork_digest in self.required_gossip_fork_digests() { - let light_client_topic = GossipTopic::new( - light_client_topic_kind.clone(), - GossipEncoding::default(), - fork_digest, - ); - if self.libp2p.subscribe(light_client_topic.clone()) { - subscribed_topics.push(light_client_topic); - } else { - warn!(self.log, "Could not subscribe to topic"; "topic" => %light_client_topic); - } + warn!(%topic, "Could not subscribe to topic"); } } } // If we are to subscribe to all subnets we do it here - if self.subscribe_all_subnets { + if self.network_globals.config.subscribe_all_subnets { for subnet_id in 0..<::EthSpec as EthSpec>::SubnetBitfieldLength::to_u64() { let subnet = Subnet::Attestation(SubnetId::new(subnet_id)); // Update the ENR bitfield self.libp2p.update_enr_subnet(subnet, true); - for fork_digest in self.required_gossip_fork_digests() { - let topic = GossipTopic::new(subnet.into(), GossipEncoding::default(), fork_digest); - if self.libp2p.subscribe(topic.clone()) { - subscribed_topics.push(topic); - } else { - warn!(self.log, "Could not subscribe to topic"; "topic" => %topic); - } - } } let subnet_max = <::EthSpec as EthSpec>::SyncCommitteeSubnetCount::to_u64(); for subnet_id in 0..subnet_max { let subnet = Subnet::SyncCommittee(SyncSubnetId::new(subnet_id)); // Update the ENR bitfield self.libp2p.update_enr_subnet(subnet, true); - for fork_digest in self.required_gossip_fork_digests() { - let topic = GossipTopic::new( - subnet.into(), - GossipEncoding::default(), - fork_digest, - ); - if self.libp2p.subscribe(topic.clone()) { - subscribed_topics.push(topic); - } else { - warn!(self.log, "Could not subscribe to topic"; "topic" => %topic); - } - } } } if !subscribed_topics.is_empty() { info!( - self.log, - "Subscribed to topics"; - "topics" => ?subscribed_topics.into_iter().map(|topic| format!("{}", topic)).collect::>() + topics = ?subscribed_topics.into_iter().map(|topic| format!("{}", topic)).collect::>(), + "Subscribed to topics" ); } } @@ -822,19 +759,14 @@ impl NetworkService { .update_gossipsub_parameters(active_validators, slot) .is_err() { - error!( - self.log, - "Failed to update gossipsub parameters"; - "active_validators" => active_validators - ); + error!(active_validators, "Failed to update gossipsub parameters"); } } else { // This scenario will only happen if the caches on the cached canonical head aren't // built. That should never be the case. error!( - self.log, - "Active validator count unavailable"; - "info" => "please report this bug" + info = "please report this bug", + "Active validator count unavailable" ); } } @@ -876,10 +808,9 @@ impl NetworkService { let fork_context = &self.fork_context; if let Some(new_fork_name) = fork_context.from_context_bytes(new_fork_digest) { info!( - self.log, - "Transitioned to new fork"; - "old_fork" => ?fork_context.current_fork(), - "new_fork" => ?new_fork_name, + old_fork = ?fork_context.current_fork(), + new_fork = ?new_fork_name, + "Transitioned to new fork" ); fork_context.update_current_fork(*new_fork_name); @@ -896,21 +827,24 @@ impl NetworkService { self.next_fork_subscriptions = Box::pin(next_fork_subscriptions_delay(&self.beacon_chain).into()); self.next_unsubscribe = Box::pin(Some(tokio::time::sleep(unsubscribe_delay)).into()); - info!(self.log, "Network will unsubscribe from old fork gossip topics in a few epochs"; "remaining_epochs" => UNSUBSCRIBE_DELAY_EPOCHS); + info!( + remaining_epochs = UNSUBSCRIBE_DELAY_EPOCHS, + "Network will unsubscribe from old fork gossip topics in a few epochs" + ); // Remove topic weight from old fork topics to prevent peers that left on the mesh on // old topics from being penalized for not sending us messages. self.libp2p.remove_topic_weight_except(new_fork_digest); } else { - crit!(self.log, "Unknown new enr fork id"; "new_fork_id" => ?new_enr_fork_id); + crit!(new_fork_id = ?new_enr_fork_id, "Unknown new enr fork id"); } } fn subscribed_core_topics(&self) -> bool { let core_topics = core_topics_to_subscribe::( self.fork_context.current_fork(), - &self.fork_context.spec, &self.network_globals.as_topic_config(), + &self.fork_context.spec, ); let core_topics: HashSet<&GossipKind> = HashSet::from_iter(&core_topics); let subscriptions = self.network_globals.gossipsub_subscriptions.read(); @@ -951,26 +885,18 @@ impl Drop for NetworkService { fn drop(&mut self) { // network thread is terminating let enrs = self.libp2p.enr_entries(); - debug!( - self.log, - "Persisting DHT to store"; - "Number of peers" => enrs.len(), - ); + debug!(number_of_peers = enrs.len(), "Persisting DHT to store"); if let Err(e) = clear_dht::(self.store.clone()) { - error!(self.log, "Failed to clear old DHT entries"; "error" => ?e); + error!(error = ?e, "Failed to clear old DHT entries"); } // Still try to update new entries match persist_dht::(self.store.clone(), enrs) { Err(e) => error!( - self.log, - "Failed to persist DHT on drop"; - "error" => ?e - ), - Ok(_) => info!( - self.log, - "Saved DHT state"; + error = ?e, + "Failed to persist DHT on drop" ), + Ok(_) => info!("Saved DHT state"), } - info!(self.log, "Network service shutdown"); + info!("Network service shutdown"); } } diff --git a/beacon_node/network/src/service/tests.rs b/beacon_node/network/src/service/tests.rs index 32bbfcbcaa..15c3321e94 100644 --- a/beacon_node/network/src/service/tests.rs +++ b/beacon_node/network/src/service/tests.rs @@ -8,8 +8,6 @@ use beacon_processor::{BeaconProcessorChannels, BeaconProcessorConfig}; use futures::StreamExt; use lighthouse_network::types::{GossipEncoding, GossipKind}; use lighthouse_network::{Enr, GossipTopic}; -use slog::{o, Drain, Level, Logger}; -use sloggers::{null::NullLoggerBuilder, Build}; use std::str::FromStr; use std::sync::Arc; use tokio::runtime::Runtime; @@ -21,28 +19,8 @@ impl NetworkService { } } -fn get_logger(actual_log: bool) -> Logger { - if actual_log { - let drain = { - let decorator = slog_term::TermDecorator::new().build(); - let decorator = - logging::AlignedTermDecorator::new(decorator, logging::MAX_MESSAGE_WIDTH); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).chan_size(2048).build(); - drain.filter_level(Level::Debug) - }; - - Logger::root(drain.fuse(), o!()) - } else { - let builder = NullLoggerBuilder; - builder.build().expect("should build logger") - } -} - #[test] fn test_dht_persistence() { - let log = get_logger(false); - let beacon_chain = BeaconChainHarness::builder(MinimalEthSpec) .default_spec() .deterministic_keypairs(8) @@ -60,8 +38,12 @@ fn test_dht_persistence() { let (signal, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let executor = - task_executor::TaskExecutor::new(Arc::downgrade(&runtime), exit, log.clone(), shutdown_tx); + let executor = task_executor::TaskExecutor::new( + Arc::downgrade(&runtime), + exit, + shutdown_tx, + "test-dht-persistence".to_string(), + ); let mut config = NetworkConfig::default(); config.set_ipv4_listening_address(std::net::Ipv4Addr::UNSPECIFIED, 21212, 21212, 21213); @@ -137,8 +119,8 @@ fn test_removing_topic_weight_on_old_topics() { let executor = task_executor::TaskExecutor::new( Arc::downgrade(&runtime), exit, - get_logger(false), shutdown_tx, + "test-removing-topic-weight-on-old-topics".to_string(), ); let mut config = NetworkConfig::default(); diff --git a/beacon_node/network/src/subnet_service/attestation_subnets.rs b/beacon_node/network/src/subnet_service/attestation_subnets.rs new file mode 100644 index 0000000000..dd4724b261 --- /dev/null +++ b/beacon_node/network/src/subnet_service/attestation_subnets.rs @@ -0,0 +1,681 @@ +//! This service keeps track of which shard subnet the beacon node should be subscribed to at any +//! given time. It schedules subscriptions to shard subnets, requests peer discoveries and +//! determines whether attestations should be aggregated and/or passed to the beacon node. + +use super::SubnetServiceMessage; +use std::collections::HashSet; +use std::collections::{HashMap, VecDeque}; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; + +use beacon_chain::{BeaconChain, BeaconChainTypes}; +use delay_map::{HashMapDelay, HashSetDelay}; +use futures::prelude::*; +use lighthouse_network::{discv5::enr::NodeId, NetworkConfig, Subnet, SubnetDiscovery}; +use slot_clock::SlotClock; +use tracing::{debug, error, info, trace, warn}; +use types::{Attestation, EthSpec, Slot, SubnetId, ValidatorSubscription}; + +use crate::metrics; + +/// The minimum number of slots ahead that we attempt to discover peers for a subscription. If the +/// slot is less than this number, skip the peer discovery process. +/// Subnet discovery query takes at most 30 secs, 2 slots take 24s. +pub(crate) const MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD: u64 = 2; +/// The fraction of a slot that we subscribe to a subnet before the required slot. +/// +/// Currently a whole slot ahead. +const ADVANCE_SUBSCRIBE_SLOT_FRACTION: u32 = 1; + +/// The number of slots after an aggregator duty where we remove the entry from +/// `aggregate_validators_on_subnet` delay map. +const UNSUBSCRIBE_AFTER_AGGREGATOR_DUTY: u32 = 2; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub(crate) enum SubscriptionKind { + /// Long lived subscriptions. + /// + /// These have a longer duration and are advertised in our ENR. + LongLived, + /// Short lived subscriptions. + /// + /// Subscribing to these subnets has a short duration and we don't advertise it in our ENR. + ShortLived, +} + +/// A particular subnet at a given slot. +#[derive(PartialEq, Eq, Hash, Clone, Debug, Copy)] +pub struct ExactSubnet { + /// The `SubnetId` associated with this subnet. + pub subnet_id: SubnetId, + /// The `Slot` associated with this subnet. + pub slot: Slot, +} + +pub struct AttestationService { + /// Queued events to return to the driving service. + events: VecDeque, + + /// A reference to the beacon chain to process received attestations. + pub(crate) beacon_chain: Arc>, + + /// Subnets we are currently subscribed to as short lived subscriptions. + /// + /// Once they expire, we unsubscribe from these. + /// We subscribe to subnets when we are an aggregator for an exact subnet. + short_lived_subscriptions: HashMapDelay, + + /// Subnets we are currently subscribed to as long lived subscriptions. + /// + /// We advertise these in our ENR. When these expire, the subnet is removed from our ENR. + /// These are required of all beacon nodes. The exact number is determined by the chain + /// specification. + long_lived_subscriptions: HashSet, + + /// Short lived subscriptions that need to be executed in the future. + scheduled_short_lived_subscriptions: HashSetDelay, + + /// A collection timeouts to track the existence of aggregate validator subscriptions at an + /// `ExactSubnet`. + aggregate_validators_on_subnet: Option>, + + /// The waker for the current thread. + waker: Option, + + /// The discovery mechanism of lighthouse is disabled. + discovery_disabled: bool, + + /// We are always subscribed to all subnets. + subscribe_all_subnets: bool, + + /// Our Discv5 node_id. + node_id: NodeId, + + /// Future used to manage subscribing and unsubscribing from long lived subnets. + next_long_lived_subscription_event: Pin>, + + /// Whether this node is a block proposer-only node. + proposer_only: bool, +} + +impl AttestationService { + /* Public functions */ + + /// Establish the service based on the passed configuration. + pub fn new(beacon_chain: Arc>, node_id: NodeId, config: &NetworkConfig) -> Self { + let slot_duration = beacon_chain.slot_clock.slot_duration(); + + if config.subscribe_all_subnets { + info!("Subscribing to all subnets"); + } else { + info!( + subnets_per_node = beacon_chain.spec.subnets_per_node, + subscription_duration_in_epochs = beacon_chain.spec.epochs_per_subnet_subscription, + "Deterministic long lived subnets enabled" + ); + } + + let track_validators = !config.import_all_attestations; + let aggregate_validators_on_subnet = + track_validators.then(|| HashSetDelay::new(slot_duration)); + let mut service = AttestationService { + events: VecDeque::with_capacity(10), + beacon_chain, + short_lived_subscriptions: HashMapDelay::new(slot_duration), + long_lived_subscriptions: HashSet::default(), + scheduled_short_lived_subscriptions: HashSetDelay::default(), + aggregate_validators_on_subnet, + waker: None, + discovery_disabled: config.disable_discovery, + subscribe_all_subnets: config.subscribe_all_subnets, + node_id, + next_long_lived_subscription_event: { + // Set a dummy sleep. Calculating the current subnet subscriptions will update this + // value with a smarter timing + Box::pin(tokio::time::sleep(Duration::from_secs(1))) + }, + proposer_only: config.proposer_only, + }; + + // If we are not subscribed to all subnets, handle the deterministic set of subnets + if !config.subscribe_all_subnets { + service.recompute_long_lived_subnets(); + } + + service + } + + /// Return count of all currently subscribed subnets (long-lived **and** short-lived). + #[cfg(test)] + pub fn subscription_count(&self) -> usize { + if self.subscribe_all_subnets { + self.beacon_chain.spec.attestation_subnet_count as usize + } else { + let count = self + .short_lived_subscriptions + .keys() + .chain(self.long_lived_subscriptions.iter()) + .collect::>() + .len(); + count + } + } + + /// Returns whether we are subscribed to a subnet for testing purposes. + #[cfg(test)] + pub(crate) fn is_subscribed( + &self, + subnet_id: &SubnetId, + subscription_kind: SubscriptionKind, + ) -> bool { + match subscription_kind { + SubscriptionKind::LongLived => self.long_lived_subscriptions.contains(subnet_id), + SubscriptionKind::ShortLived => self.short_lived_subscriptions.contains_key(subnet_id), + } + } + + #[cfg(test)] + pub(crate) fn long_lived_subscriptions(&self) -> &HashSet { + &self.long_lived_subscriptions + } + + /// Processes a list of validator subscriptions. + /// + /// This will: + /// - Register new validators as being known. + /// - Search for peers for required subnets. + /// - Request subscriptions for subnets on specific slots when required. + /// - Build the timeouts for each of these events. + /// + /// This returns a result simply for the ergonomics of using ?. The result can be + /// safely dropped. + pub fn validator_subscriptions( + &mut self, + subscriptions: impl Iterator, + ) -> Result<(), String> { + // If the node is in a proposer-only state, we ignore all subnet subscriptions. + if self.proposer_only { + return Ok(()); + } + + // Maps each subnet_id subscription to it's highest slot + let mut subnets_to_discover: HashMap = HashMap::new(); + + // Registers the validator with the attestation service. + for subscription in subscriptions { + metrics::inc_counter(&metrics::SUBNET_SUBSCRIPTION_REQUESTS); + + trace!(?subscription, "Validator subscription"); + + // Compute the subnet that is associated with this subscription + let subnet_id = match SubnetId::compute_subnet::( + subscription.slot, + subscription.attestation_committee_index, + subscription.committee_count_at_slot, + &self.beacon_chain.spec, + ) { + Ok(subnet_id) => subnet_id, + Err(e) => { + warn!( + error = ?e, + "Failed to compute subnet id for validator subscription" + ); + continue; + } + }; + // Ensure each subnet_id inserted into the map has the highest slot as it's value. + // Higher slot corresponds to higher min_ttl in the `SubnetDiscovery` entry. + if let Some(slot) = subnets_to_discover.get(&subnet_id) { + if subscription.slot > *slot { + subnets_to_discover.insert(subnet_id, subscription.slot); + } + } else if !self.discovery_disabled { + subnets_to_discover.insert(subnet_id, subscription.slot); + } + + let exact_subnet = ExactSubnet { + subnet_id, + slot: subscription.slot, + }; + + // Determine if the validator is an aggregator. If so, we subscribe to the subnet and + // if successful add the validator to a mapping of known aggregators for that exact + // subnet. + + if subscription.is_aggregator { + metrics::inc_counter(&metrics::SUBNET_SUBSCRIPTION_AGGREGATOR_REQUESTS); + if let Err(e) = self.subscribe_to_short_lived_subnet(exact_subnet) { + warn!(error = e, "Subscription to subnet error"); + } else { + trace!(?exact_subnet, "Subscribed to subnet for aggregator duties"); + } + } + } + + // If the discovery mechanism isn't disabled, attempt to set up a peer discovery for the + // required subnets. + if !self.discovery_disabled { + if let Err(e) = self.discover_peers_request( + subnets_to_discover + .into_iter() + .map(|(subnet_id, slot)| ExactSubnet { subnet_id, slot }), + ) { + warn!(error = e, "Discovery lookup request error"); + }; + } + + Ok(()) + } + + fn recompute_long_lived_subnets(&mut self) { + // Ensure the next computation is scheduled even if assigning subnets fails. + let next_subscription_event = self + .recompute_long_lived_subnets_inner() + .unwrap_or_else(|_| self.beacon_chain.slot_clock.slot_duration()); + + debug!("Recomputing deterministic long lived subnets"); + self.next_long_lived_subscription_event = + Box::pin(tokio::time::sleep(next_subscription_event)); + + if let Some(waker) = self.waker.as_ref() { + waker.wake_by_ref(); + } + } + + /// Gets the long lived subnets the node should be subscribed to during the current epoch and + /// the remaining duration for which they remain valid. + fn recompute_long_lived_subnets_inner(&mut self) -> Result { + let current_epoch = self.beacon_chain.epoch().map_err(|e| { + if !self + .beacon_chain + .slot_clock + .is_prior_to_genesis() + .unwrap_or(false) + { + error!(err = ?e,"Failed to get the current epoch from clock") + } + })?; + + let (subnets, next_subscription_epoch) = SubnetId::compute_subnets_for_epoch::( + self.node_id.raw(), + current_epoch, + &self.beacon_chain.spec, + ) + .map_err(|e| error!(err = e, "Could not compute subnets for current epoch"))?; + + let next_subscription_slot = + next_subscription_epoch.start_slot(T::EthSpec::slots_per_epoch()); + let next_subscription_event = self + .beacon_chain + .slot_clock + .duration_to_slot(next_subscription_slot) + .ok_or_else(|| { + error!("Failed to compute duration to next to long lived subscription event") + })?; + + self.update_long_lived_subnets(subnets.collect()); + + Ok(next_subscription_event) + } + + /// Updates the long lived subnets. + /// + /// New subnets are registered as subscribed, removed subnets as unsubscribed and the Enr + /// updated accordingly. + fn update_long_lived_subnets(&mut self, mut subnets: HashSet) { + info!(subnets = ?subnets.iter().collect::>(),"Subscribing to long-lived subnets"); + for subnet in &subnets { + // Add the events for those subnets that are new as long lived subscriptions. + if !self.long_lived_subscriptions.contains(subnet) { + // Check if this subnet is new and send the subscription event if needed. + if !self.short_lived_subscriptions.contains_key(subnet) { + debug!( + ?subnet, + subscription_kind = ?SubscriptionKind::LongLived, + "Subscribing to subnet" + ); + self.queue_event(SubnetServiceMessage::Subscribe(Subnet::Attestation( + *subnet, + ))); + } + self.queue_event(SubnetServiceMessage::EnrAdd(Subnet::Attestation(*subnet))); + if !self.discovery_disabled { + self.queue_event(SubnetServiceMessage::DiscoverPeers(vec![SubnetDiscovery { + subnet: Subnet::Attestation(*subnet), + min_ttl: None, + }])) + } + } + } + + // Update the long_lived_subnets set and check for subnets that are being removed + std::mem::swap(&mut self.long_lived_subscriptions, &mut subnets); + for subnet in subnets { + if !self.long_lived_subscriptions.contains(&subnet) { + self.handle_removed_subnet(subnet, SubscriptionKind::LongLived); + } + } + } + + /// Checks if we have subscribed aggregate validators for the subnet. If not, checks the gossip + /// verification, re-propagates and returns false. + pub fn should_process_attestation( + &self, + subnet: SubnetId, + attestation: &Attestation, + ) -> bool { + // Proposer-only mode does not need to process attestations + if self.proposer_only { + return false; + } + self.aggregate_validators_on_subnet + .as_ref() + .map(|tracked_vals| { + tracked_vals.contains_key(&ExactSubnet { + subnet_id: subnet, + slot: attestation.data().slot, + }) + }) + .unwrap_or(true) + } + + /* Internal private functions */ + + /// Adds an event to the event queue and notifies that this service is ready to be polled + /// again. + fn queue_event(&mut self, ev: SubnetServiceMessage) { + self.events.push_back(ev); + if let Some(waker) = &self.waker { + waker.wake_by_ref() + } + } + /// Checks if there are currently queued discovery requests and the time required to make the + /// request. + /// + /// If there is sufficient time, queues a peer discovery request for all the required subnets. + fn discover_peers_request( + &mut self, + exact_subnets: impl Iterator, + ) -> Result<(), &'static str> { + let current_slot = self + .beacon_chain + .slot_clock + .now() + .ok_or("Could not get the current slot")?; + + let discovery_subnets: Vec = exact_subnets + .filter_map(|exact_subnet| { + // Check if there is enough time to perform a discovery lookup. + if exact_subnet.slot + >= current_slot.saturating_add(MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD) + { + // Send out an event to start looking for peers. + // Require the peer for an additional slot to ensure we keep the peer for the + // duration of the subscription. + let min_ttl = self + .beacon_chain + .slot_clock + .duration_to_slot(exact_subnet.slot + 1) + .map(|duration| std::time::Instant::now() + duration); + Some(SubnetDiscovery { + subnet: Subnet::Attestation(exact_subnet.subnet_id), + min_ttl, + }) + } else { + // We may want to check the global PeerInfo to see estimated timeouts for each + // peer before they can be removed. + warn!( + subnet_id = ?exact_subnet, + "Not enough time for a discovery search" + ); + None + } + }) + .collect(); + + if !discovery_subnets.is_empty() { + self.queue_event(SubnetServiceMessage::DiscoverPeers(discovery_subnets)); + } + Ok(()) + } + + // Subscribes to the subnet if it should be done immediately, or schedules it if required. + fn subscribe_to_short_lived_subnet( + &mut self, + ExactSubnet { subnet_id, slot }: ExactSubnet, + ) -> Result<(), &'static str> { + let slot_duration = self.beacon_chain.slot_clock.slot_duration(); + + // The short time we schedule the subscription before it's actually required. This + // ensures we are subscribed on time, and allows consecutive subscriptions to the same + // subnet to overlap, reducing subnet churn. + let advance_subscription_duration = slot_duration / ADVANCE_SUBSCRIBE_SLOT_FRACTION; + // The time to the required slot. + let time_to_subscription_slot = self + .beacon_chain + .slot_clock + .duration_to_slot(slot) + .unwrap_or_default(); // If this is a past slot we will just get a 0 duration. + + // Calculate how long before we need to subscribe to the subnet. + let time_to_subscription_start = + time_to_subscription_slot.saturating_sub(advance_subscription_duration); + + // The time after a duty slot where we no longer need it in the `aggregate_validators_on_subnet` + // delay map. + let time_to_unsubscribe = + time_to_subscription_slot + UNSUBSCRIBE_AFTER_AGGREGATOR_DUTY * slot_duration; + if let Some(tracked_vals) = self.aggregate_validators_on_subnet.as_mut() { + tracked_vals.insert_at(ExactSubnet { subnet_id, slot }, time_to_unsubscribe); + } + + // If the subscription should be done in the future, schedule it. Otherwise subscribe + // immediately. + if time_to_subscription_start.is_zero() { + // This is a current or past slot, we subscribe immediately. + self.subscribe_to_short_lived_subnet_immediately(subnet_id, slot + 1)?; + } else { + // This is a future slot, schedule subscribing. + trace!(subnet = ?subnet_id, ?time_to_subscription_start,"Scheduling subnet subscription"); + self.scheduled_short_lived_subscriptions + .insert_at(ExactSubnet { subnet_id, slot }, time_to_subscription_start); + } + + Ok(()) + } + + /* A collection of functions that handle the various timeouts */ + + /// Registers a subnet as subscribed. + /// + /// Checks that the time in which the subscription would end is not in the past. If we are + /// already subscribed, extends the timeout if necessary. If this is a new subscription, we send + /// out the appropriate events. + /// + /// On determinist long lived subnets, this is only used for short lived subscriptions. + fn subscribe_to_short_lived_subnet_immediately( + &mut self, + subnet_id: SubnetId, + end_slot: Slot, + ) -> Result<(), &'static str> { + if self.subscribe_all_subnets { + // Case not handled by this service. + return Ok(()); + } + + let time_to_subscription_end = self + .beacon_chain + .slot_clock + .duration_to_slot(end_slot) + .unwrap_or_default(); + + // First check this is worth doing. + if time_to_subscription_end.is_zero() { + return Err("Time when subscription would end has already passed."); + } + + let subscription_kind = SubscriptionKind::ShortLived; + + // We need to check and add a subscription for the right kind, regardless of the presence + // of the subnet as a subscription of the other kind. This is mainly since long lived + // subscriptions can be removed at any time when a validator goes offline. + + let (subscriptions, already_subscribed_as_other_kind) = ( + &mut self.short_lived_subscriptions, + self.long_lived_subscriptions.contains(&subnet_id), + ); + + match subscriptions.get(&subnet_id) { + Some(current_end_slot) => { + // We are already subscribed. Check if we need to extend the subscription. + if &end_slot > current_end_slot { + trace!( + subnet = ?subnet_id, + prev_end_slot = %current_end_slot, + new_end_slot = %end_slot, + ?subscription_kind, + "Extending subscription to subnet" + ); + subscriptions.insert_at(subnet_id, end_slot, time_to_subscription_end); + } + } + None => { + // This is a new subscription. Add with the corresponding timeout and send the + // notification. + subscriptions.insert_at(subnet_id, end_slot, time_to_subscription_end); + + // Inform of the subscription. + if !already_subscribed_as_other_kind { + debug!( + subnet = ?subnet_id, + %end_slot, + ?subscription_kind, + "Subscribing to subnet" + ); + self.queue_event(SubnetServiceMessage::Subscribe(Subnet::Attestation( + subnet_id, + ))); + } + } + } + + Ok(()) + } + + // Unsubscribes from a subnet that was removed if it does not continue to exist as a + // subscription of the other kind. For long lived subscriptions, it also removes the + // advertisement from our ENR. + fn handle_removed_subnet(&mut self, subnet_id: SubnetId, subscription_kind: SubscriptionKind) { + let exists_in_other_subscriptions = match subscription_kind { + SubscriptionKind::LongLived => self.short_lived_subscriptions.contains_key(&subnet_id), + SubscriptionKind::ShortLived => self.long_lived_subscriptions.contains(&subnet_id), + }; + + if !exists_in_other_subscriptions { + // Subscription no longer exists as short lived or long lived. + debug!( + subnet = ?subnet_id, + ?subscription_kind, + "Unsubscribing from subnet" + ); + self.queue_event(SubnetServiceMessage::Unsubscribe(Subnet::Attestation( + subnet_id, + ))); + } + + if subscription_kind == SubscriptionKind::LongLived { + // Remove from our ENR even if we remain subscribed in other way. + self.queue_event(SubnetServiceMessage::EnrRemove(Subnet::Attestation( + subnet_id, + ))); + } + } +} + +impl Stream for AttestationService { + type Item = SubnetServiceMessage; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // Update the waker if needed. + if let Some(waker) = &self.waker { + if waker.will_wake(cx.waker()) { + self.waker = Some(cx.waker().clone()); + } + } else { + self.waker = Some(cx.waker().clone()); + } + + // Send out any generated events. + if let Some(event) = self.events.pop_front() { + return Poll::Ready(Some(event)); + } + + // If we aren't subscribed to all subnets, handle the deterministic long-lived subnets + if !self.subscribe_all_subnets { + match self.next_long_lived_subscription_event.as_mut().poll(cx) { + Poll::Ready(_) => { + self.recompute_long_lived_subnets(); + // We re-wake the task as there could be other subscriptions to process + self.waker + .as_ref() + .expect("Waker has been set") + .wake_by_ref(); + } + Poll::Pending => {} + } + } + + // Process scheduled subscriptions that might be ready, since those can extend a soon to + // expire subscription. + match self.scheduled_short_lived_subscriptions.poll_next_unpin(cx) { + Poll::Ready(Some(Ok(ExactSubnet { subnet_id, slot }))) => { + if let Err(e) = + self.subscribe_to_short_lived_subnet_immediately(subnet_id, slot + 1) + { + debug!(subnet = ?subnet_id, err = e,"Failed to subscribe to short lived subnet"); + } + self.waker + .as_ref() + .expect("Waker has been set") + .wake_by_ref(); + } + Poll::Ready(Some(Err(e))) => { + error!( + error = e, + "Failed to check for scheduled subnet subscriptions" + ); + } + Poll::Ready(None) | Poll::Pending => {} + } + + // Finally process any expired subscriptions. + match self.short_lived_subscriptions.poll_next_unpin(cx) { + Poll::Ready(Some(Ok((subnet_id, _end_slot)))) => { + self.handle_removed_subnet(subnet_id, SubscriptionKind::ShortLived); + // We re-wake the task as there could be other subscriptions to process + self.waker + .as_ref() + .expect("Waker has been set") + .wake_by_ref(); + } + Poll::Ready(Some(Err(e))) => { + error!(error = e, "Failed to check for subnet unsubscription times"); + } + Poll::Ready(None) | Poll::Pending => {} + } + + // Poll to remove entries on expiration, no need to act on expiration events. + if let Some(tracked_vals) = self.aggregate_validators_on_subnet.as_mut() { + if let Poll::Ready(Some(Err(e))) = tracked_vals.poll_next_unpin(cx) { + error!( + error = e, + "Failed to check for aggregate validator on subnet expirations" + ); + } + } + + Poll::Pending + } +} diff --git a/beacon_node/network/src/subnet_service/mod.rs b/beacon_node/network/src/subnet_service/mod.rs index de90e22254..5340538e52 100644 --- a/beacon_node/network/src/subnet_service/mod.rs +++ b/beacon_node/network/src/subnet_service/mod.rs @@ -14,8 +14,8 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use delay_map::HashSetDelay; use futures::prelude::*; use lighthouse_network::{discv5::enr::NodeId, NetworkConfig, Subnet, SubnetDiscovery}; -use slog::{debug, error, o, warn}; use slot_clock::SlotClock; +use tracing::{debug, error, info, instrument, warn}; use types::{ AttestationData, EthSpec, Slot, SubnetId, SyncCommitteeSubscription, SyncSubnetId, ValidatorSubscription, @@ -107,27 +107,23 @@ pub struct SubnetService { /// Whether this node is a block proposer-only node. proposer_only: bool, - - /// The logger for the attestation service. - log: slog::Logger, } impl SubnetService { /* Public functions */ /// Establish the service based on the passed configuration. - pub fn new( - beacon_chain: Arc>, - node_id: NodeId, - config: &NetworkConfig, - log: &slog::Logger, - ) -> Self { - let log = log.new(o!("service" => "subnet_service")); - + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] + pub fn new(beacon_chain: Arc>, node_id: NodeId, config: &NetworkConfig) -> Self { let slot_duration = beacon_chain.slot_clock.slot_duration(); if config.subscribe_all_subnets { - slog::info!(log, "Subscribing to all subnets"); + info!("Subscribing to all subnets"); } // Build the list of known permanent subscriptions, so that we know not to subscribe or @@ -194,7 +190,6 @@ impl SubnetService { discovery_disabled: config.disable_discovery, subscribe_all_subnets: config.subscribe_all_subnets, proposer_only: config.proposer_only, - log, } } @@ -233,6 +228,12 @@ impl SubnetService { /// /// This returns a result simply for the ergonomics of using ?. The result can be /// safely dropped. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] pub fn validator_subscriptions(&mut self, subscriptions: impl Iterator) { // If the node is in a proposer-only state, we ignore all subnet subscriptions. if self.proposer_only { @@ -257,9 +258,9 @@ impl SubnetService { ) { Ok(subnet_id) => Subnet::Attestation(subnet_id), Err(e) => { - warn!(self.log, - "Failed to compute subnet id for validator subscription"; - "error" => ?e, + warn!( + error = ?e, + "Failed to compute subnet id for validator subscription" ); continue; } @@ -287,10 +288,7 @@ impl SubnetService { if subscription.is_aggregator { metrics::inc_counter(&metrics::SUBNET_SUBSCRIPTION_AGGREGATOR_REQUESTS); if let Err(e) = self.subscribe_to_subnet(exact_subnet) { - warn!(self.log, - "Subscription to subnet error"; - "error" => e, - ); + warn!(error = e, "Subscription to subnet error"); } } } @@ -305,10 +303,10 @@ impl SubnetService { ) { Ok(subnet_ids) => subnet_ids, Err(e) => { - warn!(self.log, - "Failed to compute subnet id for sync committee subscription"; - "error" => ?e, - "validator_index" => subscription.validator_index + warn!( + error = ?e, + validator_index = subscription.validator_index, + "Failed to compute subnet id for sync committee subscription" ); continue; } @@ -326,7 +324,11 @@ impl SubnetService { .slot_clock .duration_to_slot(slot_required_until) else { - warn!(self.log, "Subscription to sync subnet error"; "error" => "Unable to determine duration to unsubscription slot", "validator_index" => subscription.validator_index); + warn!( + error = "Unable to determine duration to unsubscription slot", + validator_index = subscription.validator_index, + "Subscription to sync subnet error" + ); continue; }; @@ -337,11 +339,11 @@ impl SubnetService { .now() .unwrap_or(Slot::from(0u64)); warn!( - self.log, - "Sync committee subscription is past expiration"; - "subnet" => ?subnet, - "current_slot" => ?current_slot, - "unsubscribe_slot" => ?slot_required_until, ); + ?subnet, + ?current_slot, + unsubscribe_slot = ?slot_required_until, + "Sync committee subscription is past expiration" + ); continue; } @@ -359,13 +361,19 @@ impl SubnetService { // required subnets. if !self.discovery_disabled { if let Err(e) = self.discover_peers_request(subnets_to_discover.into_iter()) { - warn!(self.log, "Discovery lookup request error"; "error" => e); + warn!(error = e, "Discovery lookup request error"); }; } } /// Checks if we have subscribed aggregate validators for the subnet. If not, checks the gossip /// verification, re-propagates and returns false. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] pub fn should_process_attestation( &self, subnet: Subnet, @@ -390,6 +398,12 @@ impl SubnetService { /// Adds an event to the event queue and notifies that this service is ready to be polled /// again. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn queue_event(&mut self, ev: SubnetServiceMessage) { self.events.push_back(ev); if let Some(waker) = &self.waker { @@ -401,6 +415,11 @@ impl SubnetService { /// /// If there is sufficient time, queues a peer discovery request for all the required subnets. // NOTE: Sending early subscriptions results in early searching for peers on subnets. + #[instrument(parent = None, + level = "info", + name = "subnet_service", + skip_all + )] fn discover_peers_request( &mut self, subnets_to_discover: impl Iterator, @@ -432,9 +451,9 @@ impl SubnetService { } else { // We may want to check the global PeerInfo to see estimated timeouts for each // peer before they can be removed. - warn!(self.log, - "Not enough time for a discovery search"; - "subnet_id" => ?subnet, + warn!( + subnet_id = ?subnet, + "Not enough time for a discovery search" ); None } @@ -448,6 +467,12 @@ impl SubnetService { } // Subscribes to the subnet if it should be done immediately, or schedules it if required. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn subscribe_to_subnet( &mut self, ExactSubnet { subnet, slot }: ExactSubnet, @@ -500,6 +525,12 @@ impl SubnetService { } /// Adds a subscription event to the sync subnet. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn subscribe_to_sync_subnet( &mut self, subnet: Subnet, @@ -529,7 +560,11 @@ impl SubnetService { self.subscriptions .insert_at(subnet, duration_to_unsubscribe); // We are not currently subscribed and have no waiting subscription, create one - debug!(self.log, "Subscribing to subnet"; "subnet" => ?subnet, "until" => ?slot_required_until); + debug!( + ?subnet, + until = ?slot_required_until, + "Subscribing to subnet" + ); self.events .push_back(SubnetServiceMessage::Subscribe(subnet)); @@ -545,6 +580,12 @@ impl SubnetService { /// Checks that the time in which the subscription would end is not in the past. If we are /// already subscribed, extends the timeout if necessary. If this is a new subscription, we send /// out the appropriate events. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn subscribe_to_subnet_immediately( &mut self, subnet: Subnet, @@ -588,9 +629,10 @@ impl SubnetService { .insert_at(subnet, time_to_subscription_end); // Inform of the subscription. - debug!(self.log, "Subscribing to subnet"; - "subnet" => ?subnet, - "end_slot" => end_slot, + debug!( + ?subnet, + %end_slot, + "Subscribing to subnet" ); self.queue_event(SubnetServiceMessage::Subscribe(subnet)); } @@ -599,10 +641,16 @@ impl SubnetService { } // Unsubscribes from a subnet that was removed. + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn handle_removed_subnet(&mut self, subnet: Subnet) { if !self.subscriptions.contains_key(&subnet) { // Subscription no longer exists as short lived subnet - debug!(self.log, "Unsubscribing from subnet"; "subnet" => ?subnet); + debug!(?subnet, "Unsubscribing from subnet"); self.queue_event(SubnetServiceMessage::Unsubscribe(subnet)); // If this is a sync subnet, we need to remove it from our ENR. @@ -616,6 +664,12 @@ impl SubnetService { impl Stream for SubnetService { type Item = SubnetServiceMessage; + #[instrument(parent = None, + level = "info", + fields(service = "subnet_service"), + name = "subnet_service", + skip_all + )] fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // Update the waker if needed. if let Some(waker) = &self.waker { @@ -639,7 +693,11 @@ impl Stream for SubnetService { // Set the `end_slot` for the subscription to be `duty.slot + 1` so that we unsubscribe // only at the end of the duty slot. if let Err(e) = self.subscribe_to_subnet_immediately(subnet, slot + 1) { - debug!(self.log, "Failed to subscribe to short lived subnet"; "subnet" => ?subnet, "err" => e); + debug!( + subnet = ?subnet, + err = e, + "Failed to subscribe to short lived subnet" + ); } self.waker .as_ref() @@ -647,7 +705,10 @@ impl Stream for SubnetService { .wake_by_ref(); } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for scheduled subnet subscriptions"; "error"=> e); + error!( + error = e, + "Failed to check for scheduled subnet subscriptions" + ); } Poll::Ready(None) | Poll::Pending => {} } @@ -663,7 +724,7 @@ impl Stream for SubnetService { .wake_by_ref(); } Poll::Ready(Some(Err(e))) => { - error!(self.log, "Failed to check for subnet unsubscription times"; "error"=> e); + error!(error = e, "Failed to check for subnet unsubscription times"); } Poll::Ready(None) | Poll::Pending => {} } @@ -671,7 +732,10 @@ impl Stream for SubnetService { // Poll to remove entries on expiration, no need to act on expiration events. if let Some(tracked_vals) = self.aggregate_validators_on_subnet.as_mut() { if let Poll::Ready(Some(Err(e))) = tracked_vals.poll_next_unpin(cx) { - error!(self.log, "Failed to check for aggregate validator on subnet expirations"; "error"=> e); + error!( + error = e, + "Failed to check for aggregate validator on subnet expirations" + ); } } diff --git a/beacon_node/network/src/subnet_service/sync_subnets.rs b/beacon_node/network/src/subnet_service/sync_subnets.rs new file mode 100644 index 0000000000..59ec278a95 --- /dev/null +++ b/beacon_node/network/src/subnet_service/sync_subnets.rs @@ -0,0 +1,345 @@ +//! This service keeps track of which sync committee subnet the beacon node should be subscribed to at any +//! given time. It schedules subscriptions to sync committee subnets and requests peer discoveries. + +use std::collections::{hash_map::Entry, HashMap, VecDeque}; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; + +use futures::prelude::*; +use tracing::{debug, error, trace, warn}; + +use super::SubnetServiceMessage; +use beacon_chain::{BeaconChain, BeaconChainTypes}; +use delay_map::HashSetDelay; +use lighthouse_network::{NetworkConfig, Subnet, SubnetDiscovery}; +use slot_clock::SlotClock; +use types::{Epoch, EthSpec, SyncCommitteeSubscription, SyncSubnetId}; + +use crate::metrics; + +/// The minimum number of slots ahead that we attempt to discover peers for a subscription. If the +/// slot is less than this number, skip the peer discovery process. +/// Subnet discovery query takes at most 30 secs, 2 slots take 24s. +const MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD: u64 = 2; + +/// A particular subnet at a given slot. +#[derive(PartialEq, Eq, Hash, Clone, Debug)] +pub struct ExactSubnet { + /// The `SyncSubnetId` associated with this subnet. + pub subnet_id: SyncSubnetId, + /// The epoch until which we need to stay subscribed to the subnet. + pub until_epoch: Epoch, +} +pub struct SyncCommitteeService { + /// Queued events to return to the driving service. + events: VecDeque, + + /// A reference to the beacon chain to process received attestations. + pub(crate) beacon_chain: Arc>, + + /// The collection of all currently subscribed subnets. + subscriptions: HashMap, + + /// A collection of timeouts for when to unsubscribe from a subnet. + unsubscriptions: HashSetDelay, + + /// The waker for the current thread. + waker: Option, + + /// The discovery mechanism of lighthouse is disabled. + discovery_disabled: bool, + + /// We are always subscribed to all subnets. + subscribe_all_subnets: bool, + + /// Whether this node is a block proposer-only node. + proposer_only: bool, +} + +impl SyncCommitteeService { + /* Public functions */ + + pub fn new(beacon_chain: Arc>, config: &NetworkConfig) -> Self { + let spec = &beacon_chain.spec; + let epoch_duration_secs = + beacon_chain.slot_clock.slot_duration().as_secs() * T::EthSpec::slots_per_epoch(); + let default_timeout = + epoch_duration_secs.saturating_mul(spec.epochs_per_sync_committee_period.as_u64()); + + SyncCommitteeService { + events: VecDeque::with_capacity(10), + beacon_chain, + subscriptions: HashMap::new(), + unsubscriptions: HashSetDelay::new(Duration::from_secs(default_timeout)), + waker: None, + subscribe_all_subnets: config.subscribe_all_subnets, + discovery_disabled: config.disable_discovery, + proposer_only: config.proposer_only, + } + } + + /// Return count of all currently subscribed subnets. + #[cfg(test)] + pub fn subscription_count(&self) -> usize { + use types::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT; + if self.subscribe_all_subnets { + SYNC_COMMITTEE_SUBNET_COUNT as usize + } else { + self.subscriptions.len() + } + } + + /// Processes a list of sync committee subscriptions. + /// + /// This will: + /// - Search for peers for required subnets. + /// - Request subscriptions required subnets. + /// - Build the timeouts for each of these events. + /// + /// This returns a result simply for the ergonomics of using ?. The result can be + /// safely dropped. + pub fn validator_subscriptions( + &mut self, + subscriptions: Vec, + ) -> Result<(), String> { + // A proposer-only node does not subscribe to any sync-committees + if self.proposer_only { + return Ok(()); + } + + let mut subnets_to_discover = Vec::new(); + for subscription in subscriptions { + metrics::inc_counter(&metrics::SYNC_COMMITTEE_SUBSCRIPTION_REQUESTS); + //NOTE: We assume all subscriptions have been verified before reaching this service + + // Registers the validator with the subnet service. + // This will subscribe to long-lived random subnets if required. + trace!(?subscription, "Sync committee subscription"); + + let subnet_ids = match SyncSubnetId::compute_subnets_for_sync_committee::( + &subscription.sync_committee_indices, + ) { + Ok(subnet_ids) => subnet_ids, + Err(e) => { + warn!( + error = ?e, + validator_index = subscription.validator_index, + "Failed to compute subnet id for sync committee subscription" + ); + continue; + } + }; + + for subnet_id in subnet_ids { + let exact_subnet = ExactSubnet { + subnet_id, + until_epoch: subscription.until_epoch, + }; + subnets_to_discover.push(exact_subnet.clone()); + if let Err(e) = self.subscribe_to_subnet(exact_subnet.clone()) { + warn!( + error = e, + validator_index = subscription.validator_index, + "Subscription to sync subnet error" + ); + } else { + trace!( + ?exact_subnet, + validator_index = subscription.validator_index, + "Subscribed to subnet for sync committee duties" + ); + } + } + } + // If the discovery mechanism isn't disabled, attempt to set up a peer discovery for the + // required subnets. + if !self.discovery_disabled { + if let Err(e) = self.discover_peers_request(subnets_to_discover.iter()) { + warn!(error = e, "Discovery lookup request error"); + }; + } + + // pre-emptively wake the thread to check for new events + if let Some(waker) = &self.waker { + waker.wake_by_ref(); + } + Ok(()) + } + + /* Internal private functions */ + + /// Checks if there are currently queued discovery requests and the time required to make the + /// request. + /// + /// If there is sufficient time, queues a peer discovery request for all the required subnets. + fn discover_peers_request<'a>( + &mut self, + exact_subnets: impl Iterator, + ) -> Result<(), &'static str> { + let current_slot = self + .beacon_chain + .slot_clock + .now() + .ok_or("Could not get the current slot")?; + + let slots_per_epoch = T::EthSpec::slots_per_epoch(); + + let discovery_subnets: Vec = exact_subnets + .filter_map(|exact_subnet| { + let until_slot = exact_subnet.until_epoch.end_slot(slots_per_epoch); + // check if there is enough time to perform a discovery lookup + if until_slot >= current_slot.saturating_add(MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD) { + // if the slot is more than epoch away, add an event to start looking for peers + // add one slot to ensure we keep the peer for the subscription slot + let min_ttl = self + .beacon_chain + .slot_clock + .duration_to_slot(until_slot + 1) + .map(|duration| std::time::Instant::now() + duration); + Some(SubnetDiscovery { + subnet: Subnet::SyncCommittee(exact_subnet.subnet_id), + min_ttl, + }) + } else { + // We may want to check the global PeerInfo to see estimated timeouts for each + // peer before they can be removed. + warn!( + subnet_id = ?exact_subnet, + "Not enough time for a discovery search" + ); + None + } + }) + .collect(); + + if !discovery_subnets.is_empty() { + self.events + .push_back(SubnetServiceMessage::DiscoverPeers(discovery_subnets)); + } + Ok(()) + } + + /// Adds a subscription event and an associated unsubscription event if required. + fn subscribe_to_subnet(&mut self, exact_subnet: ExactSubnet) -> Result<(), &'static str> { + // Return if we have subscribed to all subnets + if self.subscribe_all_subnets { + return Ok(()); + } + + // Return if we already have a subscription for exact_subnet + if self.subscriptions.get(&exact_subnet.subnet_id) == Some(&exact_subnet.until_epoch) { + return Ok(()); + } + + // Return if we already have subscription set to expire later than the current request. + if let Some(until_epoch) = self.subscriptions.get(&exact_subnet.subnet_id) { + if *until_epoch >= exact_subnet.until_epoch { + return Ok(()); + } + } + + // initialise timing variables + let current_slot = self + .beacon_chain + .slot_clock + .now() + .ok_or("Could not get the current slot")?; + + let slots_per_epoch = T::EthSpec::slots_per_epoch(); + let until_slot = exact_subnet.until_epoch.end_slot(slots_per_epoch); + // Calculate the duration to the unsubscription event. + let expected_end_subscription_duration = if current_slot >= until_slot { + warn!( + %current_slot, + ?exact_subnet, + "Sync committee subscription is past expiration" + ); + return Ok(()); + } else { + let slot_duration = self.beacon_chain.slot_clock.slot_duration(); + + // the duration until we no longer need this subscription. We assume a single slot is + // sufficient. + self.beacon_chain + .slot_clock + .duration_to_slot(until_slot) + .ok_or("Unable to determine duration to unsubscription slot")? + + slot_duration + }; + + if let Entry::Vacant(e) = self.subscriptions.entry(exact_subnet.subnet_id) { + // We are not currently subscribed and have no waiting subscription, create one + debug!(subnet = *exact_subnet.subnet_id, until_epoch = ?exact_subnet.until_epoch, "Subscribing to subnet"); + e.insert(exact_subnet.until_epoch); + self.events + .push_back(SubnetServiceMessage::Subscribe(Subnet::SyncCommittee( + exact_subnet.subnet_id, + ))); + + // add the subnet to the ENR bitfield + self.events + .push_back(SubnetServiceMessage::EnrAdd(Subnet::SyncCommittee( + exact_subnet.subnet_id, + ))); + + // add an unsubscription event to remove ourselves from the subnet once completed + self.unsubscriptions + .insert_at(exact_subnet.subnet_id, expected_end_subscription_duration); + } else { + // We are already subscribed, extend the unsubscription duration + self.unsubscriptions + .update_timeout(&exact_subnet.subnet_id, expected_end_subscription_duration); + } + + Ok(()) + } + + /// A queued unsubscription is ready. + fn handle_unsubscriptions(&mut self, subnet_id: SyncSubnetId) { + debug!(subnet = *subnet_id, "Unsubscribing from subnet"); + + self.subscriptions.remove(&subnet_id); + self.events + .push_back(SubnetServiceMessage::Unsubscribe(Subnet::SyncCommittee( + subnet_id, + ))); + + self.events + .push_back(SubnetServiceMessage::EnrRemove(Subnet::SyncCommittee( + subnet_id, + ))); + } +} + +impl Stream for SyncCommitteeService { + type Item = SubnetServiceMessage; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // update the waker if needed + if let Some(waker) = &self.waker { + if waker.will_wake(cx.waker()) { + self.waker = Some(cx.waker().clone()); + } + } else { + self.waker = Some(cx.waker().clone()); + } + + // process any un-subscription events + match self.unsubscriptions.poll_next_unpin(cx) { + Poll::Ready(Some(Ok(exact_subnet))) => self.handle_unsubscriptions(exact_subnet), + Poll::Ready(Some(Err(e))) => { + error!(error = e, "Failed to check for subnet unsubscription times"); + } + Poll::Ready(None) | Poll::Pending => {} + } + + // process any generated events + if let Some(event) = self.events.pop_front() { + return Poll::Ready(Some(event)); + } + + Poll::Pending + } +} diff --git a/beacon_node/network/src/subnet_service/tests/mod.rs b/beacon_node/network/src/subnet_service/tests/mod.rs index 0f3343df63..7e274850b5 100644 --- a/beacon_node/network/src/subnet_service/tests/mod.rs +++ b/beacon_node/network/src/subnet_service/tests/mod.rs @@ -13,6 +13,7 @@ use std::time::{Duration, SystemTime}; use store::config::StoreConfig; use store::{HotColdDB, MemoryStore}; use task_executor::test_utils::TestRuntime; +use tracing_subscriber::EnvFilter; use types::{ CommitteeIndex, Epoch, EthSpec, Hash256, MainnetEthSpec, Slot, SubnetId, SyncCommitteeSubscription, SyncSubnetId, ValidatorSubscription, @@ -20,6 +21,8 @@ use types::{ const SLOT_DURATION_MILLIS: u64 = 400; +const TEST_LOG_LEVEL: Option<&str> = None; + type TestBeaconChainType = Witness< SystemTimeSlotClock, CachingEth1Backend, @@ -37,11 +40,11 @@ impl TestBeaconChain { pub fn new_with_system_clock() -> Self { let spec = Arc::new(MainnetEthSpec::default_spec()); + get_tracing_subscriber(TEST_LOG_LEVEL); + let keypairs = generate_deterministic_keypairs(1); - let log = logging::test_logger(); - let store = - HotColdDB::open_ephemeral(StoreConfig::default(), spec.clone(), log.clone()).unwrap(); + let store = HotColdDB::open_ephemeral(StoreConfig::default(), spec.clone()).unwrap(); let kzg = get_kzg(&spec); @@ -51,7 +54,6 @@ impl TestBeaconChain { let chain = Arc::new( BeaconChainBuilder::new(MainnetEthSpec, kzg.clone()) - .logger(log.clone()) .custom_spec(spec.clone()) .store(Arc::new(store)) .task_executor(test_runtime.task_executor.clone()) @@ -91,10 +93,18 @@ pub fn recent_genesis_time() -> u64 { .as_secs() } +fn get_tracing_subscriber(log_level: Option<&str>) { + if let Some(level) = log_level { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::try_new(level).unwrap()) + .try_init() + .unwrap(); + } +} + static CHAIN: LazyLock = LazyLock::new(TestBeaconChain::new_with_system_clock); fn get_subnet_service() -> SubnetService { - let log = logging::test_logger(); let config = NetworkConfig::default(); let beacon_chain = CHAIN.chain.clone(); @@ -103,7 +113,6 @@ fn get_subnet_service() -> SubnetService { beacon_chain, lighthouse_network::discv5::enr::NodeId::random(), &config, - &log, ) } diff --git a/beacon_node/network/src/sync/backfill_sync/mod.rs b/beacon_node/network/src/sync/backfill_sync/mod.rs index 4220f85fc3..cd3f0dcbeb 100644 --- a/beacon_node/network/src/sync/backfill_sync/mod.rs +++ b/beacon_node/network/src/sync/backfill_sync/mod.rs @@ -20,13 +20,14 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use lighthouse_network::service::api_types::Id; use lighthouse_network::types::{BackFillState, NetworkGlobals}; use lighthouse_network::{PeerAction, PeerId}; +use logging::crit; use rand::seq::SliceRandom; -use slog::{crit, debug, error, info, warn}; use std::collections::{ btree_map::{BTreeMap, Entry}, HashMap, HashSet, }; use std::sync::Arc; +use tracing::{debug, error, info, instrument, warn}; use types::{Epoch, EthSpec}; /// Blocks are downloaded in batches from peers. This constant specifies how many epochs worth of @@ -146,16 +147,17 @@ pub struct BackFillSync { /// Reference to the network globals in order to obtain valid peers to backfill blocks from /// (i.e synced peers). network_globals: Arc>, - - /// A logger for backfill sync. - log: slog::Logger, } impl BackFillSync { + #[instrument(parent = None, + level = "info", + name = "backfill_sync", + skip_all + )] pub fn new( beacon_chain: Arc>, network_globals: Arc>, - log: slog::Logger, ) -> Self { // Determine if backfill is enabled or not. // If, for some reason a backfill has already been completed (or we've used a trusted @@ -186,7 +188,6 @@ impl BackFillSync { participating_peers: HashSet::new(), restart_failed_sync: false, beacon_chain, - log, }; // Update the global network state with the current backfill state. @@ -195,9 +196,15 @@ impl BackFillSync { } /// Pauses the backfill sync if it's currently syncing. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] pub fn pause(&mut self) { if let BackFillState::Syncing = self.state() { - debug!(self.log, "Backfill sync paused"; "processed_epochs" => self.validated_batches, "to_be_processed" => self.current_start); + debug!(processed_epochs = %self.validated_batches, to_be_processed = %self.current_start,"Backfill sync paused"); self.set_state(BackFillState::Paused); } } @@ -206,6 +213,12 @@ impl BackFillSync { /// /// If resuming is successful, reports back the current syncing metrics. #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] pub fn start( &mut self, network: &mut SyncNetworkContext, @@ -222,7 +235,7 @@ impl BackFillSync { .is_some() { // If there are peers to resume with, begin the resume. - debug!(self.log, "Resuming backfill sync"; "start_epoch" => self.current_start, "awaiting_batches" => self.batches.len(), "processing_target" => self.processing_target); + debug!(start_epoch = ?self.current_start, awaiting_batches = self.batches.len(), processing_target = ?self.processing_target, "Resuming backfill sync"); self.set_state(BackFillState::Syncing); // Resume any previously failed batches. self.resume_batches(network)?; @@ -251,14 +264,14 @@ impl BackFillSync { // This infallible match exists to force us to update this code if a future // refactor of `ResetEpochError` adds a variant. let ResetEpochError::SyncCompleted = e; - error!(self.log, "Backfill sync completed whilst in failed status"); + error!("Backfill sync completed whilst in failed status"); self.set_state(BackFillState::Completed); return Err(BackFillError::InvalidSyncState(String::from( "chain completed", ))); } - debug!(self.log, "Resuming a failed backfill sync"; "start_epoch" => self.current_start); + debug!(start_epoch = %self.current_start, "Resuming a failed backfill sync"); // begin requesting blocks from the peer pool, until all peers are exhausted. self.request_batches(network)?; @@ -281,6 +294,12 @@ impl BackFillSync { /// A fully synced peer has joined us. /// If we are in a failed state, update a local variable to indicate we are able to restart /// the failed sync on the next attempt. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] pub fn fully_synced_peer_joined(&mut self) { if matches!(self.state(), BackFillState::Failed) { self.restart_failed_sync = true; @@ -289,6 +308,12 @@ impl BackFillSync { /// A peer has disconnected. /// If the peer has active batches, those are considered failed and re-requested. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] pub fn peer_disconnected( &mut self, @@ -318,15 +343,13 @@ impl BackFillSync { // short circuit early. if self.retry_batch_download(network, id).is_err() { debug!( - self.log, - "Batch could not be retried"; - "batch_id" => id, - "error" => "no synced peers" + batch_id = %id, + error = "no synced peers", + "Batch could not be retried" ); } } else { - debug!(self.log, "Batch not found while removing peer"; - "peer" => %peer_id, "batch" => id) + debug!(peer = %peer_id, batch = %id, "Batch not found while removing peer"); } } } @@ -339,6 +362,12 @@ impl BackFillSync { /// An RPC error has occurred. /// /// If the batch exists it is re-requested. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] pub fn inject_error( &mut self, @@ -356,7 +385,7 @@ impl BackFillSync { if !batch.is_expecting_block(&request_id) { return Ok(()); } - debug!(self.log, "Batch failed"; "batch_epoch" => batch_id, "error" => "rpc_error"); + debug!(batch_epoch = %batch_id, error = "rpc_error", "Batch failed"); if let Some(active_requests) = self.active_requests.get_mut(peer_id) { active_requests.remove(&batch_id); } @@ -378,6 +407,12 @@ impl BackFillSync { /// If this returns an error, the backfill sync has failed and will be restarted once new peers /// join the system. /// The sync manager should update the global sync state on failure. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] pub fn on_block_response( &mut self, @@ -391,7 +426,7 @@ impl BackFillSync { let Some(batch) = self.batches.get_mut(&batch_id) else { if !matches!(self.state(), BackFillState::Failed) { // A batch might get removed when the chain advances, so this is non fatal. - debug!(self.log, "Received a block for unknown batch"; "epoch" => batch_id); + debug!(epoch = %batch_id, "Received a block for unknown batch"); } return Ok(ProcessResult::Successful); }; @@ -416,7 +451,12 @@ impl BackFillSync { Ok(received) => { let awaiting_batches = self.processing_target.saturating_sub(batch_id) / BACKFILL_EPOCHS_PER_BATCH; - debug!(self.log, "Completed batch received"; "epoch" => batch_id, "blocks" => received, "awaiting_batches" => awaiting_batches); + debug!( + epoch = %batch_id, + blocks = received, + %awaiting_batches, + "Completed batch received" + ); // pre-emptively request more blocks from peers whilst we process current blocks, self.request_batches(network)?; @@ -432,6 +472,12 @@ impl BackFillSync { /// The syncing process has failed. /// /// This resets past variables, to allow for a fresh start when resuming. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn fail_sync(&mut self, error: BackFillError) -> Result<(), BackFillError> { // Some errors shouldn't fail the chain. if matches!(error, BackFillError::Paused) { @@ -455,7 +501,7 @@ impl BackFillSync { // NOTE: Lets keep validated_batches for posterity // Emit the log here - error!(self.log, "Backfill sync failed"; "error" => ?error); + error!(?error, "Backfill sync failed"); // Return the error, kinda weird pattern, but I want to use // `self.fail_chain(_)?` in other parts of the code. @@ -464,6 +510,12 @@ impl BackFillSync { /// Processes the batch with the given id. /// The batch must exist and be ready for processing + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn process_batch( &mut self, network: &mut SyncNetworkContext, @@ -503,8 +555,12 @@ impl BackFillSync { .beacon_processor() .send_chain_segment(process_id, blocks) { - crit!(self.log, "Failed to send backfill segment to processor."; "msg" => "process_batch", - "error" => %e, "batch" => self.processing_target); + crit!( + msg = "process_batch", + error = %e, + batch = ?self.processing_target, + "Failed to send backfill segment to processor." + ); // This is unlikely to happen but it would stall syncing since the batch now has no // blocks to continue, and the chain is expecting a processing result that won't // arrive. To mitigate this, (fake) fail this processing so that the batch is @@ -518,6 +574,12 @@ impl BackFillSync { /// The block processor has completed processing a batch. This function handles the result /// of the batch processor. /// If an error is returned the BackFill sync has failed. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] #[must_use = "A failure here indicates the backfill sync has failed and the global sync state should be updated"] pub fn on_batch_process_result( &mut self, @@ -530,13 +592,15 @@ impl BackFillSync { // result let batch = match &self.current_processing_batch { Some(processing_id) if *processing_id != batch_id => { - debug!(self.log, "Unexpected batch result"; - "batch_epoch" => batch_id, "expected_batch_epoch" => processing_id); + debug!( + batch_epoch = %batch_id.as_u64(), + expected_batch_epoch = processing_id.as_u64(), + "Unexpected batch result" + ); return Ok(ProcessResult::Successful); } None => { - debug!(self.log, "Chain was not expecting a batch result"; - "batch_epoch" => batch_id); + debug!(%batch_id, "Chain was not expecting a batch result"); return Ok(ProcessResult::Successful); } _ => { @@ -566,8 +630,14 @@ impl BackFillSync { return Ok(ProcessResult::Successful); }; - debug!(self.log, "Backfill batch processed"; "result" => ?result, &batch, - "batch_epoch" => batch_id, "peer" => %peer, "client" => %network.client_type(peer)); + debug!( + ?result, + %batch, + batch_epoch = %batch_id, + %peer, + client = %network.client_type(peer), + "Backfill batch processed" + ); match result { BatchProcessResult::Success { @@ -591,7 +661,10 @@ impl BackFillSync { // check if the chain has completed syncing if self.check_completed() { // chain is completed - info!(self.log, "Backfill sync completed"; "blocks_processed" => self.validated_batches * T::EthSpec::slots_per_epoch()); + info!( + blocks_processed = self.validated_batches * T::EthSpec::slots_per_epoch(), + "Backfill sync completed" + ); self.set_state(BackFillState::Completed); Ok(ProcessResult::SyncCompleted) } else { @@ -619,10 +692,9 @@ impl BackFillSync { // repeatedly and are either malicious or faulty. We stop the backfill sync and // report all synced peers that have participated. warn!( - self.log, - "Backfill batch failed to download. Penalizing peers"; - "score_adjustment" => %penalty, - "batch_epoch"=> batch_id + score_adjustment = %penalty, + batch_epoch = %batch_id, + "Backfill batch failed to download. Penalizing peers" ); for peer in self.participating_peers.drain() { @@ -658,6 +730,12 @@ impl BackFillSync { } /// Processes the next ready batch. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn process_completed_batches( &mut self, network: &mut SyncNetworkContext, @@ -692,7 +770,10 @@ impl BackFillSync { BatchState::AwaitingValidation(_) => { // TODO: I don't think this state is possible, log a CRIT just in case. // If this is not observed, add it to the failed state branch above. - crit!(self.log, "Chain encountered a robust batch awaiting validation"; "batch" => self.processing_target); + crit!( + batch = ?self.processing_target, + "Chain encountered a robust batch awaiting validation" + ); self.processing_target -= BACKFILL_EPOCHS_PER_BATCH; if self.to_be_downloaded >= self.processing_target { @@ -718,6 +799,12 @@ impl BackFillSync { /// /// If a previous batch has been validated and it had been re-processed, penalize the original /// peer. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn advance_chain(&mut self, network: &mut SyncNetworkContext, validating_epoch: Epoch) { // make sure this epoch produces an advancement if validating_epoch >= self.current_start { @@ -745,9 +832,12 @@ impl BackFillSync { // A different peer sent the correct batch, the previous peer did not // We negatively score the original peer. let action = PeerAction::LowToleranceError; - debug!(self.log, "Re-processed batch validated. Scoring original peer"; - "batch_epoch" => id, "score_adjustment" => %action, - "original_peer" => %attempt.peer_id, "new_peer" => %processed_attempt.peer_id + debug!( + batch_epoch = ?id, + score_adjustment = %action, + original_peer = %attempt.peer_id, + new_peer = %processed_attempt.peer_id, + "Re-processed batch validated. Scoring original peer" ); network.report_peer( attempt.peer_id, @@ -758,9 +848,12 @@ impl BackFillSync { // The same peer corrected it's previous mistake. There was an error, so we // negative score the original peer. let action = PeerAction::MidToleranceError; - debug!(self.log, "Re-processed batch validated by the same peer"; - "batch_epoch" => id, "score_adjustment" => %action, - "original_peer" => %attempt.peer_id, "new_peer" => %processed_attempt.peer_id + debug!( + batch_epoch = ?id, + score_adjustment = %action, + original_peer = %attempt.peer_id, + new_peer = %processed_attempt.peer_id, + "Re-processed batch validated by the same peer" ); network.report_peer( attempt.peer_id, @@ -778,14 +871,11 @@ impl BackFillSync { } } BatchState::Failed | BatchState::Poisoned | BatchState::AwaitingDownload => { - crit!( - self.log, - "batch indicates inconsistent chain state while advancing chain" - ) + crit!("batch indicates inconsistent chain state while advancing chain") } BatchState::AwaitingProcessing(..) => {} BatchState::Processing(_) => { - debug!(self.log, "Advancing chain while processing a batch"; "batch" => id, batch); + debug!(batch = %id, %batch, "Advancing chain while processing a batch"); if let Some(processing_id) = self.current_processing_batch { if id >= processing_id { self.current_processing_batch = None; @@ -803,7 +893,7 @@ impl BackFillSync { // won't have this batch, so we need to request it. self.to_be_downloaded -= BACKFILL_EPOCHS_PER_BATCH; } - debug!(self.log, "Backfill advanced"; "validated_epoch" => validating_epoch, "processing_target" => self.processing_target); + debug!(?validating_epoch, processing_target = ?self.processing_target, "Backfill advanced"); } /// An invalid batch has been received that could not be processed, but that can be retried. @@ -811,6 +901,12 @@ impl BackFillSync { /// These events occur when a peer has successfully responded with blocks, but the blocks we /// have received are incorrect or invalid. This indicates the peer has not performed as /// intended and can result in downvoting a peer. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn handle_invalid_batch( &mut self, network: &mut SyncNetworkContext, @@ -862,6 +958,12 @@ impl BackFillSync { } /// Sends and registers the request of a batch awaiting download. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn retry_batch_download( &mut self, network: &mut SyncNetworkContext, @@ -896,13 +998,19 @@ impl BackFillSync { self.send_batch(network, batch_id, peer) } else { // If we are here the chain has no more synced peers - info!(self.log, "Backfill sync paused"; "reason" => "insufficient_synced_peers"); + info!(reason = "insufficient_synced_peers", "Backfill sync paused"); self.set_state(BackFillState::Paused); Err(BackFillError::Paused) } } /// Requests the batch assigned to the given id from a given peer. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn send_batch( &mut self, network: &mut SyncNetworkContext, @@ -922,7 +1030,7 @@ impl BackFillSync { if let Err(e) = batch.start_downloading_from_peer(peer, request_id) { return self.fail_sync(BackFillError::BatchInvalidState(batch_id, e.0)); } - debug!(self.log, "Requesting batch"; "epoch" => batch_id, &batch); + debug!(epoch = %batch_id, %batch, "Requesting batch"); // register the batch for this peer self.active_requests @@ -933,8 +1041,7 @@ impl BackFillSync { } Err(e) => { // NOTE: under normal conditions this shouldn't happen but we handle it anyway - warn!(self.log, "Could not send batch request"; - "batch_id" => batch_id, "error" => ?e, &batch); + warn!(%batch_id, error = ?e, %batch,"Could not send batch request"); // register the failed download and check if the batch can be retried if let Err(e) = batch.start_downloading_from_peer(peer, 1) { return self.fail_sync(BackFillError::BatchInvalidState(batch_id, e.0)); @@ -963,6 +1070,12 @@ impl BackFillSync { /// When resuming a chain, this function searches for batches that need to be re-downloaded and /// transitions their state to redownload the batch. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn resume_batches(&mut self, network: &mut SyncNetworkContext) -> Result<(), BackFillError> { let batch_ids_to_retry = self .batches @@ -987,6 +1100,12 @@ impl BackFillSync { /// Attempts to request the next required batches from the peer pool if the chain is syncing. It will exhaust the peer /// pool and left over batches until the batch buffer is reached or all peers are exhausted. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn request_batches( &mut self, network: &mut SyncNetworkContext, @@ -1029,6 +1148,12 @@ impl BackFillSync { /// Creates the next required batch from the chain. If there are no more batches required, /// `false` is returned. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn include_next_batch(&mut self, network: &mut SyncNetworkContext) -> Option { // don't request batches beyond genesis; if self.last_batch_downloaded { @@ -1090,6 +1215,12 @@ impl BackFillSync { /// /// This errors if the beacon chain indicates that backfill sync has already completed or is /// not required. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn reset_start_epoch(&mut self) -> Result<(), ResetEpochError> { let anchor_info = self.beacon_chain.store.get_anchor_info(); if anchor_info.block_backfill_complete(self.beacon_chain.genesis_backfill_slot) { @@ -1103,6 +1234,12 @@ impl BackFillSync { } /// Checks with the beacon chain if backfill sync has completed. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn check_completed(&mut self) -> bool { if self.would_complete(self.current_start) { // Check that the beacon chain agrees @@ -1111,13 +1248,19 @@ impl BackFillSync { if anchor_info.block_backfill_complete(self.beacon_chain.genesis_backfill_slot) { return true; } else { - error!(self.log, "Backfill out of sync with beacon chain"); + error!("Backfill out of sync with beacon chain"); } } false } /// Checks if backfill would complete by syncing to `start_epoch`. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn would_complete(&self, start_epoch: Epoch) -> bool { start_epoch <= self @@ -1127,10 +1270,22 @@ impl BackFillSync { } /// Updates the global network state indicating the current state of a backfill sync. + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn set_state(&self, state: BackFillState) { *self.network_globals.backfill_state.write() = state; } + #[instrument(parent = None, + level = "info", + fields(service = "backfill_sync"), + name = "backfill_sync", + skip_all + )] fn state(&self) -> BackFillState { self.network_globals.backfill_state.read().clone() } diff --git a/beacon_node/network/src/sync/block_lookups/mod.rs b/beacon_node/network/src/sync/block_lookups/mod.rs index 2172c8dcd8..8c884f644e 100644 --- a/beacon_node/network/src/sync/block_lookups/mod.rs +++ b/beacon_node/network/src/sync/block_lookups/mod.rs @@ -41,11 +41,11 @@ use lighthouse_network::service::api_types::SingleLookupReqId; use lighthouse_network::{PeerAction, PeerId}; use lru_cache::LRUTimeCache; pub use single_block_lookup::{BlobRequestState, BlockRequestState, CustodyRequestState}; -use slog::{debug, error, warn, Logger}; use std::collections::hash_map::Entry; use std::sync::Arc; use std::time::Duration; use store::Hash256; +use tracing::{debug, error, instrument, warn}; use types::{BlobSidecar, DataColumnSidecar, EthSpec, SignedBeaconBlock}; pub mod common; @@ -116,9 +116,6 @@ pub struct BlockLookups { // TODO: Why not index lookups by block_root? single_block_lookups: FnvHashMap>, - - /// The logger for the import manager. - log: Logger, } #[cfg(test)] @@ -130,27 +127,45 @@ use lighthouse_network::service::api_types::Id; pub(crate) type BlockLookupSummary = (Id, Hash256, Option, Vec); impl BlockLookups { - pub fn new(log: Logger) -> Self { + #[instrument(parent = None,level = "info", fields(service = "lookup_sync"), name = "lookup_sync")] + pub fn new() -> Self { Self { failed_chains: LRUTimeCache::new(Duration::from_secs( FAILED_CHAINS_CACHE_EXPIRY_SECONDS, )), single_block_lookups: Default::default(), - log, } } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub(crate) fn insert_failed_chain(&mut self, block_root: Hash256) { self.failed_chains.insert(block_root); } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub(crate) fn get_failed_chains(&mut self) -> Vec { self.failed_chains.keys().cloned().collect() } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub(crate) fn active_single_lookups(&self) -> Vec { self.single_block_lookups .iter() @@ -159,6 +174,12 @@ impl BlockLookups { } /// Returns a vec of all parent lookup chains by tip, in descending slot order (tip first) + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub(crate) fn active_parent_lookups(&self) -> Vec { compute_parent_chains( &self @@ -173,6 +194,12 @@ impl BlockLookups { /// Creates a parent lookup for the block with the given `block_root` and immediately triggers it. /// If a parent lookup exists or is triggered, a current lookup will be created. + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn search_child_and_parent( &mut self, block_root: Hash256, @@ -202,6 +229,12 @@ impl BlockLookups { /// Seach a block whose parent root is unknown. /// Returns true if the lookup is created or already exists + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn search_unknown_block( &mut self, block_root: Hash256, @@ -217,6 +250,12 @@ impl BlockLookups { /// - `block_root_to_search` is a failed chain /// /// Returns true if the lookup is created or already exists + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn search_parent_of_child( &mut self, block_root_to_search: Hash256, @@ -238,7 +277,7 @@ impl BlockLookups { if (block_would_extend_chain || trigger_is_chain_tip) && parent_chain.len() >= PARENT_DEPTH_TOLERANCE { - debug!(self.log, "Parent lookup chain too long"; "block_root" => ?block_root_to_search); + debug!(block_root = ?block_root_to_search, "Parent lookup chain too long"); // Searching for this parent would extend a parent chain over the max // Insert the tip only to failed chains @@ -283,9 +322,10 @@ impl BlockLookups { }); } else { // Should never happen, log error and continue the lookup drop - error!(self.log, "Unable to transition lookup to range sync"; - "error" => "Parent chain tip lookup not found", - "block_root" => ?parent_chain_tip + error!( + error = "Parent chain tip lookup not found", + block_root = ?parent_chain_tip, + "Unable to transition lookup to range sync" ); } @@ -299,9 +339,10 @@ impl BlockLookups { self.drop_lookup_and_children(*lookup_id); } else { // Should never happen - error!(self.log, "Unable to transition lookup to range sync"; - "error" => "Block to drop lookup not found", - "block_root" => ?block_to_drop + error!( + error = "Block to drop lookup not found", + block_root = ?block_to_drop, + "Unable to transition lookup to range sync" ); } @@ -316,6 +357,12 @@ impl BlockLookups { /// Searches for a single block hash. If the blocks parent is unknown, a chain of blocks is /// constructed. /// Returns true if the lookup is created or already exists + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn new_current_lookup( &mut self, block_root: Hash256, @@ -326,7 +373,7 @@ impl BlockLookups { ) -> bool { // If this block or it's parent is part of a known failed chain, ignore it. if self.failed_chains.contains(&block_root) { - debug!(self.log, "Block is from a past failed chain. Dropping"; "block_root" => ?block_root); + debug!(?block_root, "Block is from a past failed chain. Dropping"); for peer_id in peers { cx.report_peer(*peer_id, PeerAction::MidToleranceError, "failed_chain"); } @@ -343,12 +390,15 @@ impl BlockLookups { let component_type = block_component.get_type(); let imported = lookup.add_child_components(block_component); if !imported { - debug!(self.log, "Lookup child component ignored"; "block_root" => ?block_root, "type" => component_type); + debug!( + ?block_root, + component_type, "Lookup child component ignored" + ); } } if let Err(e) = self.add_peers_to_lookup_and_ancestors(lookup_id, peers, cx) { - warn!(self.log, "Error adding peers to ancestor lookup"; "error" => ?e); + warn!(error = ?e, "Error adding peers to ancestor lookup"); } return true; @@ -361,7 +411,7 @@ impl BlockLookups { .iter() .any(|(_, lookup)| lookup.is_for_block(awaiting_parent)) { - warn!(self.log, "Ignoring child lookup parent lookup not found"; "block_root" => ?awaiting_parent); + warn!(block_root = ?awaiting_parent, "Ignoring child lookup parent lookup not found"); return false; } } @@ -369,7 +419,7 @@ impl BlockLookups { // Lookups contain untrusted data, bound the total count of lookups hold in memory to reduce // the risk of OOM in case of bugs of malicious activity. if self.single_block_lookups.len() > MAX_LOOKUPS { - warn!(self.log, "Dropping lookup reached max"; "block_root" => ?block_root); + warn!(?block_root, "Dropping lookup reached max"); return false; } @@ -387,18 +437,19 @@ impl BlockLookups { Entry::Vacant(entry) => entry.insert(lookup), Entry::Occupied(_) => { // Should never happen - warn!(self.log, "Lookup exists with same id"; "id" => id); + warn!(id, "Lookup exists with same id"); return false; } }; debug!( - self.log, - "Created block lookup"; - "peer_ids" => ?peers, - "block_root" => ?block_root, - "awaiting_parent" => awaiting_parent.map(|root| root.to_string()).unwrap_or("none".to_owned()), - "id" => lookup.id, + ?peers, + ?block_root, + awaiting_parent = awaiting_parent + .map(|root| root.to_string()) + .unwrap_or("none".to_owned()), + id = lookup.id, + "Created block lookup" ); metrics::inc_counter(&metrics::SYNC_LOOKUP_CREATED); @@ -414,6 +465,12 @@ impl BlockLookups { /* Lookup responses */ /// Process a block or blob response received from a single lookup request. + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn on_download_response>( &mut self, id: SingleLookupReqId, @@ -437,7 +494,7 @@ impl BlockLookups { let Some(lookup) = self.single_block_lookups.get_mut(&id.lookup_id) else { // We don't have the ability to cancel in-flight RPC requests. So this can happen // if we started this RPC request, and later saw the block/blobs via gossip. - debug!(self.log, "Block returned for single block lookup not present"; "id" => ?id); + debug!(?id, "Block returned for single block lookup not present"); return Err(LookupRequestError::UnknownLookup); }; @@ -448,12 +505,12 @@ impl BlockLookups { match response { Ok((response, peer_group, seen_timestamp)) => { - debug!(self.log, - "Received lookup download success"; - "block_root" => ?block_root, - "id" => ?id, - "peer_group" => ?peer_group, - "response_type" => ?response_type, + debug!( + ?block_root, + ?id, + ?peer_group, + ?response_type, + "Received lookup download success" ); // Here we could check if response extends a parent chain beyond its max length. @@ -479,14 +536,14 @@ impl BlockLookups { // continue_request will send for processing as the request state is AwaitingProcessing } Err(e) => { - // TODO(das): is it okay to not log the peer source of request failures? Then we - // should log individual requests failures in the SyncNetworkContext - debug!(self.log, - "Received lookup download failure"; - "block_root" => ?block_root, - "id" => ?id, - "response_type" => ?response_type, - "error" => ?e, + // No need to log peer source here. When sending a DataColumnsByRoot request we log + // the peer and the request ID which is linked to this `id` value here. + debug!( + ?block_root, + ?id, + ?response_type, + error = ?e, + "Received lookup download failure" ); request_state.on_download_failure(id.req_id)?; @@ -499,6 +556,12 @@ impl BlockLookups { /* Error responses */ + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn peer_disconnected(&mut self, peer_id: &PeerId) { for (_, lookup) in self.single_block_lookups.iter_mut() { lookup.remove_peer(peer_id); @@ -507,6 +570,12 @@ impl BlockLookups { /* Processing responses */ + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn on_processing_result( &mut self, process_type: BlockProcessType, @@ -527,6 +596,12 @@ impl BlockLookups { self.on_lookup_result(process_type.id(), lookup_result, "processing_result", cx); } + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn on_processing_result_inner>( &mut self, lookup_id: SingleLookupId, @@ -534,7 +609,7 @@ impl BlockLookups { cx: &mut SyncNetworkContext, ) -> Result { let Some(lookup) = self.single_block_lookups.get_mut(&lookup_id) else { - debug!(self.log, "Unknown single block lookup"; "id" => lookup_id); + debug!(id = lookup_id, "Unknown single block lookup"); return Err(LookupRequestError::UnknownLookup); }; @@ -544,12 +619,11 @@ impl BlockLookups { .get_state_mut(); debug!( - self.log, - "Received lookup processing result"; - "component" => ?R::response_type(), - "block_root" => ?block_root, - "id" => lookup_id, - "result" => ?result, + component = ?R::response_type(), + ?block_root, + id = lookup_id, + ?result, + "Received lookup processing result" ); let action = match result { @@ -581,20 +655,15 @@ impl BlockLookups { BlockProcessingResult::Err(BlockError::DuplicateImportStatusUnknown(..)) => { // This is unreachable because RPC blocks do not undergo gossip verification, and // this error can *only* come from gossip verification. - error!( - self.log, - "Single block lookup hit unreachable condition"; - "block_root" => ?block_root - ); + error!(?block_root, "Single block lookup hit unreachable condition"); Action::Drop } BlockProcessingResult::Ignored => { // Beacon processor signalled to ignore the block processing result. // This implies that the cpu is overloaded. Drop the request. warn!( - self.log, - "Lookup component processing ignored, cpu might be overloaded"; - "component" => ?R::response_type(), + component = ?R::response_type(), + "Lookup component processing ignored, cpu might be overloaded" ); Action::Drop } @@ -602,7 +671,7 @@ impl BlockLookups { match e { BlockError::BeaconChainError(e) => { // Internal error - error!(self.log, "Beacon chain error processing lookup component"; "block_root" => %block_root, "error" => ?e); + error!(%block_root, error = ?e, "Beacon chain error processing lookup component"); Action::Drop } BlockError::ParentUnknown { parent_root, .. } => { @@ -618,10 +687,9 @@ impl BlockLookups { // These errors indicate that the execution layer is offline // and failed to validate the execution payload. Do not downscore peer. debug!( - self.log, - "Single block lookup failed. Execution layer is offline / unsynced / misconfigured"; - "block_root" => ?block_root, - "error" => ?e + ?block_root, + error = ?e, + "Single block lookup failed. Execution layer is offline / unsynced / misconfigured" ); Action::Drop } @@ -629,7 +697,7 @@ impl BlockLookups { if e.category() == AvailabilityCheckErrorCategory::Internal => { // There errors indicate internal problems and should not downscore the peer - warn!(self.log, "Internal availability check failure"; "block_root" => ?block_root, "error" => ?e); + warn!(?block_root, error = ?e, "Internal availability check failure"); // Here we choose *not* to call `on_processing_failure` because this could result in a bad // lookup state transition. This error invalidates both blob and block requests, and we don't know the @@ -638,7 +706,12 @@ impl BlockLookups { Action::Drop } other => { - debug!(self.log, "Invalid lookup component"; "block_root" => ?block_root, "component" => ?R::response_type(), "error" => ?other); + debug!( + ?block_root, + component = ?R::response_type(), + error = ?other, + "Invalid lookup component" + ); let peer_group = request_state.on_processing_failure()?; let peers_to_penalize: Vec<_> = match other { // Note: currenlty only InvalidColumn errors have index granularity, @@ -685,7 +758,12 @@ impl BlockLookups { Action::ParentUnknown { parent_root } => { let peers = lookup.all_peers(); lookup.set_awaiting_parent(parent_root); - debug!(self.log, "Marking lookup as awaiting parent"; "id" => lookup.id, "block_root" => ?block_root, "parent_root" => ?parent_root); + debug!( + id = lookup.id, + ?block_root, + ?parent_root, + "Marking lookup as awaiting parent" + ); self.search_parent_of_child(parent_root, block_root, &peers, cx); Ok(LookupResult::Pending) } @@ -700,6 +778,12 @@ impl BlockLookups { } } + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn on_external_processing_result( &mut self, block_root: Hash256, @@ -725,13 +809,24 @@ impl BlockLookups { } /// Makes progress on the immediate children of `block_root` + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn continue_child_lookups(&mut self, block_root: Hash256, cx: &mut SyncNetworkContext) { let mut lookup_results = vec![]; // < need to buffer lookup results to not re-borrow &mut self for (id, lookup) in self.single_block_lookups.iter_mut() { if lookup.awaiting_parent() == Some(block_root) { lookup.resolve_awaiting_parent(); - debug!(self.log, "Continuing child lookup"; "parent_root" => ?block_root, "id" => id, "block_root" => ?lookup.block_root()); + debug!( + parent_root = ?block_root, + id, + block_root = ?lookup.block_root(), + "Continuing child lookup" + ); let result = lookup.continue_requests(cx); lookup_results.push((*id, result)); } @@ -745,12 +840,19 @@ impl BlockLookups { /// Drops `dropped_id` lookup and all its children recursively. Lookups awaiting a parent need /// the parent to make progress to resolve, therefore we must drop them if the parent is /// dropped. + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn drop_lookup_and_children(&mut self, dropped_id: SingleLookupId) { if let Some(dropped_lookup) = self.single_block_lookups.remove(&dropped_id) { - debug!(self.log, "Dropping lookup"; - "id" => ?dropped_id, - "block_root" => ?dropped_lookup.block_root(), - "awaiting_parent" => ?dropped_lookup.awaiting_parent(), + debug!( + id = ?dropped_id, + block_root = ?dropped_lookup.block_root(), + awaiting_parent = ?dropped_lookup.awaiting_parent(), + "Dropping lookup" ); let child_lookups = self @@ -768,6 +870,12 @@ impl BlockLookups { /// Common handler a lookup request error, drop it and update metrics /// Returns true if the lookup is created or already exists + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn on_lookup_result( &mut self, id: SingleLookupId, @@ -779,13 +887,13 @@ impl BlockLookups { Ok(LookupResult::Pending) => true, // no action Ok(LookupResult::Completed) => { if let Some(lookup) = self.single_block_lookups.remove(&id) { - debug!(self.log, "Dropping completed lookup"; "block" => ?lookup.block_root(), "id" => id); + debug!(block = ?lookup.block_root(), id, "Dropping completed lookup"); metrics::inc_counter(&metrics::SYNC_LOOKUP_COMPLETED); // Block imported, continue the requests of pending child blocks self.continue_child_lookups(lookup.block_root(), cx); self.update_metrics(); } else { - debug!(self.log, "Attempting to drop non-existent lookup"; "id" => id); + debug!(id, "Attempting to drop non-existent lookup"); } false } @@ -793,7 +901,7 @@ impl BlockLookups { // update metrics because the lookup does not exist. Err(LookupRequestError::UnknownLookup) => false, Err(error) => { - debug!(self.log, "Dropping lookup on request error"; "id" => id, "source" => source, "error" => ?error); + debug!(id, source, ?error, "Dropping lookup on request error"); metrics::inc_counter_vec(&metrics::SYNC_LOOKUP_DROPPED, &[error.into()]); self.drop_lookup_and_children(id); self.update_metrics(); @@ -805,12 +913,24 @@ impl BlockLookups { /* Helper functions */ /// Drops all the single block requests and returns how many requests were dropped. + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn drop_single_block_requests(&mut self) -> usize { let requests_to_drop = self.single_block_lookups.len(); self.single_block_lookups.clear(); requests_to_drop } + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn update_metrics(&self) { metrics::set_gauge( &metrics::SYNC_SINGLE_BLOCK_LOOKUPS, @@ -819,6 +939,12 @@ impl BlockLookups { } /// Perform some prune operations on lookups on some interval + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] pub fn prune_lookups(&mut self) { self.drop_lookups_without_peers(); self.drop_stuck_lookups(); @@ -842,6 +968,12 @@ impl BlockLookups { /// /// Instead there's no negative for keeping lookups with no peers around for some time. If we /// regularly prune them, it should not be a memory concern (TODO: maybe yes!). + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn drop_lookups_without_peers(&mut self) { for (lookup_id, block_root) in self .single_block_lookups @@ -857,9 +989,10 @@ impl BlockLookups { .map(|lookup| (lookup.id, lookup.block_root())) .collect::>() { - debug!(self.log, "Dropping lookup with no peers"; - "id" => lookup_id, - "block_root" => ?block_root + debug!( + id = lookup_id, + %block_root, + "Dropping lookup with no peers" ); self.drop_lookup_and_children(lookup_id); } @@ -878,6 +1011,12 @@ impl BlockLookups { /// /// - One single clear warn level log per stuck incident /// - If the original bug is sporadic, it reduces the time a node is stuck from forever to 15 min + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn drop_stuck_lookups(&mut self) { // While loop to find and drop all disjoint trees of potentially stuck lookups. while let Some(stuck_lookup) = self.single_block_lookups.values().find(|lookup| { @@ -886,7 +1025,7 @@ impl BlockLookups { let ancestor_stuck_lookup = match self.find_oldest_ancestor_lookup(stuck_lookup) { Ok(lookup) => lookup, Err(e) => { - warn!(self.log, "Error finding oldest ancestor lookup"; "error" => ?e); + warn!(error = ?e,"Error finding oldest ancestor lookup"); // Default to dropping the lookup that exceeds the max duration so at least // eventually sync should be unstuck stuck_lookup @@ -894,16 +1033,18 @@ impl BlockLookups { }; if stuck_lookup.id == ancestor_stuck_lookup.id { - warn!(self.log, "Notify the devs a sync lookup is stuck"; - "block_root" => ?stuck_lookup.block_root(), - "lookup" => ?stuck_lookup, + warn!( + block_root = ?stuck_lookup.block_root(), + lookup = ?stuck_lookup, + "Notify the devs a sync lookup is stuck" ); } else { - warn!(self.log, "Notify the devs a sync lookup is stuck"; - "block_root" => ?stuck_lookup.block_root(), - "lookup" => ?stuck_lookup, - "ancestor_block_root" => ?ancestor_stuck_lookup.block_root(), - "ancestor_lookup" => ?ancestor_stuck_lookup, + warn!( + block_root = ?stuck_lookup.block_root(), + lookup = ?stuck_lookup, + ancestor_block_root = ?ancestor_stuck_lookup.block_root(), + ancestor_lookup = ?ancestor_stuck_lookup, + "Notify the devs a sync lookup is stuck" ); } @@ -913,6 +1054,12 @@ impl BlockLookups { } /// Recursively find the oldest ancestor lookup of another lookup + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn find_oldest_ancestor_lookup<'a>( &'a self, lookup: &'a SingleBlockLookup, @@ -937,6 +1084,12 @@ impl BlockLookups { /// Adds peers to a lookup and its ancestors recursively. /// Note: Takes a `lookup_id` as argument to allow recursion on mutable lookups, without having /// to duplicate the code to add peers to a lookup + #[instrument(parent = None, + level = "info", + fields(service = "lookup_sync"), + name = "lookup_sync", + skip_all + )] fn add_peers_to_lookup_and_ancestors( &mut self, lookup_id: SingleLookupId, @@ -952,9 +1105,10 @@ impl BlockLookups { for peer in peers { if lookup.add_peer(*peer) { added_some_peer = true; - debug!(self.log, "Adding peer to existing single block lookup"; - "block_root" => ?lookup.block_root(), - "peer" => ?peer + debug!( + block_root = ?lookup.block_root(), + ?peer, + "Adding peer to existing single block lookup" ); } } diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index fc31e83727..671fa1e3b4 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -63,12 +63,13 @@ use lighthouse_network::service::api_types::{ use lighthouse_network::types::{NetworkGlobals, SyncState}; use lighthouse_network::SyncInfo; use lighthouse_network::{PeerAction, PeerId}; +use logging::crit; use lru_cache::LRUTimeCache; -use slog::{crit, debug, error, info, o, trace, warn, Logger}; use std::ops::Sub; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc; +use tracing::{debug, error, info, info_span, trace, warn, Instrument}; use types::{ BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, Hash256, SignedBeaconBlock, Slot, }; @@ -246,9 +247,6 @@ pub struct SyncManager { notified_unknown_roots: LRUTimeCache<(PeerId, Hash256)>, sampling: Sampling, - - /// The logger for the import manager. - log: Logger, } /// Spawns a new `SyncManager` thread which has a weak reference to underlying beacon @@ -261,7 +259,6 @@ pub fn spawn( beacon_processor: Arc>, sync_recv: mpsc::UnboundedReceiver>, fork_context: Arc, - log: slog::Logger, ) { assert!( beacon_chain.spec.max_request_blocks(fork_context.current_fork()) as u64 >= T::EthSpec::slots_per_epoch() * EPOCHS_PER_BATCH, @@ -276,12 +273,18 @@ pub fn spawn( sync_recv, SamplingConfig::Default, fork_context, - log.clone(), ); // spawn the sync manager thread - debug!(log, "Sync Manager started"); - executor.spawn(async move { Box::pin(sync_manager.main()).await }, "sync"); + debug!("Sync Manager started"); + executor.spawn( + async move { + Box::pin(sync_manager.main()) + .instrument(info_span!("", service = "sync")) + .await + }, + "sync", + ); } impl SyncManager { @@ -292,7 +295,6 @@ impl SyncManager { sync_recv: mpsc::UnboundedReceiver>, sampling_config: SamplingConfig, fork_context: Arc, - log: slog::Logger, ) -> Self { let network_globals = beacon_processor.network_globals.clone(); Self { @@ -303,23 +305,14 @@ impl SyncManager { beacon_processor.clone(), beacon_chain.clone(), fork_context.clone(), - log.clone(), ), - range_sync: RangeSync::new( - beacon_chain.clone(), - log.new(o!("service" => "range_sync")), - ), - backfill_sync: BackFillSync::new( - beacon_chain.clone(), - network_globals, - log.new(o!("service" => "backfill_sync")), - ), - block_lookups: BlockLookups::new(log.new(o!("service"=> "lookup_sync"))), + range_sync: RangeSync::new(beacon_chain.clone()), + backfill_sync: BackFillSync::new(beacon_chain.clone(), network_globals), + block_lookups: BlockLookups::new(), notified_unknown_roots: LRUTimeCache::new(Duration::from_secs( NOTIFIED_UNKNOWN_ROOT_EXPIRY_SECONDS, )), - sampling: Sampling::new(sampling_config, log.new(o!("service" => "sampling"))), - log: log.clone(), + sampling: Sampling::new(sampling_config), } } @@ -344,6 +337,16 @@ impl SyncManager { self.range_sync.state() } + #[cfg(test)] + pub(crate) fn range_sync_state(&self) -> super::range_sync::SyncChainStatus { + self.range_sync.state() + } + + #[cfg(test)] + pub(crate) fn __range_failed_chains(&mut self) -> Vec { + self.range_sync.__failed_chains() + } + #[cfg(test)] pub(crate) fn get_failed_chains(&mut self) -> Vec { self.block_lookups.get_failed_chains() @@ -368,11 +371,6 @@ impl SyncManager { self.sampling.get_request_status(block_root, index) } - #[cfg(test)] - pub(crate) fn range_sync_state(&self) -> super::range_sync::SyncChainStatus { - self.range_sync.state() - } - #[cfg(test)] pub(crate) fn update_execution_engine_state(&mut self, state: EngineState) { self.handle_new_execution_engine_state(state); @@ -456,10 +454,10 @@ impl SyncManager { }; let head_slot = head_slot.unwrap_or_else(|| { - debug!(self.log, - "On add peers force range sync assuming local head_slot"; - "local_head_slot" => local.head_slot, - "head_root" => ?head_root + debug!( + local_head_slot = %local.head_slot, + ?head_root, + "On add peers force range sync assuming local head_slot" ); local.head_slot }); @@ -480,7 +478,7 @@ impl SyncManager { /// Handles RPC errors related to requests that were emitted from the sync manager. fn inject_error(&mut self, peer_id: PeerId, request_id: SyncRequestId, error: RPCError) { - trace!(self.log, "Sync manager received a failed RPC"); + trace!("Sync manager received a failed RPC"); match request_id { SyncRequestId::SingleBlock { id } => { self.on_single_block_response(id, peer_id, RpcEvent::RPCError(error)) @@ -560,15 +558,14 @@ impl SyncManager { let is_connected = self.network_globals().peers.read().is_connected(peer_id); if was_updated { debug!( - self.log, - "Peer transitioned sync state"; - "peer_id" => %peer_id, - "new_state" => rpr, - "our_head_slot" => local_sync_info.head_slot, - "our_finalized_epoch" => local_sync_info.finalized_epoch, - "their_head_slot" => remote_sync_info.head_slot, - "their_finalized_epoch" => remote_sync_info.finalized_epoch, - "is_connected" => is_connected + %peer_id, + new_state = rpr, + our_head_slot = %local_sync_info.head_slot, + our_finalized_epoch = %local_sync_info.finalized_epoch, + their_head_slot = %remote_sync_info.head_slot, + their_finalized_epoch = %remote_sync_info.finalized_epoch, + is_connected, + "Peer transitioned sync state" ); // A peer has transitioned its sync state. If the new state is "synced" we @@ -579,7 +576,7 @@ impl SyncManager { } is_connected } else { - error!(self.log, "Status'd peer is unknown"; "peer_id" => %peer_id); + error!(%peer_id, "Status'd peer is unknown"); false } } @@ -598,7 +595,7 @@ impl SyncManager { fn update_sync_state(&mut self) { let new_state: SyncState = match self.range_sync.state() { Err(e) => { - crit!(self.log, "Error getting range sync state"; "error" => %e); + crit!(error = %e, "Error getting range sync state"); return; } Ok(state) => match state { @@ -647,7 +644,7 @@ impl SyncManager { } Ok(SyncStart::NotSyncing) => {} // Ignore updating the state if the backfill sync state didn't start. Err(e) => { - error!(self.log, "Backfill sync failed to start"; "error" => ?e); + error!(error = ?e, "Backfill sync failed to start"); } } } @@ -681,7 +678,7 @@ impl SyncManager { let old_state = self.network_globals().set_sync_state(new_state); let new_state = self.network_globals().sync_state.read().clone(); if !new_state.eq(&old_state) { - info!(self.log, "Sync state updated"; "old_state" => %old_state, "new_state" => %new_state); + info!(%old_state, %new_state, "Sync state updated"); // If we have become synced - Subscribe to all the core subnet topics // We don't need to subscribe if the old state is a state that would have already // invoked this call. @@ -776,7 +773,7 @@ impl SyncManager { SyncMessage::UnknownParentBlock(peer_id, block, block_root) => { let block_slot = block.slot(); let parent_root = block.parent_root(); - debug!(self.log, "Received unknown parent block message"; "block_root" => %block_root, "parent_root" => %parent_root); + debug!(%block_root, %parent_root, "Received unknown parent block message"); self.handle_unknown_parent( peer_id, block_root, @@ -794,7 +791,7 @@ impl SyncManager { let blob_slot = blob.slot(); let block_root = blob.block_root(); let parent_root = blob.block_parent_root(); - debug!(self.log, "Received unknown parent blob message"; "block_root" => %block_root, "parent_root" => %parent_root); + debug!(%block_root, %parent_root, "Received unknown parent blob message"); self.handle_unknown_parent( peer_id, block_root, @@ -812,7 +809,7 @@ impl SyncManager { let data_column_slot = data_column.slot(); let block_root = data_column.block_root(); let parent_root = data_column.block_parent_root(); - debug!(self.log, "Received unknown parent data column message"; "block_root" => %block_root, "parent_root" => %parent_root); + debug!(%block_root, %parent_root, "Received unknown parent data column message"); self.handle_unknown_parent( peer_id, block_root, @@ -829,12 +826,12 @@ impl SyncManager { SyncMessage::UnknownBlockHashFromAttestation(peer_id, block_root) => { if !self.notified_unknown_roots.contains(&(peer_id, block_root)) { self.notified_unknown_roots.insert((peer_id, block_root)); - debug!(self.log, "Received unknown block hash message"; "block_root" => ?block_root, "peer" => ?peer_id); + debug!(?block_root, ?peer_id, "Received unknown block hash message"); self.handle_unknown_block_root(peer_id, block_root); } } SyncMessage::SampleBlock(block_root, block_slot) => { - debug!(self.log, "Received SampleBlock message"; "block_root" => %block_root, "slot" => block_slot); + debug!(%block_root, slot = %block_slot, "Received SampleBlock message"); if let Some((requester, result)) = self .sampling .on_new_sample_request(block_root, &mut self.network) @@ -843,7 +840,7 @@ impl SyncManager { } } SyncMessage::Disconnect(peer_id) => { - debug!(self.log, "Received disconnected message"; "peer_id" => %peer_id); + debug!(%peer_id, "Received disconnected message"); self.peer_disconnect(&peer_id); } SyncMessage::RpcError { @@ -884,7 +881,7 @@ impl SyncManager { Ok(ProcessResult::Successful) => {} Ok(ProcessResult::SyncCompleted) => self.update_sync_state(), Err(error) => { - error!(self.log, "Backfill sync failed"; "error" => ?error); + error!(error = ?error, "Backfill sync failed"); // Update the global status self.update_sync_state(); } @@ -920,7 +917,7 @@ impl SyncManager { ); } Err(reason) => { - debug!(self.log, "Ignoring unknown parent request"; "block_root" => %block_root, "parent_root" => %parent_root, "reason" => reason); + debug!(%block_root, %parent_root, reason, "Ignoring unknown parent request"); } } } @@ -932,7 +929,7 @@ impl SyncManager { .search_unknown_block(block_root, &[peer_id], &mut self.network); } Err(reason) => { - debug!(self.log, "Ignoring unknown block request"; "block_root" => %block_root, "reason" => reason); + debug!(%block_root, reason, "Ignoring unknown block request"); } } } @@ -1010,8 +1007,9 @@ impl SyncManager { // Some logs. if dropped_single_blocks_requests > 0 { - debug!(self.log, "Execution engine not online. Dropping active requests."; - "dropped_single_blocks_requests" => dropped_single_blocks_requests, + debug!( + dropped_single_blocks_requests, + "Execution engine not online. Dropping active requests." ); } } @@ -1037,7 +1035,7 @@ impl SyncManager { RpcEvent::from_chunk(block, seen_timestamp), ), _ => { - crit!(self.log, "bad request id for block"; "peer_id" => %peer_id ); + crit!(%peer_id, "bad request id for block"); } } } @@ -1079,7 +1077,7 @@ impl SyncManager { RpcEvent::from_chunk(blob, seen_timestamp), ), _ => { - crit!(self.log, "bad request id for blob"; "peer_id" => %peer_id); + crit!(%peer_id, "bad request id for blob"); } } } @@ -1105,7 +1103,7 @@ impl SyncManager { RpcEvent::from_chunk(data_column, seen_timestamp), ), _ => { - crit!(self.log, "bad request id for data_column"; "peer_id" => %peer_id); + crit!(%peer_id, "bad request id for data_column"); } } } @@ -1212,12 +1210,10 @@ impl SyncManager { requester: CustodyRequester, response: CustodyByRootResult, ) { - // TODO(das): get proper timestamp - let seen_timestamp = timestamp_now(); self.block_lookups .on_download_response::>( requester.0, - response.map(|(columns, peer_group)| (columns, peer_group, seen_timestamp)), + response, &mut self.network, ); } @@ -1225,7 +1221,7 @@ impl SyncManager { fn on_sampling_result(&mut self, requester: SamplingRequester, result: SamplingResult) { match requester { SamplingRequester::ImportedBlock(block_root) => { - debug!(self.log, "Sampling result"; "block_root" => %block_root, "result" => ?result); + debug!(%block_root, ?result, "Sampling result"); match result { Ok(_) => { @@ -1236,11 +1232,11 @@ impl SyncManager { .beacon_processor() .send_sampling_completed(block_root) { - warn!(self.log, "Error sending sampling result"; "block_root" => ?block_root, "reason" => ?e); + warn!(?block_root, reason = ?e, "Error sending sampling result"); } } Err(e) => { - warn!(self.log, "Sampling failed"; "block_root" => %block_root, "reason" => ?e); + warn!(?block_root, reason = ?e, "Sampling failed"); } } } diff --git a/beacon_node/network/src/sync/network_context.rs b/beacon_node/network/src/sync/network_context.rs index b03a446add..68a963dd41 100644 --- a/beacon_node/network/src/sync/network_context.rs +++ b/beacon_node/network/src/sync/network_context.rs @@ -34,13 +34,13 @@ use requests::{ ActiveRequests, BlobsByRangeRequestItems, BlobsByRootRequestItems, BlocksByRangeRequestItems, BlocksByRootRequestItems, DataColumnsByRangeRequestItems, DataColumnsByRootRequestItems, }; -use slog::{debug, error, warn}; use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc; +use tracing::{debug, error, span, warn, Level}; use types::blob_sidecar::FixedBlobSidecarList; use types::{ BlobSidecar, ColumnIndex, DataColumnSidecar, DataColumnSidecarList, EthSpec, ForkContext, @@ -68,14 +68,16 @@ impl RpcEvent { pub type RpcResponseResult = Result<(T, Duration), RpcResponseError>; -pub type CustodyByRootResult = Result<(DataColumnSidecarList, PeerGroup), RpcResponseError>; +/// Duration = latest seen timestamp of all received data columns +pub type CustodyByRootResult = + Result<(DataColumnSidecarList, PeerGroup, Duration), RpcResponseError>; #[derive(Debug)] pub enum RpcResponseError { - RpcError(RPCError), + RpcError(#[allow(dead_code)] RPCError), VerifyError(LookupVerifyError), - CustodyRequestError(CustodyRequestError), - BlockComponentCouplingError(String), + CustodyRequestError(#[allow(dead_code)] CustodyRequestError), + BlockComponentCouplingError(#[allow(dead_code)] String), } #[derive(Debug, PartialEq, Eq)] @@ -87,6 +89,19 @@ pub enum RpcRequestSendError { SlotClockError, } +impl std::fmt::Display for RpcRequestSendError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + RpcRequestSendError::NetworkSendError => write!(f, "Network send error"), + RpcRequestSendError::NoCustodyPeers => write!(f, "No custody peers"), + RpcRequestSendError::CustodyRequestError(e) => { + write!(f, "Custody request error: {:?}", e) + } + RpcRequestSendError::SlotClockError => write!(f, "Slot clock error"), + } + } +} + #[derive(Debug, PartialEq, Eq)] pub enum SendErrorProcessor { SendError, @@ -199,9 +214,6 @@ pub struct SyncNetworkContext { pub chain: Arc>, fork_context: Arc, - - /// Logger for the `SyncNetworkContext`. - pub log: slog::Logger, } /// Small enumeration to make dealing with block and blob requests easier. @@ -217,8 +229,13 @@ impl SyncNetworkContext { network_beacon_processor: Arc>, chain: Arc>, fork_context: Arc, - log: slog::Logger, ) -> Self { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); SyncNetworkContext { network_send, execution_engine_state: EngineState::Online, // always assume `Online` at the start @@ -234,7 +251,6 @@ impl SyncNetworkContext { network_beacon_processor, chain, fork_context, - log, } } @@ -265,7 +281,6 @@ impl SyncNetworkContext { network_beacon_processor: _, chain: _, fork_context: _, - log: _, } = self; let blocks_by_root_ids = blocks_by_root_requests @@ -328,17 +343,23 @@ impl SyncNetworkContext { } pub fn status_peers(&self, chain: &C, peers: impl Iterator) { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let status_message = chain.status_message(); for peer_id in peers { debug!( - self.log, - "Sending Status Request"; - "peer" => %peer_id, - "fork_digest" => ?status_message.fork_digest, - "finalized_root" => ?status_message.finalized_root, - "finalized_epoch" => ?status_message.finalized_epoch, - "head_root" => %status_message.head_root, - "head_slot" => %status_message.head_slot, + peer = %peer_id, + fork_digest = ?status_message.fork_digest, + finalized_root = ?status_message.finalized_root, + finalized_epoch = ?status_message.finalized_epoch, + head_root = %status_message.head_root, + head_slot = %status_message.head_slot, + "Sending Status Request" ); let request = RequestType::Status(status_message.clone()); @@ -383,7 +404,6 @@ impl SyncNetworkContext { let (expects_columns, data_column_requests) = if matches!(batch_type, ByRangeRequestType::BlocksAndColumns) { let column_indexes = self.network_globals().sampling_columns.clone(); - let data_column_requests = self .make_columns_by_range_requests(request, &column_indexes)? .into_iter() @@ -516,6 +536,13 @@ impl SyncNetworkContext { return Ok(LookupRequestResult::Pending("no peers")); }; + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + match self.chain.get_block_process_status(&block_root) { // Unknown block, continue request to download BlockProcessStatus::Unknown => {} @@ -558,12 +585,11 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "BlocksByRoot", - "block_root" => ?block_root, - "peer" => %peer_id, - "id" => %id + method = "BlocksByRoot", + ?block_root, + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.blocks_by_root_requests.insert( @@ -606,6 +632,13 @@ impl SyncNetworkContext { return Ok(LookupRequestResult::Pending("no peers")); }; + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let imported_blob_indexes = self .chain .data_availability_checker @@ -641,13 +674,12 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "BlobsByRoot", - "block_root" => ?block_root, - "blob_indices" => ?indices, - "peer" => %peer_id, - "id" => %id + method = "BlobsByRoot", + ?block_root, + blob_indices = ?indices, + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.blobs_by_root_requests.insert( @@ -671,6 +703,13 @@ impl SyncNetworkContext { request: DataColumnsByRootSingleBlockRequest, expect_max_responses: bool, ) -> Result, &'static str> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let id = DataColumnsByRootRequestId { id: self.next_id(), requester, @@ -683,13 +722,12 @@ impl SyncNetworkContext { })?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "DataColumnsByRoot", - "block_root" => ?request.block_root, - "indices" => ?request.indices, - "peer" => %peer_id, - "id" => %id, + method = "DataColumnsByRoot", + block_root = ?request.block_root, + indices = ?request.indices, + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.data_columns_by_root_requests.insert( @@ -712,6 +750,13 @@ impl SyncNetworkContext { block_root: Hash256, lookup_peers: Arc>>, ) -> Result { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let custody_indexes_imported = self .chain .data_availability_checker @@ -738,11 +783,10 @@ impl SyncNetworkContext { }; debug!( - self.log, - "Starting custody columns request"; - "block_root" => ?block_root, - "indices" => ?custody_indexes_to_fetch, - "id" => %id + ?block_root, + indices = ?custody_indexes_to_fetch, + %id, + "Starting custody columns request" ); let requester = CustodyRequester(id); @@ -751,7 +795,6 @@ impl SyncNetworkContext { CustodyId { requester }, &custody_indexes_to_fetch, lookup_peers, - self.log.clone(), ); // Note that you can only send, but not handle a response here @@ -786,13 +829,12 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "BlocksByRange", - "slots" => request.count(), - "epoch" => Slot::new(*request.start_slot()).epoch(T::EthSpec::slots_per_epoch()), - "peer" => %peer_id, - "id" => %id, + method = "BlocksByRange", + slots = request.count(), + epoch = %Slot::new(*request.start_slot()).epoch(T::EthSpec::slots_per_epoch()), + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.blocks_by_range_requests.insert( @@ -828,13 +870,12 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "BlobsByRange", - "slots" => request.count, - "epoch" => request_epoch, - "peer" => %peer_id, - "id" => %id, + method = "BlobsByRange", + slots = request.count, + epoch = %request_epoch, + peer = %peer_id, + %id, + "Sync RPC request sent" ); let max_blobs_per_block = self.chain.spec.max_blobs_per_block(request_epoch); @@ -868,14 +909,13 @@ impl SyncNetworkContext { .map_err(|_| RpcRequestSendError::NetworkSendError)?; debug!( - self.log, - "Sync RPC request sent"; - "method" => "DataColumnsByRange", - "slots" => request.count, - "epoch" => Slot::new(request.start_slot).epoch(T::EthSpec::slots_per_epoch()), - "columns" => ?request.columns, - "peer" => %peer_id, - "id" => %id, + method = "DataColumnsByRange", + slots = request.count, + epoch = %Slot::new(request.start_slot).epoch(T::EthSpec::slots_per_epoch()), + columns = ?request.columns, + peer = %peer_id, + %id, + "Sync RPC request sent" ); self.data_columns_by_range_requests.insert( @@ -894,13 +934,26 @@ impl SyncNetworkContext { } pub fn update_execution_engine_state(&mut self, engine_state: EngineState) { - debug!(self.log, "Sync's view on execution engine state updated"; - "past_state" => ?self.execution_engine_state, "new_state" => ?engine_state); + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + + debug!(past_state = ?self.execution_engine_state, new_state = ?engine_state, "Sync's view on execution engine state updated"); self.execution_engine_state = engine_state; } /// Terminates the connection with the peer and bans them. pub fn goodbye_peer(&mut self, peer_id: PeerId, reason: GoodbyeReason) { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + self.network_send .send(NetworkMessage::GoodbyePeer { peer_id, @@ -908,13 +961,20 @@ impl SyncNetworkContext { source: ReportSource::SyncService, }) .unwrap_or_else(|_| { - warn!(self.log, "Could not report peer: channel failed"); + warn!("Could not report peer: channel failed"); }); } /// Reports to the scoring algorithm the behaviour of a peer. pub fn report_peer(&self, peer_id: PeerId, action: PeerAction, msg: &'static str) { - debug!(self.log, "Sync reporting peer"; "peer_id" => %peer_id, "action" => %action, "msg" => %msg); + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + + debug!(%peer_id, %action, %msg, "Sync reporting peer"); self.network_send .send(NetworkMessage::ReportPeer { peer_id, @@ -923,23 +983,37 @@ impl SyncNetworkContext { msg, }) .unwrap_or_else(|e| { - warn!(self.log, "Could not report peer: channel failed"; "error"=> %e); + warn!(error = %e, "Could not report peer: channel failed"); }); } /// Subscribes to core topics. pub fn subscribe_core_topics(&self) { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + self.network_send .send(NetworkMessage::SubscribeCoreTopics) .unwrap_or_else(|e| { - warn!(self.log, "Could not subscribe to core topics."; "error" => %e); + warn!(error = %e, "Could not subscribe to core topics."); }); } /// Sends an arbitrary network message. fn send_network_msg(&self, msg: NetworkMessage) -> Result<(), &'static str> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + self.network_send.send(msg).map_err(|_| { - debug!(self.log, "Could not send message to the network service"); + debug!("Could not send message to the network service"); "Network channel send Failed" }) } @@ -1126,20 +1200,18 @@ impl SyncNetworkContext { None => {} Some(Ok((v, _))) => { debug!( - self.log, - "Sync RPC request completed"; - "id" => %id, - "method" => method, - "count" => get_count(v) + %id, + method, + count = get_count(v), + "Sync RPC request completed" ); } Some(Err(e)) => { debug!( - self.log, - "Sync RPC request error"; - "id" => %id, - "method" => method, - "error" => ?e + %id, + method, + error = ?e, + "Sync RPC request error" ); } } @@ -1164,11 +1236,18 @@ impl SyncNetworkContext { peer_id: PeerId, resp: RpcResponseResult>>>, ) -> Option> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + // Note: need to remove the request to borrow self again below. Otherwise we can't // do nested requests let Some(mut request) = self.custody_by_root_requests.remove(&id.requester) else { // TOOD(das): This log can happen if the request is error'ed early and dropped - debug!(self.log, "Custody column downloaded event for unknown request"; "id" => ?id); + debug!(?id, "Custody column downloaded event for unknown request"); return None; }; @@ -1183,6 +1262,13 @@ impl SyncNetworkContext { request: ActiveCustodyRequest, result: CustodyRequestResult, ) -> Option> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let result = result .map_err(RpcResponseError::CustodyRequestError) .transpose(); @@ -1190,11 +1276,11 @@ impl SyncNetworkContext { // Convert a result from internal format of `ActiveCustodyRequest` (error first to use ?) to // an Option first to use in an `if let Some() { act on result }` block. match result.as_ref() { - Some(Ok((columns, peer_group))) => { - debug!(self.log, "Custody request success, removing"; "id" => ?id, "count" => columns.len(), "peers" => ?peer_group) + Some(Ok((columns, peer_group, _))) => { + debug!(?id, count = columns.len(), peers = ?peer_group, "Custody request success, removing") } Some(Err(e)) => { - debug!(self.log, "Custody request failure, removing"; "id" => ?id, "error" => ?e) + debug!(?id, error = ?e, "Custody request failure, removing" ) } None => { self.custody_by_root_requests.insert(id, request); @@ -1208,27 +1294,33 @@ impl SyncNetworkContext { id: Id, block_root: Hash256, block: RpcBlock, - duration: Duration, + seen_timestamp: Duration, ) -> Result<(), SendErrorProcessor> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let beacon_processor = self .beacon_processor_if_enabled() .ok_or(SendErrorProcessor::ProcessorNotAvailable)?; - debug!(self.log, "Sending block for processing"; "block" => ?block_root, "id" => id); + debug!(block = ?block_root, id, "Sending block for processing"); // Lookup sync event safety: If `beacon_processor.send_rpc_beacon_block` returns Ok() sync // must receive a single `SyncMessage::BlockComponentProcessed` with this process type beacon_processor .send_rpc_beacon_block( block_root, block, - duration, + seen_timestamp, BlockProcessType::SingleBlock { id }, ) .map_err(|e| { error!( - self.log, - "Failed to send sync block to processor"; - "error" => ?e + error = ?e, + "Failed to send sync block to processor" ); SendErrorProcessor::SendError }) @@ -1239,27 +1331,33 @@ impl SyncNetworkContext { id: Id, block_root: Hash256, blobs: FixedBlobSidecarList, - duration: Duration, + seen_timestamp: Duration, ) -> Result<(), SendErrorProcessor> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let beacon_processor = self .beacon_processor_if_enabled() .ok_or(SendErrorProcessor::ProcessorNotAvailable)?; - debug!(self.log, "Sending blobs for processing"; "block" => ?block_root, "id" => id); + debug!(?block_root, ?id, "Sending blobs for processing"); // Lookup sync event safety: If `beacon_processor.send_rpc_blobs` returns Ok() sync // must receive a single `SyncMessage::BlockComponentProcessed` event with this process type beacon_processor .send_rpc_blobs( block_root, blobs, - duration, + seen_timestamp, BlockProcessType::SingleBlob { id }, ) .map_err(|e| { error!( - self.log, - "Failed to send sync blobs to processor"; - "error" => ?e + error = ?e, + "Failed to send sync blobs to processor" ); SendErrorProcessor::SendError }) @@ -1270,22 +1368,32 @@ impl SyncNetworkContext { _id: Id, block_root: Hash256, custody_columns: DataColumnSidecarList, - duration: Duration, + seen_timestamp: Duration, process_type: BlockProcessType, ) -> Result<(), SendErrorProcessor> { + let span = span!( + Level::INFO, + "SyncNetworkContext", + service = "network_context" + ); + let _enter = span.enter(); + let beacon_processor = self .beacon_processor_if_enabled() .ok_or(SendErrorProcessor::ProcessorNotAvailable)?; - debug!(self.log, "Sending custody columns for processing"; "block" => ?block_root, "process_type" => ?process_type); + debug!( + ?block_root, + ?process_type, + "Sending custody columns for processing" + ); beacon_processor - .send_rpc_custody_columns(block_root, custody_columns, duration, process_type) + .send_rpc_custody_columns(block_root, custody_columns, seen_timestamp, process_type) .map_err(|e| { error!( - self.log, - "Failed to send sync custody columns to processor"; - "error" => ?e + error = ?e, + "Failed to send sync custody columns to processor" ); SendErrorProcessor::SendError }) diff --git a/beacon_node/network/src/sync/network_context/custody.rs b/beacon_node/network/src/sync/network_context/custody.rs index 8a29545c21..018381a850 100644 --- a/beacon_node/network/src/sync/network_context/custody.rs +++ b/beacon_node/network/src/sync/network_context/custody.rs @@ -1,7 +1,7 @@ use crate::sync::network_context::{ DataColumnsByRootRequestId, DataColumnsByRootSingleBlockRequest, }; - +use beacon_chain::validator_monitor::timestamp_now; use beacon_chain::BeaconChainTypes; use fnv::FnvHashMap; use lighthouse_network::service::api_types::{CustodyId, DataColumnsByRootRequester}; @@ -9,10 +9,10 @@ use lighthouse_network::PeerId; use lru_cache::LRUTimeCache; use parking_lot::RwLock; use rand::Rng; -use slog::{debug, warn}; use std::collections::HashSet; use std::time::{Duration, Instant}; use std::{collections::HashMap, marker::PhantomData, sync::Arc}; +use tracing::{debug, warn}; use types::EthSpec; use types::{data_column_sidecar::ColumnIndex, DataColumnSidecar, Hash256}; @@ -36,8 +36,7 @@ pub struct ActiveCustodyRequest { failed_peers: LRUTimeCache, /// Set of peers that claim to have imported this block and their custody columns lookup_peers: Arc>>, - /// Logger for the `SyncNetworkContext`. - pub log: slog::Logger, + _phantom: PhantomData, } @@ -61,7 +60,8 @@ struct ActiveBatchColumnsRequest { indices: Vec, } -pub type CustodyRequestResult = Result, PeerGroup)>, Error>; +pub type CustodyRequestResult = + Result, PeerGroup, Duration)>, Error>; impl ActiveCustodyRequest { pub(crate) fn new( @@ -69,7 +69,6 @@ impl ActiveCustodyRequest { custody_id: CustodyId, column_indices: &[ColumnIndex], lookup_peers: Arc>>, - log: slog::Logger, ) -> Self { Self { block_root, @@ -82,7 +81,6 @@ impl ActiveCustodyRequest { active_batch_columns_requests: <_>::default(), failed_peers: LRUTimeCache::new(Duration::from_secs(FAILED_PEERS_CACHE_EXPIRY_SECONDS)), lookup_peers, - log, _phantom: PhantomData, } } @@ -102,27 +100,25 @@ impl ActiveCustodyRequest { resp: RpcResponseResult>, cx: &mut SyncNetworkContext, ) -> CustodyRequestResult { - // TODO(das): Should downscore peers for verify errors here - let Some(batch_request) = self.active_batch_columns_requests.get_mut(&req_id) else { - warn!(self.log, - "Received custody column response for unrequested index"; - "id" => ?self.custody_id, - "block_root" => ?self.block_root, - "req_id" => %req_id, + warn!( + id = ?self.custody_id, + block_root = ?self.block_root, + %req_id, + "Received custody column response for unrequested index" ); return Ok(None); }; match resp { - Ok((data_columns, _seen_timestamp)) => { - debug!(self.log, - "Custody column download success"; - "id" => ?self.custody_id, - "block_root" => ?self.block_root, - "req_id" => %req_id, - "peer" => %peer_id, - "count" => data_columns.len() + Ok((data_columns, seen_timestamp)) => { + debug!( + id = ?self.custody_id, + block_root = ?self.block_root, + %req_id, + %peer_id, + count = data_columns.len(), + "Custody column download success" ); // Map columns by index as an optimization to not loop the returned list on each @@ -141,7 +137,12 @@ impl ActiveCustodyRequest { .ok_or(Error::BadState("unknown column_index".to_owned()))?; if let Some(data_column) = data_columns.remove(column_index) { - column_request.on_download_success(req_id, peer_id, data_column)?; + column_request.on_download_success( + req_id, + peer_id, + data_column, + seen_timestamp, + )?; } else { // Peer does not have the requested data. // TODO(das) do not consider this case a success. We know for sure the block has @@ -159,27 +160,27 @@ impl ActiveCustodyRequest { if !missing_column_indexes.is_empty() { // Note: Batch logging that columns are missing to not spam logger - debug!(self.log, - "Custody column peer claims to not have some data"; - "id" => ?self.custody_id, - "block_root" => ?self.block_root, - "req_id" => %req_id, - "peer" => %peer_id, + debug!( + id = ?self.custody_id, + block_root = ?self.block_root, + %req_id, + %peer_id, // TODO(das): this property can become very noisy, being the full range 0..128 - "missing_column_indexes" => ?missing_column_indexes + ?missing_column_indexes, + "Custody column peer claims to not have some data" ); self.failed_peers.insert(peer_id); } } Err(err) => { - debug!(self.log, - "Custody column download error"; - "id" => ?self.custody_id, - "block_root" => ?self.block_root, - "req_id" => %req_id, - "peer" => %peer_id, - "error" => ?err + debug!( + id = ?self.custody_id, + block_root = ?self.block_root, + %req_id, + %peer_id, + error = ?err, + "Custody column download error" ); // TODO(das): Should mark peer as failed and try from another peer @@ -204,20 +205,23 @@ impl ActiveCustodyRequest { if self.column_requests.values().all(|r| r.is_downloaded()) { // All requests have completed successfully. let mut peers = HashMap::>::new(); + let mut seen_timestamps = vec![]; let columns = std::mem::take(&mut self.column_requests) .into_values() .map(|request| { - let (peer, data_column) = request.complete()?; + let (peer, data_column, seen_timestamp) = request.complete()?; peers .entry(peer) .or_default() .push(data_column.index as usize); + seen_timestamps.push(seen_timestamp); Ok(data_column) }) .collect::, _>>()?; let peer_group = PeerGroup::from_set(peers); - return Ok(Some((columns, peer_group))); + let max_seen_timestamp = seen_timestamps.into_iter().max().unwrap_or(timestamp_now()); + return Ok(Some((columns, peer_group, max_seen_timestamp))); } let mut columns_to_request_by_peer = HashMap::>::new(); @@ -335,7 +339,7 @@ struct ColumnRequest { enum Status { NotStarted(Instant), Downloading(DataColumnsByRootRequestId), - Downloaded(PeerId, Arc>), + Downloaded(PeerId, Arc>, Duration), } impl ColumnRequest { @@ -404,6 +408,7 @@ impl ColumnRequest { req_id: DataColumnsByRootRequestId, peer_id: PeerId, data_column: Arc>, + seen_timestamp: Duration, ) -> Result<(), Error> { match &self.status { Status::Downloading(expected_req_id) => { @@ -413,7 +418,7 @@ impl ColumnRequest { req_id, }); } - self.status = Status::Downloaded(peer_id, data_column); + self.status = Status::Downloaded(peer_id, data_column, seen_timestamp); Ok(()) } other => Err(Error::BadState(format!( @@ -422,9 +427,11 @@ impl ColumnRequest { } } - fn complete(self) -> Result<(PeerId, Arc>), Error> { + fn complete(self) -> Result<(PeerId, Arc>, Duration), Error> { match self.status { - Status::Downloaded(peer_id, data_column) => Ok((peer_id, data_column)), + Status::Downloaded(peer_id, data_column, seen_timestamp) => { + Ok((peer_id, data_column, seen_timestamp)) + } other => Err(Error::BadState(format!( "bad state complete expected Downloaded got {other:?}" ))), diff --git a/beacon_node/network/src/sync/peer_sampling.rs b/beacon_node/network/src/sync/peer_sampling.rs index 289ed73cdd..59b751787e 100644 --- a/beacon_node/network/src/sync/peer_sampling.rs +++ b/beacon_node/network/src/sync/peer_sampling.rs @@ -12,11 +12,11 @@ use lighthouse_network::service::api_types::{ }; use lighthouse_network::{PeerAction, PeerId}; use rand::{seq::SliceRandom, thread_rng}; -use slog::{debug, error, warn}; use std::{ collections::hash_map::Entry, collections::HashMap, marker::PhantomData, sync::Arc, time::Duration, }; +use tracing::{debug, error, instrument, warn}; use types::{data_column_sidecar::ColumnIndex, ChainSpec, DataColumnSidecar, Hash256}; pub type SamplingResult = Result<(), SamplingError>; @@ -26,24 +26,35 @@ type DataColumnSidecarList = Vec>>; pub struct Sampling { requests: HashMap>, sampling_config: SamplingConfig, - log: slog::Logger, } impl Sampling { - pub fn new(sampling_config: SamplingConfig, log: slog::Logger) -> Self { + #[instrument(parent = None,level = "info", fields(service = "sampling"), name = "sampling")] + pub fn new(sampling_config: SamplingConfig) -> Self { Self { requests: <_>::default(), sampling_config, - log, } } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn active_sampling_requests(&self) -> Vec { self.requests.values().map(|r| r.block_root).collect() } #[cfg(test)] + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn get_request_status( &self, block_root: Hash256, @@ -61,6 +72,12 @@ impl Sampling { /// /// - `Some`: Request completed, won't make more progress. Expect requester to act on the result. /// - `None`: Request still active, requester should do no action + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn on_new_sample_request( &mut self, block_root: Hash256, @@ -73,7 +90,6 @@ impl Sampling { block_root, id, &self.sampling_config, - self.log.clone(), &cx.chain.spec, )), Entry::Occupied(_) => { @@ -82,15 +98,15 @@ impl Sampling { // TODO(das): Should track failed sampling request for some time? Otherwise there's // a risk of a loop with multiple triggers creating the request, then failing, // and repeat. - debug!(self.log, "Ignoring duplicate sampling request"; "id" => ?id); + debug!(?id, "Ignoring duplicate sampling request"); return None; } }; - debug!(self.log, - "Created new sample request"; - "id" => ?id, - "column_selection" => ?request.column_selection() + debug!( + ?id, + column_selection = ?request.column_selection(), + "Created new sample request" ); // TOOD(das): If a node has very little peers, continue_sampling() will attempt to find enough @@ -107,6 +123,12 @@ impl Sampling { /// /// - `Some`: Request completed, won't make more progress. Expect requester to act on the result. /// - `None`: Request still active, requester should do no action + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn on_sample_downloaded( &mut self, id: SamplingId, @@ -116,7 +138,7 @@ impl Sampling { ) -> Option<(SamplingRequester, SamplingResult)> { let Some(request) = self.requests.get_mut(&id.id) else { // TOOD(das): This log can happen if the request is error'ed early and dropped - debug!(self.log, "Sample downloaded event for unknown request"; "id" => ?id); + debug!(?id, "Sample downloaded event for unknown request"); return None; }; @@ -131,6 +153,12 @@ impl Sampling { /// /// - `Some`: Request completed, won't make more progress. Expect requester to act on the result. /// - `None`: Request still active, requester should do no action + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] pub fn on_sample_verified( &mut self, id: SamplingId, @@ -139,7 +167,7 @@ impl Sampling { ) -> Option<(SamplingRequester, SamplingResult)> { let Some(request) = self.requests.get_mut(&id.id) else { // TOOD(das): This log can happen if the request is error'ed early and dropped - debug!(self.log, "Sample verified event for unknown request"; "id" => ?id); + debug!(?id, "Sample verified event for unknown request"); return None; }; @@ -150,6 +178,12 @@ impl Sampling { /// Converts a result from the internal format of `ActiveSamplingRequest` (error first to use ? /// conveniently), to an Option first format to use an `if let Some() { act on result }` pattern /// in the sync manager. + #[instrument(parent = None, + level = "info", + fields(service = "sampling"), + name = "sampling", + skip_all + )] fn handle_sampling_result( &mut self, result: Result, SamplingError>, @@ -157,7 +191,7 @@ impl Sampling { ) -> Option<(SamplingRequester, SamplingResult)> { let result = result.transpose(); if let Some(result) = result { - debug!(self.log, "Sampling request completed, removing"; "id" => ?id, "result" => ?result); + debug!(?id, ?result, "Sampling request completed, removing"); metrics::inc_counter_vec( &metrics::SAMPLING_REQUEST_RESULT, &[metrics::from_result(&result)], @@ -180,8 +214,6 @@ pub struct ActiveSamplingRequest { current_sampling_request_id: SamplingRequestId, column_shuffle: Vec, required_successes: Vec, - /// Logger for the `SyncNetworkContext`. - pub log: slog::Logger, _phantom: PhantomData, } @@ -212,7 +244,6 @@ impl ActiveSamplingRequest { block_root: Hash256, requester_id: SamplingRequester, sampling_config: &SamplingConfig, - log: slog::Logger, spec: &ChainSpec, ) -> Self { // Select ahead of time the full list of to-sample columns @@ -232,7 +263,6 @@ impl ActiveSamplingRequest { SamplingConfig::Default => REQUIRED_SUCCESSES.to_vec(), SamplingConfig::Custom { required_successes } => required_successes.clone(), }, - log, _phantom: PhantomData, } } @@ -275,9 +305,9 @@ impl ActiveSamplingRequest { .column_indexes_by_sampling_request .get(&sampling_request_id) else { - error!(self.log, - "Column indexes for the sampling request ID not found"; - "sampling_request_id" => ?sampling_request_id + error!( + ?sampling_request_id, + "Column indexes for the sampling request ID not found" ); return Ok(None); }; @@ -288,11 +318,11 @@ impl ActiveSamplingRequest { .iter() .map(|r| r.index) .collect::>(); - debug!(self.log, - "Sample download success"; - "block_root" => %self.block_root, - "column_indexes" => ?resp_column_indexes, - "count" => resp_data_columns.len() + debug!( + block_root = %self.block_root, + column_indexes = ?resp_column_indexes, + count = resp_data_columns.len(), + "Sample download success" ); metrics::inc_counter_vec(&metrics::SAMPLE_DOWNLOAD_RESULT, &[metrics::SUCCESS]); @@ -300,10 +330,10 @@ impl ActiveSamplingRequest { let mut data_columns = vec![]; for column_index in column_indexes { let Some(request) = self.column_requests.get_mut(column_index) else { - warn!(self.log, - "Active column sample request not found"; - "block_root" => %self.block_root, - "column_index" => column_index + warn!( + block_root = %self.block_root, + column_index, + "Active column sample request not found" ); continue; }; @@ -314,10 +344,10 @@ impl ActiveSamplingRequest { else { // Peer does not have the requested data, mark peer as "dont have" and try // again with a different peer. - debug!(self.log, - "Sampling peer claims to not have the data"; - "block_root" => %self.block_root, - "column_index" => column_index + debug!( + block_root = %self.block_root, + column_index, + "Sampling peer claims to not have the data" ); request.on_sampling_error()?; continue; @@ -331,16 +361,16 @@ impl ActiveSamplingRequest { .iter() .map(|d| d.index) .collect::>(); - debug!(self.log, - "Received data that was not requested"; - "block_root" => %self.block_root, - "column_indexes" => ?resp_column_indexes + debug!( + block_root = %self.block_root, + column_indexes = ?resp_column_indexes, + "Received data that was not requested" ); } // Handle the downloaded data columns. if data_columns.is_empty() { - debug!(self.log, "Received empty response"; "block_root" => %self.block_root); + debug!(block_root = %self.block_root, "Received empty response"); self.column_indexes_by_sampling_request .remove(&sampling_request_id); } else { @@ -351,17 +381,17 @@ impl ActiveSamplingRequest { // Peer has data column, send to verify let Some(beacon_processor) = cx.beacon_processor_if_enabled() else { // If processor is not available, error the entire sampling - debug!(self.log, - "Dropping sampling"; - "block" => %self.block_root, - "reason" => "beacon processor unavailable" + debug!( + block = %self.block_root, + reason = "beacon processor unavailable", + "Dropping sampling" ); return Err(SamplingError::ProcessorUnavailable); }; - debug!(self.log, - "Sending data_column for verification"; - "block" => ?self.block_root, - "column_indexes" => ?column_indexes + debug!( + block = ?self.block_root, + ?column_indexes, + "Sending data_column for verification" ); if let Err(e) = beacon_processor.send_rpc_validate_data_columns( self.block_root, @@ -375,20 +405,21 @@ impl ActiveSamplingRequest { // Beacon processor is overloaded, drop sampling attempt. Failing to sample // is not a permanent state so we should recover once the node has capacity // and receives a descendant block. - error!(self.log, - "Dropping sampling"; - "block" => %self.block_root, - "reason" => e.to_string() + error!( + block = %self.block_root, + reason = e.to_string(), + "Dropping sampling" ); return Err(SamplingError::SendFailed("beacon processor send failure")); } } } Err(err) => { - debug!(self.log, "Sample download error"; - "block_root" => %self.block_root, - "column_indexes" => ?column_indexes, - "error" => ?err + debug!( + block_root = %self.block_root, + ?column_indexes, + error = ?err, + "Sample download error" ); metrics::inc_counter_vec(&metrics::SAMPLE_DOWNLOAD_RESULT, &[metrics::FAILURE]); @@ -396,10 +427,10 @@ impl ActiveSamplingRequest { // reaching this function. Mark the peer as failed and try again with another. for column_index in column_indexes { let Some(request) = self.column_requests.get_mut(column_index) else { - warn!(self.log, - "Active column sample request not found"; - "block_root" => %self.block_root, - "column_index" => column_index + warn!( + block_root = %self.block_root, + column_index, + "Active column sample request not found" ); continue; }; @@ -429,21 +460,24 @@ impl ActiveSamplingRequest { .column_indexes_by_sampling_request .get(&sampling_request_id) else { - error!(self.log, "Column indexes for the sampling request ID not found"; "sampling_request_id" => ?sampling_request_id); + error!( + ?sampling_request_id, + "Column indexes for the sampling request ID not found" + ); return Ok(None); }; match result { Ok(_) => { - debug!(self.log, "Sample verification success"; "block_root" => %self.block_root, "column_indexes" => ?column_indexes); + debug!(block_root = %self.block_root,?column_indexes, "Sample verification success"); metrics::inc_counter_vec(&metrics::SAMPLE_VERIFY_RESULT, &[metrics::SUCCESS]); // Valid, continue_sampling will maybe consider sampling succees for column_index in column_indexes { let Some(request) = self.column_requests.get_mut(column_index) else { warn!( - self.log, - "Active column sample request not found"; "block_root" => %self.block_root, "column_index" => column_index + block_root = %self.block_root, column_index, + "Active column sample request not found" ); continue; }; @@ -451,7 +485,7 @@ impl ActiveSamplingRequest { } } Err(err) => { - debug!(self.log, "Sample verification failure"; "block_root" => %self.block_root, "column_indexes" => ?column_indexes, "reason" => ?err); + debug!(block_root = %self.block_root, ?column_indexes, reason = ?err, "Sample verification failure"); metrics::inc_counter_vec(&metrics::SAMPLE_VERIFY_RESULT, &[metrics::FAILURE]); // Peer sent invalid data, penalize and try again from different peer @@ -459,8 +493,9 @@ impl ActiveSamplingRequest { for column_index in column_indexes { let Some(request) = self.column_requests.get_mut(column_index) else { warn!( - self.log, - "Active column sample request not found"; "block_root" => %self.block_root, "column_index" => column_index + block_root = %self.block_root, + column_index, + "Active column sample request not found" ); continue; }; @@ -570,7 +605,7 @@ impl ActiveSamplingRequest { // request was sent, loop to increase the required_successes until the sampling fails if // there are no peers. if ongoings == 0 && !sent_request { - debug!(self.log, "Sampling request stalled"; "block_root" => %self.block_root); + debug!(block_root = %self.block_root, "Sampling request stalled"); } Ok(None) diff --git a/beacon_node/network/src/sync/range_sync/batch.rs b/beacon_node/network/src/sync/range_sync/batch.rs index 912287a8a4..c1ad550376 100644 --- a/beacon_node/network/src/sync/range_sync/batch.rs +++ b/beacon_node/network/src/sync/range_sync/batch.rs @@ -3,6 +3,7 @@ use lighthouse_network::rpc::methods::BlocksByRangeRequest; use lighthouse_network::service::api_types::Id; use lighthouse_network::PeerId; use std::collections::HashSet; +use std::fmt; use std::hash::{Hash, Hasher}; use std::ops::Sub; use std::time::{Duration, Instant}; @@ -61,6 +62,7 @@ pub trait BatchConfig { fn batch_attempt_hash(blocks: &[RpcBlock]) -> u64; } +#[derive(Debug)] pub struct RangeSyncBatchConfig {} impl BatchConfig for RangeSyncBatchConfig { @@ -93,6 +95,7 @@ pub enum BatchProcessingResult { NonFaultyFailure, } +#[derive(Debug)] /// A segment of a chain. pub struct BatchInfo { /// Start slot of the batch. @@ -113,6 +116,17 @@ pub struct BatchInfo { marker: std::marker::PhantomData, } +impl fmt::Display for BatchInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Start Slot: {}, End Slot: {}, State: {}", + self.start_slot, self.end_slot, self.state + ) + } +} + +#[derive(Display)] /// Current state of a batch pub enum BatchState { /// The batch has failed either downloading or processing, but can be requested again. @@ -190,15 +204,6 @@ impl BatchInfo { peers } - /// Return the number of times this batch has failed downloading and failed processing, in this - /// order. - pub fn failed_attempts(&self) -> (usize, usize) { - ( - self.failed_download_attempts.len(), - self.failed_processing_attempts.len(), - ) - } - /// Verifies if an incoming block belongs to this batch. pub fn is_expecting_block(&self, request_id: &Id) -> bool { if let BatchState::Downloading(_, expected_id) = &self.state { @@ -456,39 +461,6 @@ impl Attempt { } } -impl slog::KV for &mut BatchInfo { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - slog::KV::serialize(*self, record, serializer) - } -} - -impl slog::KV for BatchInfo { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - use slog::Value; - Value::serialize(&self.start_slot, record, "start_slot", serializer)?; - Value::serialize( - &(self.end_slot - 1), // NOTE: The -1 shows inclusive blocks - record, - "end_slot", - serializer, - )?; - serializer.emit_usize("downloaded", self.failed_download_attempts.len())?; - serializer.emit_usize("processed", self.failed_processing_attempts.len())?; - serializer.emit_u8("processed_no_penalty", self.non_faulty_processing_attempts)?; - serializer.emit_arguments("state", &format_args!("{:?}", self.state))?; - serializer.emit_arguments("batch_ty", &format_args!("{}", self.batch_type))?; - slog::Result::Ok(()) - } -} - impl std::fmt::Debug for BatchState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/beacon_node/network/src/sync/range_sync/chain.rs b/beacon_node/network/src/sync/range_sync/chain.rs index f02262e4b5..70c7b6f98f 100644 --- a/beacon_node/network/src/sync/range_sync/chain.rs +++ b/beacon_node/network/src/sync/range_sync/chain.rs @@ -1,7 +1,6 @@ use super::batch::{BatchInfo, BatchProcessingResult, BatchState}; use super::RangeSyncType; use crate::metrics; -use crate::metrics::PEERS_PER_COLUMN_SUBNET; use crate::network_beacon_processor::ChainSegmentProcessId; use crate::sync::network_context::RangeRequestId; use crate::sync::{network_context::SyncNetworkContext, BatchOperationOutcome, BatchProcessResult}; @@ -10,12 +9,13 @@ use beacon_chain::BeaconChainTypes; use fnv::FnvHashMap; use lighthouse_network::service::api_types::Id; use lighthouse_network::{PeerAction, PeerId}; -use metrics::set_int_gauge; +use logging::crit; use rand::seq::SliceRandom; use rand::Rng; -use slog::{crit, debug, o, warn}; use std::collections::{btree_map::Entry, BTreeMap, HashSet}; +use std::fmt; use strum::IntoStaticStr; +use tracing::{debug, instrument, warn}; use types::{Epoch, EthSpec, Hash256, Slot}; /// Blocks are downloaded in batches from peers. This constant specifies how many epochs worth of @@ -39,6 +39,7 @@ pub type ProcessingResult = Result; /// Reasons for removing a chain #[derive(Debug)] +#[allow(dead_code)] pub enum RemoveChain { EmptyPeerPool, ChainCompleted, @@ -68,6 +69,7 @@ pub enum SyncingChainType { /// A chain of blocks that need to be downloaded. Peers who claim to contain the target head /// root are grouped into the peer pool and queried for batches when downloading the /// chain. +#[derive(Debug)] pub struct SyncingChain { /// A random id used to identify this chain. id: ChainId, @@ -112,9 +114,16 @@ pub struct SyncingChain { /// The current processing batch, if any. current_processing_batch: Option, +} - /// The chain's log. - log: slog::Logger, +impl fmt::Display for SyncingChain { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.chain_type { + SyncingChainType::Head => write!(f, "Head"), + SyncingChainType::Finalized => write!(f, "Finalized"), + SyncingChainType::Backfill => write!(f, "Backfill"), + } + } } #[derive(PartialEq, Debug)] @@ -134,7 +143,6 @@ impl SyncingChain { target_head_root: Hash256, peer_id: PeerId, chain_type: SyncingChainType, - log: &slog::Logger, ) -> Self { let mut peers = FnvHashMap::default(); peers.insert(peer_id, Default::default()); @@ -153,7 +161,6 @@ impl SyncingChain { attempted_optimistic_starts: HashSet::default(), state: ChainSyncingState::Stopped, current_processing_batch: None, - log: log.new(o!("chain" => id)), } } @@ -163,21 +170,25 @@ impl SyncingChain { } /// Check if the chain has peers from which to process batches. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn available_peers(&self) -> usize { self.peers.len() } /// Get the chain's id. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn get_id(&self) -> ChainId { self.id } /// Peers currently syncing this chain. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn peers(&self) -> impl Iterator + '_ { self.peers.keys().cloned() } /// Progress in epochs made by the chain + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn processed_epochs(&self) -> u64 { self.processing_target .saturating_sub(self.start_epoch) @@ -185,6 +196,7 @@ impl SyncingChain { } /// Returns the total count of pending blocks in all the batches of this chain + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn pending_blocks(&self) -> usize { self.batches .values() @@ -194,6 +206,7 @@ impl SyncingChain { /// Removes a peer from the chain. /// If the peer has active batches, those are considered failed and re-requested. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn remove_peer( &mut self, peer_id: &PeerId, @@ -213,8 +226,7 @@ impl SyncingChain { } self.retry_batch_download(network, id)?; } else { - debug!(self.log, "Batch not found while removing peer"; - "peer" => %peer_id, "batch" => id) + debug!(%peer_id, batch = ?id, "Batch not found while removing peer") } } } @@ -227,6 +239,7 @@ impl SyncingChain { } /// Returns the latest slot number that has been processed. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn current_processed_slot(&self) -> Slot { // the last slot we processed was included in the previous batch, and corresponds to the // first slot of the current target epoch @@ -236,6 +249,7 @@ impl SyncingChain { /// A block has been received for a batch on this chain. /// If the block correctly completes the batch it will be processed if possible. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn on_block_response( &mut self, network: &mut SyncNetworkContext, @@ -247,7 +261,7 @@ impl SyncingChain { // check if we have this batch let batch = match self.batches.get_mut(&batch_id) { None => { - debug!(self.log, "Received a block for unknown batch"; "epoch" => batch_id); + debug!(epoch = %batch_id, "Received a block for unknown batch"); // A batch might get removed when the chain advances, so this is non fatal. return Ok(KeepChain); } @@ -275,7 +289,7 @@ impl SyncingChain { let awaiting_batches = batch_id .saturating_sub(self.optimistic_start.unwrap_or(self.processing_target)) / EPOCHS_PER_BATCH; - debug!(self.log, "Batch downloaded"; "epoch" => batch_id, "blocks" => received, "batch_state" => self.visualize_batch_state(), "awaiting_batches" => awaiting_batches); + debug!(epoch = %batch_id, blocks = received, batch_state = self.visualize_batch_state(), %awaiting_batches,"Batch downloaded"); // pre-emptively request more blocks from peers whilst we process current blocks, self.request_batches(network)?; @@ -284,6 +298,7 @@ impl SyncingChain { /// Processes the batch with the given id. /// The batch must exist and be ready for processing + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn process_batch( &mut self, network: &mut SyncNetworkContext, @@ -319,8 +334,7 @@ impl SyncingChain { self.current_processing_batch = Some(batch_id); if let Err(e) = beacon_processor.send_chain_segment(process_id, blocks) { - crit!(self.log, "Failed to send chain segment to processor."; "msg" => "process_batch", - "error" => %e, "batch" => self.processing_target); + crit!(msg = "process_batch",error = %e, batch = ?self.processing_target, "Failed to send chain segment to processor."); // This is unlikely to happen but it would stall syncing since the batch now has no // blocks to continue, and the chain is expecting a processing result that won't // arrive. To mitigate this, (fake) fail this processing so that the batch is @@ -332,6 +346,7 @@ impl SyncingChain { } /// Processes the next ready batch, prioritizing optimistic batches over the processing target. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn process_completed_batches( &mut self, network: &mut SyncNetworkContext, @@ -351,7 +366,7 @@ impl SyncingChain { match state { BatchState::AwaitingProcessing(..) => { // this batch is ready - debug!(self.log, "Processing optimistic start"; "epoch" => epoch); + debug!(%epoch, "Processing optimistic start"); return self.process_batch(network, epoch); } BatchState::Downloading(..) => { @@ -379,7 +394,7 @@ impl SyncingChain { // batch has been requested and processed we can land here. We drop the // optimistic candidate since we can't conclude whether the batch included // blocks or not at this point - debug!(self.log, "Dropping optimistic candidate"; "batch" => epoch); + debug!(batch = %epoch, "Dropping optimistic candidate"); self.optimistic_start = None; } } @@ -413,7 +428,10 @@ impl SyncingChain { // inside the download buffer (between `self.processing_target` and // `self.to_be_downloaded`). In this case, eventually the chain advances to the // batch (`self.processing_target` reaches this point). - debug!(self.log, "Chain encountered a robust batch awaiting validation"; "batch" => self.processing_target); + debug!( + batch = %self.processing_target, + "Chain encountered a robust batch awaiting validation" + ); self.processing_target += EPOCHS_PER_BATCH; if self.to_be_downloaded <= self.processing_target { @@ -438,6 +456,7 @@ impl SyncingChain { /// The block processor has completed processing a batch. This function handles the result /// of the batch processor. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn on_batch_process_result( &mut self, network: &mut SyncNetworkContext, @@ -449,13 +468,11 @@ impl SyncingChain { let batch_state = self.visualize_batch_state(); let batch = match &self.current_processing_batch { Some(processing_id) if *processing_id != batch_id => { - debug!(self.log, "Unexpected batch result"; - "batch_epoch" => batch_id, "expected_batch_epoch" => processing_id); + debug!(batch_epoch = %batch_id, expected_batch_epoch = %processing_id,"Unexpected batch result"); return Ok(KeepChain); } None => { - debug!(self.log, "Chain was not expecting a batch result"; - "batch_epoch" => batch_id); + debug!(batch_epoch = %batch_id,"Chain was not expecting a batch result"); return Ok(KeepChain); } _ => { @@ -478,8 +495,14 @@ impl SyncingChain { })?; // Log the process result and the batch for debugging purposes. - debug!(self.log, "Batch processing result"; "result" => ?result, &batch, - "batch_epoch" => batch_id, "client" => %network.client_type(&peer), "batch_state" => batch_state); + debug!( + result = ?result, + batch_epoch = %batch_id, + client = %network.client_type(&peer), + batch_state = ?batch_state, + ?batch, + "Batch processing result" + ); // We consider three cases. Batch was successfully processed, Batch failed processing due // to a faulty peer, or batch failed processing but the peer can't be deemed faulty. @@ -565,10 +588,9 @@ impl SyncingChain { // There are some edge cases with forks that could land us in this situation. // This should be unlikely, so we tolerate these errors, but not often. warn!( - self.log, - "Batch failed to download. Dropping chain scoring peers"; - "score_adjustment" => %penalty, - "batch_epoch"=> batch_id, + score_adjustment = %penalty, + batch_epoch = %batch_id, + "Batch failed to download. Dropping chain scoring peers" ); for (peer, _) in self.peers.drain() { @@ -589,6 +611,7 @@ impl SyncingChain { } } + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn reject_optimistic_batch( &mut self, network: &mut SyncNetworkContext, @@ -601,13 +624,13 @@ impl SyncingChain { // it. NOTE: this is done to prevent non-sequential batches coming from optimistic // starts from filling up the buffer size if epoch < self.to_be_downloaded { - debug!(self.log, "Rejected optimistic batch left for future use"; "epoch" => %epoch, "reason" => reason); + debug!(%epoch, reason, "Rejected optimistic batch left for future use"); // this batch is now treated as any other batch, and re-requested for future use if redownload { return self.retry_batch_download(network, epoch); } } else { - debug!(self.log, "Rejected optimistic batch"; "epoch" => %epoch, "reason" => reason); + debug!(%epoch, reason, "Rejected optimistic batch"); self.batches.remove(&epoch); } } @@ -623,6 +646,7 @@ impl SyncingChain { /// If a previous batch has been validated and it had been re-processed, penalize the original /// peer. #[allow(clippy::modulo_one)] + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn advance_chain(&mut self, network: &mut SyncNetworkContext, validating_epoch: Epoch) { // make sure this epoch produces an advancement if validating_epoch <= self.start_epoch { @@ -631,7 +655,7 @@ impl SyncingChain { // safety check for batch boundaries if validating_epoch % EPOCHS_PER_BATCH != self.start_epoch % EPOCHS_PER_BATCH { - crit!(self.log, "Validating Epoch is not aligned"); + crit!("Validating Epoch is not aligned"); return; } @@ -653,9 +677,10 @@ impl SyncingChain { // A different peer sent the correct batch, the previous peer did not // We negatively score the original peer. let action = PeerAction::LowToleranceError; - debug!(self.log, "Re-processed batch validated. Scoring original peer"; - "batch_epoch" => id, "score_adjustment" => %action, - "original_peer" => %attempt.peer_id, "new_peer" => %processed_attempt.peer_id + debug!( + batch_epoch = %id, score_adjustment = %action, + original_peer = %attempt.peer_id, new_peer = %processed_attempt.peer_id, + "Re-processed batch validated. Scoring original peer" ); network.report_peer( attempt.peer_id, @@ -666,9 +691,12 @@ impl SyncingChain { // The same peer corrected it's previous mistake. There was an error, so we // negative score the original peer. let action = PeerAction::MidToleranceError; - debug!(self.log, "Re-processed batch validated by the same peer"; - "batch_epoch" => id, "score_adjustment" => %action, - "original_peer" => %attempt.peer_id, "new_peer" => %processed_attempt.peer_id + debug!( + batch_epoch = %id, + score_adjustment = %action, + original_peer = %attempt.peer_id, + new_peer = %processed_attempt.peer_id, + "Re-processed batch validated by the same peer" ); network.report_peer( attempt.peer_id, @@ -685,13 +713,12 @@ impl SyncingChain { active_batches.remove(&id); } } - BatchState::Failed | BatchState::Poisoned | BatchState::AwaitingDownload => crit!( - self.log, - "batch indicates inconsistent chain state while advancing chain" - ), + BatchState::Failed | BatchState::Poisoned | BatchState::AwaitingDownload => { + crit!("batch indicates inconsistent chain state while advancing chain") + } BatchState::AwaitingProcessing(..) => {} BatchState::Processing(_) => { - debug!(self.log, "Advancing chain while processing a batch"; "batch" => id, batch); + debug!(batch = %id, %batch, "Advancing chain while processing a batch"); if let Some(processing_id) = self.current_processing_batch { if id <= processing_id { self.current_processing_batch = None; @@ -715,8 +742,12 @@ impl SyncingChain { self.optimistic_start = None; } } - debug!(self.log, "Chain advanced"; "previous_start" => old_start, - "new_start" => self.start_epoch, "processing_target" => self.processing_target); + debug!( + previous_start = %old_start, + new_start = %self.start_epoch, + processing_target = %self.processing_target, + "Chain advanced" + ); } /// An invalid batch has been received that could not be processed, but that can be retried. @@ -724,6 +755,7 @@ impl SyncingChain { /// These events occur when a peer has successfully responded with blocks, but the blocks we /// have received are incorrect or invalid. This indicates the peer has not performed as /// intended and can result in downvoting a peer. + #[instrument(parent = None,level = "info", fields(service = self.id, network), skip_all)] fn handle_invalid_batch( &mut self, network: &mut SyncNetworkContext, @@ -783,6 +815,7 @@ impl SyncingChain { /// This chain has been requested to start syncing. /// /// This could be new chain, or an old chain that is being resumed. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn start_syncing( &mut self, network: &mut SyncNetworkContext, @@ -821,6 +854,7 @@ impl SyncingChain { /// Add a peer to the chain. /// /// If the chain is active, this starts requesting batches from this peer. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn add_peer( &mut self, network: &mut SyncNetworkContext, @@ -838,6 +872,7 @@ impl SyncingChain { /// An RPC error has occurred. /// /// If the batch exists it is re-requested. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn inject_error( &mut self, network: &mut SyncNetworkContext, @@ -854,24 +889,21 @@ impl SyncingChain { // columns. if !batch.is_expecting_block(&request_id) { debug!( - self.log, - "Batch not expecting block"; - "batch_epoch" => batch_id, - "batch_state" => ?batch.state(), - "peer_id" => %peer_id, - "request_id" => %request_id, - "batch_state" => batch_state + batch_epoch = %batch_id, + batch_state = ?batch.state(), + %peer_id, + %request_id, + ?batch_state, + "Batch not expecting block" ); return Ok(KeepChain); } debug!( - self.log, - "Batch failed. RPC Error"; - "batch_epoch" => batch_id, - "batch_state" => ?batch.state(), - "peer_id" => %peer_id, - "request_id" => %request_id, - "batch_state" => batch_state + batch_epoch = %batch_id, + batch_state = ?batch.state(), + %peer_id, + %request_id, + "Batch failed. RPC Error" ); if let Some(active_requests) = self.peers.get_mut(peer_id) { active_requests.remove(&batch_id); @@ -885,12 +917,11 @@ impl SyncingChain { self.retry_batch_download(network, batch_id) } else { debug!( - self.log, - "Batch not found"; - "batch_epoch" => batch_id, - "peer_id" => %peer_id, - "request_id" => %request_id, - "batch_state" => batch_state + batch_epoch = %batch_id, + %peer_id, + %request_id, + batch_state, + "Batch not found" ); // this could be an error for an old batch, removed when the chain advances Ok(KeepChain) @@ -898,6 +929,7 @@ impl SyncingChain { } /// Sends and registers the request of a batch awaiting download. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn retry_batch_download( &mut self, network: &mut SyncNetworkContext, @@ -934,6 +966,7 @@ impl SyncingChain { } /// Requests the batch assigned to the given id from a given peer. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn send_batch( &mut self, network: &mut SyncNetworkContext, @@ -960,9 +993,9 @@ impl SyncingChain { .map(|epoch| epoch == batch_id) .unwrap_or(false) { - debug!(self.log, "Requesting optimistic batch"; "epoch" => batch_id, &batch, "batch_state" => batch_state); + debug!(epoch = %batch_id, %batch, %batch_state, "Requesting optimistic batch"); } else { - debug!(self.log, "Requesting batch"; "epoch" => batch_id, &batch, "batch_state" => batch_state); + debug!(epoch = %batch_id, %batch, %batch_state, "Requesting batch"); } // register the batch for this peer return self @@ -981,8 +1014,7 @@ impl SyncingChain { } Err(e) => { // NOTE: under normal conditions this shouldn't happen but we handle it anyway - warn!(self.log, "Could not send batch request"; - "batch_id" => batch_id, "error" => ?e, &batch); + warn!(%batch_id, error = %e, %batch, "Could not send batch request"); // register the failed download and check if the batch can be retried batch.start_downloading_from_peer(peer, 1)?; // fake request_id is not relevant self.peers @@ -1007,6 +1039,7 @@ impl SyncingChain { } /// Returns true if this chain is currently syncing. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn is_syncing(&self) -> bool { match self.state { ChainSyncingState::Syncing => true, @@ -1016,6 +1049,7 @@ impl SyncingChain { /// Kickstarts the chain by sending for processing batches that are ready and requesting more /// batches if needed. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] pub fn resume( &mut self, network: &mut SyncNetworkContext, @@ -1028,6 +1062,7 @@ impl SyncingChain { /// Attempts to request the next required batches from the peer pool if the chain is syncing. It will exhaust the peer /// pool and left over batches until the batch buffer is reached or all peers are exhausted. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn request_batches(&mut self, network: &mut SyncNetworkContext) -> ProcessingResult { if !matches!(self.state, ChainSyncingState::Syncing) { return Ok(KeepChain); @@ -1054,10 +1089,7 @@ impl SyncingChain { // We wait for this batch before requesting any other batches. if let Some(epoch) = self.optimistic_start { if !self.good_peers_on_sampling_subnets(epoch, network) { - debug!( - self.log, - "Waiting for peers to be available on sampling column subnets" - ); + debug!("Waiting for peers to be available on sampling column subnets"); return Ok(KeepChain); } @@ -1106,11 +1138,6 @@ impl SyncingChain { .good_custody_subnet_peer(*subnet_id) .count(); - set_int_gauge( - &PEERS_PER_COLUMN_SUBNET, - &[&subnet_id.to_string()], - peer_count as i64, - ); peer_count > 0 }); peers_on_all_custody_subnets @@ -1121,6 +1148,7 @@ impl SyncingChain { /// Creates the next required batch from the chain. If there are no more batches required, /// `false` is returned. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn include_next_batch(&mut self, network: &mut SyncNetworkContext) -> Option { // don't request batches beyond the target head slot if self @@ -1154,10 +1182,7 @@ impl SyncingChain { // block and data column requests are currently coupled. This can be removed once we find a // way to decouple the requests and do retries individually, see issue #6258. if !self.good_peers_on_sampling_subnets(self.to_be_downloaded, network) { - debug!( - self.log, - "Waiting for peers to be available on custody column subnets" - ); + debug!("Waiting for peers to be available on custody column subnets"); return None; } @@ -1184,6 +1209,7 @@ impl SyncingChain { /// This produces a string of the form: [D,E,E,E,E] /// to indicate the current buffer state of the chain. The symbols are defined on each of the /// batch states. See [BatchState::visualize] for symbol definitions. + #[instrument(parent = None,level = "info", fields(chain = self.id , service = "range_sync"), skip_all)] fn visualize_batch_state(&self) -> String { let mut visualization_string = String::with_capacity((BATCH_BUFFER_SIZE * 3) as usize); @@ -1219,45 +1245,6 @@ impl SyncingChain { } } -impl slog::KV for &mut SyncingChain { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - slog::KV::serialize(*self, record, serializer) - } -} - -impl slog::KV for SyncingChain { - fn serialize( - &self, - record: &slog::Record, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - use slog::Value; - serializer.emit_u32("id", self.id)?; - Value::serialize(&self.start_epoch, record, "from", serializer)?; - Value::serialize( - &self.target_head_slot.epoch(T::EthSpec::slots_per_epoch()), - record, - "to", - serializer, - )?; - serializer.emit_arguments("end_root", &format_args!("{}", self.target_head_root))?; - Value::serialize( - &self.processing_target, - record, - "current_target", - serializer, - )?; - serializer.emit_usize("batches", self.batches.len())?; - serializer.emit_usize("peers", self.peers.len())?; - serializer.emit_arguments("state", &format_args!("{:?}", self.state))?; - slog::Result::Ok(()) - } -} - use super::batch::WrongState as WrongBatchState; impl From for RemoveChain { fn from(err: WrongBatchState) -> Self { diff --git a/beacon_node/network/src/sync/range_sync/chain_collection.rs b/beacon_node/network/src/sync/range_sync/chain_collection.rs index 15bdf85e20..c6be3de576 100644 --- a/beacon_node/network/src/sync/range_sync/chain_collection.rs +++ b/beacon_node/network/src/sync/range_sync/chain_collection.rs @@ -12,11 +12,12 @@ use fnv::FnvHashMap; use lighthouse_network::service::api_types::Id; use lighthouse_network::PeerId; use lighthouse_network::SyncInfo; -use slog::{crit, debug, error}; +use logging::crit; use smallvec::SmallVec; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::sync::Arc; +use tracing::{debug, error}; use types::EthSpec; use types::{Epoch, Hash256, Slot}; @@ -50,18 +51,15 @@ pub struct ChainCollection { head_chains: FnvHashMap>, /// The current sync state of the process. state: RangeSyncState, - /// Logger for the collection. - log: slog::Logger, } impl ChainCollection { - pub fn new(beacon_chain: Arc>, log: slog::Logger) -> Self { + pub fn new(beacon_chain: Arc>) -> Self { ChainCollection { beacon_chain, finalized_chains: FnvHashMap::default(), head_chains: FnvHashMap::default(), state: RangeSyncState::Idle, - log, } } @@ -295,9 +293,8 @@ impl ChainCollection { .expect("Chain exists"); match old_id { - Some(Some(old_id)) => debug!(self.log, "Switching finalized chains"; - "old_id" => old_id, &chain), - None => debug!(self.log, "Syncing new finalized chain"; &chain), + Some(Some(old_id)) => debug!(old_id, %chain, "Switching finalized chains"), + None => debug!(%chain, "Syncing new finalized chain"), Some(None) => { // this is the same chain. We try to advance it. } @@ -309,10 +306,10 @@ impl ChainCollection { if let Err(remove_reason) = chain.start_syncing(network, local_epoch, local_head_epoch) { if remove_reason.is_critical() { - crit!(self.log, "Chain removed while switching chains"; "chain" => new_id, "reason" => ?remove_reason); + crit!(chain = new_id, reason = ?remove_reason, "Chain removed while switching chains"); } else { // this happens only if sending a batch over the `network` fails a lot - error!(self.log, "Chain removed while switching chains"; "chain" => new_id, "reason" => ?remove_reason); + error!(chain = new_id, reason = ?remove_reason, "Chain removed while switching chains"); } self.finalized_chains.remove(&new_id); self.on_chain_removed(&new_id, true, RangeSyncType::Finalized); @@ -330,7 +327,7 @@ impl ChainCollection { ) { // Include the awaiting head peers for (peer_id, peer_sync_info) in awaiting_head_peers.drain() { - debug!(self.log, "including head peer"); + debug!("including head peer"); self.add_peer_or_create_chain( local_epoch, peer_sync_info.head_root, @@ -362,16 +359,16 @@ impl ChainCollection { if syncing_chains.len() < PARALLEL_HEAD_CHAINS { // start this chain if it's not already syncing if !chain.is_syncing() { - debug!(self.log, "New head chain started syncing"; &chain); + debug!(%chain, "New head chain started syncing"); } if let Err(remove_reason) = chain.start_syncing(network, local_epoch, local_head_epoch) { self.head_chains.remove(&id); if remove_reason.is_critical() { - crit!(self.log, "Chain removed while switching head chains"; "chain" => id, "reason" => ?remove_reason); + crit!(chain = id, reason = ?remove_reason, "Chain removed while switching head chains"); } else { - error!(self.log, "Chain removed while switching head chains"; "chain" => id, "reason" => ?remove_reason); + error!(chain = id, reason = ?remove_reason, "Chain removed while switching head chains"); } } else { syncing_chains.push(id); @@ -407,7 +404,6 @@ impl ChainCollection { .start_slot(T::EthSpec::slots_per_epoch()); let beacon_chain = &self.beacon_chain; - let log_ref = &self.log; let is_outdated = |target_slot: &Slot, target_root: &Hash256| { target_slot <= &local_finalized_slot @@ -425,7 +421,7 @@ impl ChainCollection { if is_outdated(&chain.target_head_slot, &chain.target_head_root) || chain.available_peers() == 0 { - debug!(log_ref, "Purging out of finalized chain"; &chain); + debug!(%chain, "Purging out of finalized chain"); Some((*id, chain.is_syncing(), RangeSyncType::Finalized)) } else { None @@ -436,7 +432,7 @@ impl ChainCollection { if is_outdated(&chain.target_head_slot, &chain.target_head_root) || chain.available_peers() == 0 { - debug!(log_ref, "Purging out of date head chain"; &chain); + debug!(%chain, "Purging out of date head chain"); Some((*id, chain.is_syncing(), RangeSyncType::Head)) } else { None @@ -477,14 +473,14 @@ impl ChainCollection { .find(|(_, chain)| chain.has_same_target(target_head_slot, target_head_root)) { Some((&id, chain)) => { - debug!(self.log, "Adding peer to known chain"; "peer_id" => %peer, "sync_type" => ?sync_type, &chain); + debug!(peer_id = %peer, ?sync_type, id, "Adding peer to known chain"); debug_assert_eq!(chain.target_head_root, target_head_root); debug_assert_eq!(chain.target_head_slot, target_head_slot); if let Err(remove_reason) = chain.add_peer(network, peer) { if remove_reason.is_critical() { - crit!(self.log, "Chain removed after adding peer"; "chain" => id, "reason" => ?remove_reason); + crit!(chain = %id, reason = ?remove_reason, "Chain removed after adding peer"); } else { - error!(self.log, "Chain removed after adding peer"; "chain" => id, "reason" => ?remove_reason); + error!(chain = %id, reason = ?remove_reason, "Chain removed after adding peer"); } let is_syncing = chain.is_syncing(); collection.remove(&id); @@ -501,9 +497,9 @@ impl ChainCollection { target_head_root, peer, sync_type.into(), - &self.log, ); - debug!(self.log, "New chain added to sync"; "peer_id" => peer_rpr, "sync_type" => ?sync_type, &new_chain); + + debug!(peer_id = peer_rpr, ?sync_type, %new_chain, "New chain added to sync"); collection.insert(id, new_chain); metrics::inc_counter_vec(&metrics::SYNCING_CHAINS_ADDED, &[sync_type.as_str()]); self.update_metrics(); diff --git a/beacon_node/network/src/sync/range_sync/range.rs b/beacon_node/network/src/sync/range_sync/range.rs index 78679403bb..e4a20f6349 100644 --- a/beacon_node/network/src/sync/range_sync/range.rs +++ b/beacon_node/network/src/sync/range_sync/range.rs @@ -51,10 +51,11 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use lighthouse_network::rpc::GoodbyeReason; use lighthouse_network::service::api_types::Id; use lighthouse_network::{PeerId, SyncInfo}; +use logging::crit; use lru_cache::LRUTimeCache; -use slog::{crit, debug, trace, warn}; use std::collections::HashMap; use std::sync::Arc; +use tracing::{debug, instrument, trace, warn}; use types::{Epoch, EthSpec, Hash256}; /// For how long we store failed finalized chains to prevent retries. @@ -74,26 +75,40 @@ pub struct RangeSync { chains: ChainCollection, /// Chains that have failed and are stored to prevent being retried. failed_chains: LRUTimeCache, - /// The syncing logger. - log: slog::Logger, } impl RangeSync where T: BeaconChainTypes, { - pub fn new(beacon_chain: Arc>, log: slog::Logger) -> Self { + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] + pub fn new(beacon_chain: Arc>) -> Self { RangeSync { beacon_chain: beacon_chain.clone(), - chains: ChainCollection::new(beacon_chain, log.clone()), + chains: ChainCollection::new(beacon_chain), failed_chains: LRUTimeCache::new(std::time::Duration::from_secs( FAILED_CHAINS_EXPIRY_SECONDS, )), awaiting_head_peers: HashMap::new(), - log, } } + #[cfg(test)] + pub(crate) fn __failed_chains(&mut self) -> Vec { + self.failed_chains.keys().copied().collect() + } + + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn state(&self) -> SyncChainStatus { self.chains.state() } @@ -103,6 +118,12 @@ where /// may need to be synced as a result. A new peer, may increase the peer pool of a finalized /// chain, this may result in a different finalized chain from syncing as finalized chains are /// prioritised by peer-pool size. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn add_peer( &mut self, network: &mut SyncNetworkContext, @@ -128,14 +149,13 @@ where RangeSyncType::Finalized => { // Make sure we have not recently tried this chain if self.failed_chains.contains(&remote_info.finalized_root) { - debug!(self.log, "Disconnecting peer that belongs to previously failed chain"; - "failed_root" => %remote_info.finalized_root, "peer_id" => %peer_id); + debug!(failed_root = ?remote_info.finalized_root, %peer_id,"Disconnecting peer that belongs to previously failed chain"); network.goodbye_peer(peer_id, GoodbyeReason::IrrelevantNetwork); return; } // Finalized chain search - debug!(self.log, "Finalization sync peer joined"; "peer_id" => %peer_id); + debug!(%peer_id, "Finalization sync peer joined"); self.awaiting_head_peers.remove(&peer_id); // Because of our change in finalized sync batch size from 2 to 1 and our transition @@ -166,8 +186,7 @@ where if self.chains.is_finalizing_sync() { // If there are finalized chains to sync, finish these first, before syncing head // chains. - trace!(self.log, "Waiting for finalized sync to complete"; - "peer_id" => %peer_id, "awaiting_head_peers" => &self.awaiting_head_peers.len()); + trace!(%peer_id, awaiting_head_peers = &self.awaiting_head_peers.len(),"Waiting for finalized sync to complete"); self.awaiting_head_peers.insert(peer_id, remote_info); return; } @@ -199,6 +218,12 @@ where /// /// This function finds the chain that made this request. Once found, processes the result. /// This request could complete a chain or simply add to its progress. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn blocks_by_range_response( &mut self, network: &mut SyncNetworkContext, @@ -224,11 +249,17 @@ where } } Err(_) => { - trace!(self.log, "BlocksByRange response for removed chain"; "chain" => chain_id) + trace!(%chain_id, "BlocksByRange response for removed chain") } } } + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn handle_block_process_result( &mut self, network: &mut SyncNetworkContext, @@ -254,13 +285,19 @@ where } Err(_) => { - trace!(self.log, "BlocksByRange response for removed chain"; "chain" => chain_id) + trace!(%chain_id, "BlocksByRange response for removed chain") } } } /// A peer has disconnected. This removes the peer from any ongoing chains and mappings. A /// disconnected peer could remove a chain + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn peer_disconnect(&mut self, network: &mut SyncNetworkContext, peer_id: &PeerId) { // if the peer is in the awaiting head mapping, remove it self.awaiting_head_peers.remove(peer_id); @@ -273,6 +310,12 @@ where /// which pool the peer is in. The chain may also have a batch or batches awaiting /// for this peer. If so we mark the batch as failed. The batch may then hit it's maximum /// retries. In this case, we need to remove the chain. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] fn remove_peer(&mut self, network: &mut SyncNetworkContext, peer_id: &PeerId) { for (removed_chain, sync_type, remove_reason) in self .chains @@ -292,6 +335,12 @@ where /// /// Check to see if the request corresponds to a pending batch. If so, re-request it if possible, if there have /// been too many failed attempts for the batch, remove the chain. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn inject_error( &mut self, network: &mut SyncNetworkContext, @@ -316,11 +365,17 @@ where } } Err(_) => { - trace!(self.log, "BlocksByRange response for removed chain"; "chain" => chain_id) + trace!(%chain_id, "BlocksByRange response for removed chain") } } } + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] fn on_chain_removed( &mut self, chain: SyncingChain, @@ -330,14 +385,18 @@ where op: &'static str, ) { if remove_reason.is_critical() { - crit!(self.log, "Chain removed"; "sync_type" => ?sync_type, &chain, "reason" => ?remove_reason, "op" => op); + crit!(?sync_type, %chain, reason = ?remove_reason,op, "Chain removed"); } else { - debug!(self.log, "Chain removed"; "sync_type" => ?sync_type, &chain, "reason" => ?remove_reason, "op" => op); + debug!(?sync_type, %chain, reason = ?remove_reason,op, "Chain removed"); } if let RemoveChain::ChainFailed { blacklist, .. } = remove_reason { if RangeSyncType::Finalized == sync_type && blacklist { - warn!(self.log, "Chain failed! Syncing to its head won't be retried for at least the next {} seconds", FAILED_CHAINS_EXPIRY_SECONDS; &chain); + warn!( + %chain, + "Chain failed! Syncing to its head won't be retried for at least the next {} seconds", + FAILED_CHAINS_EXPIRY_SECONDS + ); self.failed_chains.insert(chain.target_head_root); } } @@ -364,6 +423,12 @@ where } /// Kickstarts sync. + #[instrument(parent = None, + level = "info", + fields(component = "range_sync"), + name = "range_sync", + skip_all + )] pub fn resume(&mut self, network: &mut SyncNetworkContext) { for (removed_chain, sync_type, remove_reason) in self.chains.call_all(|chain| chain.resume(network)) diff --git a/beacon_node/network/src/sync/tests/lookups.rs b/beacon_node/network/src/sync/tests/lookups.rs index 9ab581950c..f79dd6de96 100644 --- a/beacon_node/network/src/sync/tests/lookups.rs +++ b/beacon_node/network/src/sync/tests/lookups.rs @@ -19,14 +19,15 @@ use beacon_chain::{ block_verification_types::{AsBlock, BlockImportData}, data_availability_checker::Availability, test_utils::{ - build_log, generate_rand_block_and_blobs, generate_rand_block_and_data_columns, test_spec, - BeaconChainHarness, EphemeralHarnessType, LoggerType, NumBlobs, + generate_rand_block_and_blobs, generate_rand_block_and_data_columns, test_spec, + BeaconChainHarness, EphemeralHarnessType, NumBlobs, }, validator_monitor::timestamp_now, AvailabilityPendingExecutedBlock, AvailabilityProcessingStatus, BlockError, PayloadVerificationOutcome, PayloadVerificationStatus, }; use beacon_processor::WorkEvent; +use lighthouse_network::discovery::CombinedKey; use lighthouse_network::{ rpc::{RPCError, RequestType, RpcErrorResponse}, service::api_types::{ @@ -36,42 +37,30 @@ use lighthouse_network::{ types::SyncState, NetworkConfig, NetworkGlobals, PeerId, }; -use slog::info; use slot_clock::{SlotClock, TestingSlotClock}; use tokio::sync::mpsc; -use types::ForkContext; +use tracing::info; use types::{ data_column_sidecar::ColumnIndex, test_utils::{SeedableRng, TestRandom, XorShiftRng}, - BeaconState, BeaconStateBase, BlobSidecar, DataColumnSidecar, EthSpec, ForkName, Hash256, - MinimalEthSpec as E, SignedBeaconBlock, Slot, + BeaconState, BeaconStateBase, BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, ForkName, + Hash256, MinimalEthSpec as E, SignedBeaconBlock, Slot, }; const D: Duration = Duration::new(0, 0); const PARENT_FAIL_TOLERANCE: u8 = SINGLE_BLOCK_LOOKUP_MAX_ATTEMPTS; const SAMPLING_REQUIRED_SUCCESSES: usize = 2; - type DCByRootIds = Vec; type DCByRootId = (SyncRequestId, Vec); impl TestRig { pub fn test_setup() -> Self { - let logger_type = if cfg!(feature = "test_logger") { - LoggerType::Test - } else if cfg!(feature = "ci_logger") { - LoggerType::CI - } else { - LoggerType::Null - }; - let log = build_log(slog::Level::Trace, logger_type); - // Use `fork_from_env` logic to set correct fork epochs let spec = test_spec::(); // Initialise a new beacon chain let harness = BeaconChainHarness::>::builder(E) .spec(Arc::new(spec)) - .logger(log.clone()) .deterministic_keypairs(1) .fresh_ephemeral_store() .mock_execution_layer() @@ -96,7 +85,6 @@ impl TestRig { let network_config = Arc::new(NetworkConfig::default()); let globals = Arc::new(NetworkGlobals::new_test_globals( Vec::new(), - &log, network_config, chain.spec.clone(), )); @@ -105,7 +93,6 @@ impl TestRig { sync_tx, chain.clone(), harness.runtime.task_executor.clone(), - log.clone(), ); let fork_name = chain.spec.fork_name_at_slot::(chain.slot().unwrap()); @@ -117,7 +104,9 @@ impl TestRig { let spec = chain.spec.clone(); - let rng = XorShiftRng::from_seed([42; 16]); + // deterministic seed + let rng = ChaCha20Rng::from_seed([0u8; 32]); + TestRig { beacon_processor_rx, beacon_processor_rx_queue: vec![], @@ -136,11 +125,9 @@ impl TestRig { required_successes: vec![SAMPLING_REQUIRED_SUCCESSES], }, fork_context, - log.clone(), ), harness, fork_name, - log, spec, } } @@ -154,7 +141,7 @@ impl TestRig { } } - fn test_setup_after_fulu() -> Option { + pub fn test_setup_after_fulu() -> Option { let r = Self::test_setup(); if r.fork_name.fulu_enabled() { Some(r) @@ -164,7 +151,7 @@ impl TestRig { } pub fn log(&self, msg: &str) { - info!(self.log, "TEST_RIG"; "msg" => msg); + info!(msg, "TEST_RIG"); } pub fn after_deneb(&self) -> bool { @@ -369,20 +356,26 @@ impl TestRig { } pub fn new_connected_peer(&mut self) -> PeerId { + let key = self.determinstic_key(); self.network_globals .peers .write() - .__add_connected_peer_testing_only(false, &self.harness.spec) + .__add_connected_peer_testing_only(false, &self.harness.spec, key) } pub fn new_connected_supernode_peer(&mut self) -> PeerId { + let key = self.determinstic_key(); self.network_globals .peers .write() - .__add_connected_peer_testing_only(true, &self.harness.spec) + .__add_connected_peer_testing_only(true, &self.harness.spec, key) } - fn new_connected_peers_for_peerdas(&mut self) { + fn determinstic_key(&mut self) -> CombinedKey { + k256::ecdsa::SigningKey::random(&mut self.rng).into() + } + + pub fn new_connected_peers_for_peerdas(&mut self) { // Enough sampling peers with few columns for _ in 0..100 { self.new_connected_peer(); @@ -706,7 +699,6 @@ impl TestRig { self.complete_data_columns_by_root_request(id, data_columns); // Expect work event - // TODO(das): worth it to append sender id to the work event for stricter assertion? self.expect_rpc_sample_verify_work_event(); // Respond with valid result @@ -748,7 +740,6 @@ impl TestRig { } // Expect work event - // TODO(das): worth it to append sender id to the work event for stricter assertion? self.expect_rpc_custody_column_work_event(); // Respond with valid result @@ -1113,7 +1104,7 @@ impl TestRig { } #[track_caller] - fn expect_empty_network(&mut self) { + pub fn expect_empty_network(&mut self) { self.drain_network_rx(); if !self.network_rx_queue.is_empty() { let n = self.network_rx_queue.len(); @@ -2313,11 +2304,6 @@ mod deneb_only { }) } - fn log(self, msg: &str) -> Self { - self.rig.log(msg); - self - } - fn trigger_unknown_block_from_attestation(mut self) -> Self { let block_root = self.block.canonical_root(); self.rig @@ -2621,6 +2607,11 @@ mod deneb_only { .block_imported() } + fn log(self, msg: &str) -> Self { + self.rig.log(msg); + self + } + fn parent_block_then_empty_parent_blobs(self) -> Self { self.log( " Return empty blobs for parent, block errors with missing components, downscore", diff --git a/beacon_node/network/src/sync/tests/mod.rs b/beacon_node/network/src/sync/tests/mod.rs index 6ed5c7f8fa..ec24ddb036 100644 --- a/beacon_node/network/src/sync/tests/mod.rs +++ b/beacon_node/network/src/sync/tests/mod.rs @@ -7,12 +7,12 @@ use beacon_chain::eth1_chain::CachingEth1Backend; use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType}; use beacon_processor::WorkEvent; use lighthouse_network::NetworkGlobals; -use slog::Logger; +use rand_chacha::ChaCha20Rng; use slot_clock::ManualSlotClock; use std::sync::Arc; use store::MemoryStore; use tokio::sync::mpsc; -use types::{test_utils::XorShiftRng, ChainSpec, ForkName, MinimalEthSpec as E}; +use types::{ChainSpec, ForkName, MinimalEthSpec as E}; mod lookups; mod range; @@ -61,8 +61,7 @@ struct TestRig { /// Beacon chain harness harness: BeaconChainHarness>, /// `rng` for generating test blocks and blobs. - rng: XorShiftRng, + rng: ChaCha20Rng, fork_name: ForkName, - log: Logger, spec: Arc, } diff --git a/beacon_node/network/src/sync/tests/range.rs b/beacon_node/network/src/sync/tests/range.rs index f78b44308d..ddd4626cce 100644 --- a/beacon_node/network/src/sync/tests/range.rs +++ b/beacon_node/network/src/sync/tests/range.rs @@ -1,11 +1,14 @@ use super::*; +use crate::network_beacon_processor::ChainSegmentProcessId; use crate::status::ToStatusMessage; use crate::sync::manager::SLOT_IMPORT_TOLERANCE; +use crate::sync::network_context::RangeRequestId; use crate::sync::range_sync::RangeSyncType; use crate::sync::SyncMessage; use beacon_chain::data_column_verification::CustodyDataColumn; use beacon_chain::test_utils::{AttestationStrategy, BlockStrategy}; use beacon_chain::{block_verification_types::RpcBlock, EngineState, NotifyExecutionLayer}; +use beacon_processor::WorkType; use lighthouse_network::rpc::methods::{ BlobsByRangeRequest, DataColumnsByRangeRequest, OldBlocksByRangeRequest, OldBlocksByRangeRequestV2, @@ -18,8 +21,8 @@ use lighthouse_network::service::api_types::{ use lighthouse_network::{PeerId, SyncInfo}; use std::time::Duration; use types::{ - BlobSidecarList, BlockImportSource, EthSpec, Hash256, MinimalEthSpec as E, SignedBeaconBlock, - SignedBeaconBlockHash, Slot, + BlobSidecarList, BlockImportSource, Epoch, EthSpec, Hash256, MinimalEthSpec as E, + SignedBeaconBlock, SignedBeaconBlockHash, Slot, }; const D: Duration = Duration::new(0, 0); @@ -43,7 +46,7 @@ enum ByRangeDataRequestIds { /// To make writting tests succint, the machinery in this testing rig automatically identifies /// _which_ request to complete. Picking the right request is critical for tests to pass, so this /// filter allows better expressivity on the criteria to identify the right request. -#[derive(Default)] +#[derive(Default, Debug, Clone)] struct RequestFilter { peer: Option, epoch: Option, @@ -74,7 +77,7 @@ impl TestRig { /// Produce a head peer with an advanced head fn add_head_peer_with_root(&mut self, head_root: Hash256) -> PeerId { let local_info = self.local_info(); - self.add_peer(SyncInfo { + self.add_random_peer(SyncInfo { head_root, head_slot: local_info.head_slot + 1 + Slot::new(SLOT_IMPORT_TOLERANCE as u64), ..local_info @@ -90,7 +93,7 @@ impl TestRig { fn add_finalized_peer_with_root(&mut self, finalized_root: Hash256) -> PeerId { let local_info = self.local_info(); let finalized_epoch = local_info.finalized_epoch + 2; - self.add_peer(SyncInfo { + self.add_random_peer(SyncInfo { finalized_epoch, finalized_root, head_slot: finalized_epoch.start_slot(E::slots_per_epoch()), @@ -98,6 +101,17 @@ impl TestRig { }) } + fn finalized_remote_info_advanced_by(&self, advanced_epochs: Epoch) -> SyncInfo { + let local_info = self.local_info(); + let finalized_epoch = local_info.finalized_epoch + advanced_epochs; + SyncInfo { + finalized_epoch, + finalized_root: Hash256::random(), + head_slot: finalized_epoch.start_slot(E::slots_per_epoch()), + head_root: Hash256::random(), + } + } + fn local_info(&self) -> SyncInfo { let StatusMessage { fork_digest: _, @@ -114,28 +128,59 @@ impl TestRig { } } - fn add_peer(&mut self, remote_info: SyncInfo) -> PeerId { + fn add_random_peer_not_supernode(&mut self, remote_info: SyncInfo) -> PeerId { + let peer_id = self.new_connected_peer(); + self.send_sync_message(SyncMessage::AddPeer(peer_id, remote_info)); + peer_id + } + + fn add_random_peer(&mut self, remote_info: SyncInfo) -> PeerId { // Create valid peer known to network globals // TODO(fulu): Using supernode peers to ensure we have peer across all column // subnets for syncing. Should add tests connecting to full node peers. let peer_id = self.new_connected_supernode_peer(); // Send peer to sync - self.send_sync_message(SyncMessage::AddPeer(peer_id, remote_info.clone())); + self.send_sync_message(SyncMessage::AddPeer(peer_id, remote_info)); peer_id } + fn add_random_peers(&mut self, remote_info: SyncInfo, count: usize) { + for _ in 0..count { + let peer = self.new_connected_peer(); + self.add_peer(peer, remote_info.clone()); + } + } + + fn add_peer(&mut self, peer: PeerId, remote_info: SyncInfo) { + self.send_sync_message(SyncMessage::AddPeer(peer, remote_info)); + } + fn assert_state(&self, state: RangeSyncType) { assert_eq!( self.sync_manager .range_sync_state() .expect("State is ok") - .expect("Range should be syncing") + .expect("Range should be syncing, there are no chains") .0, state, "not expected range sync state" ); } + fn assert_no_chains_exist(&self) { + if let Some(chain) = self.sync_manager.get_range_sync_chains().unwrap() { + panic!("There still exists a chain {chain:?}"); + } + } + + fn assert_no_failed_chains(&mut self) { + assert_eq!( + self.sync_manager.__range_failed_chains(), + Vec::::new(), + "Expected no failed chains" + ) + } + #[track_caller] fn expect_chain_segments(&mut self, count: usize) { for i in 0..count { @@ -170,7 +215,7 @@ impl TestRig { true }; - let block_req_id = self + let block_req = self .pop_received_network_event(|ev| match ev { NetworkMessage::SendRequest { peer_id, @@ -182,7 +227,9 @@ impl TestRig { } if filter_f(*peer_id, *start_slot) => Some((*id, *peer_id)), _ => None, }) - .expect("Should have a blocks by range request"); + .unwrap_or_else(|e| { + panic!("Should have a BlocksByRange request, filter {request_filter:?}: {e:?}") + }); let by_range_data_requests = if self.after_fulu() { let mut data_columns_requests = vec![]; @@ -200,7 +247,7 @@ impl TestRig { data_columns_requests.push(data_columns_request); } if data_columns_requests.is_empty() { - panic!("Found zero DataColumnsByRange requests"); + panic!("Found zero DataColumnsByRange requests, filter {request_filter:?}"); } ByRangeDataRequestIds::PostPeerDAS(data_columns_requests) } else if self.after_deneb() { @@ -213,16 +260,21 @@ impl TestRig { } if filter_f(*peer_id, *start_slot) => Some((*id, *peer_id)), _ => None, }) - .expect("Should have a blobs by range request"); + .unwrap_or_else(|e| { + panic!("Should have a blobs by range request, filter {request_filter:?}: {e:?}") + }); ByRangeDataRequestIds::PrePeerDAS(id, peer) } else { ByRangeDataRequestIds::PreDeneb }; - (block_req_id, by_range_data_requests) + (block_req, by_range_data_requests) } - fn find_and_complete_blocks_by_range_request(&mut self, request_filter: RequestFilter) { + fn find_and_complete_blocks_by_range_request( + &mut self, + request_filter: RequestFilter, + ) -> RangeRequestId { let ((blocks_req_id, block_peer), by_range_data_request_ids) = self.find_blocks_by_range_request(request_filter); @@ -266,6 +318,60 @@ impl TestRig { } } } + + blocks_req_id.parent_request_id.requester + } + + fn find_and_complete_processing_chain_segment(&mut self, id: ChainSegmentProcessId) { + self.pop_received_processor_event(|ev| { + (ev.work_type() == WorkType::ChainSegment).then_some(()) + }) + .unwrap_or_else(|e| panic!("Expected chain segment work event: {e}")); + + self.log(&format!( + "Completing ChainSegment processing work {id:?} with success" + )); + self.send_sync_message(SyncMessage::BatchProcessed { + sync_type: id, + result: crate::sync::BatchProcessResult::Success { + sent_blocks: 8, + imported_blocks: 8, + }, + }); + } + + fn complete_and_process_range_sync_until( + &mut self, + last_epoch: u64, + request_filter: RequestFilter, + ) { + for epoch in 0..last_epoch { + // Note: In this test we can't predict the block peer + let id = + self.find_and_complete_blocks_by_range_request(request_filter.clone().epoch(epoch)); + if let RangeRequestId::RangeSync { batch_id, .. } = id { + assert_eq!(batch_id.as_u64(), epoch, "Unexpected batch_id"); + } else { + panic!("unexpected RangeRequestId {id:?}"); + } + + let id = match id { + RangeRequestId::RangeSync { chain_id, batch_id } => { + ChainSegmentProcessId::RangeBatchId(chain_id, batch_id) + } + RangeRequestId::BackfillSync { batch_id } => { + ChainSegmentProcessId::BackSyncBatchId(batch_id) + } + }; + + self.find_and_complete_processing_chain_segment(id); + if epoch < last_epoch - 1 { + self.assert_state(RangeSyncType::Finalized); + } else { + self.assert_no_chains_exist(); + self.assert_no_failed_chains(); + } + } } async fn create_canonical_block(&mut self) -> (SignedBeaconBlock, Option>) { @@ -442,3 +548,65 @@ fn pause_and_resume_on_ee_offline() { // The head chain and finalized chain (2) should be in the processing queue rig.expect_chain_segments(2); } + +/// To attempt to finalize the peer's status finalized checkpoint we synced to its finalized epoch + +/// 2 epochs + 1 slot. +const EXTRA_SYNCED_EPOCHS: u64 = 2 + 1; + +#[test] +fn finalized_sync_enough_global_custody_peers_few_chain_peers() { + // Run for all forks + let mut r = TestRig::test_setup(); + // This test creates enough global custody peers to satisfy column queries but only adds few + // peers to the chain + r.new_connected_peers_for_peerdas(); + + let advanced_epochs: u64 = 2; + let remote_info = r.finalized_remote_info_advanced_by(advanced_epochs.into()); + + // Current priorization only sends batches to idle peers, so we need enough peers for each batch + // TODO: Test this with a single peer in the chain, it should still work + r.add_random_peers( + remote_info, + (advanced_epochs + EXTRA_SYNCED_EPOCHS) as usize, + ); + r.assert_state(RangeSyncType::Finalized); + + let last_epoch = advanced_epochs + EXTRA_SYNCED_EPOCHS; + r.complete_and_process_range_sync_until(last_epoch, filter()); +} + +#[test] +fn finalized_sync_not_enough_custody_peers_on_start() { + let mut r = TestRig::test_setup(); + // Only run post-PeerDAS + if !r.fork_name.fulu_enabled() { + return; + } + + let advanced_epochs: u64 = 2; + let remote_info = r.finalized_remote_info_advanced_by(advanced_epochs.into()); + + // Unikely that the single peer we added has enough columns for us. Tests are determinstic and + // this error should never be hit + r.add_random_peer_not_supernode(remote_info.clone()); + r.assert_state(RangeSyncType::Finalized); + + // Because we don't have enough peers on all columns we haven't sent any request. + // NOTE: There's a small chance that this single peer happens to custody exactly the set we + // expect, in that case the test will fail. Find a way to make the test deterministic. + r.expect_empty_network(); + + // Generate enough peers and supernodes to cover all custody columns + r.new_connected_peers_for_peerdas(); + // Note: not necessary to add this peers to the chain, as we draw from the global pool + // We still need to add enough peers to trigger batch downloads with idle peers. Same issue as + // the test above. + r.add_random_peers( + remote_info, + (advanced_epochs + EXTRA_SYNCED_EPOCHS - 1) as usize, + ); + + let last_epoch = advanced_epochs + EXTRA_SYNCED_EPOCHS; + r.complete_and_process_range_sync_until(last_epoch, filter()); +} diff --git a/beacon_node/operation_pool/src/bls_to_execution_changes.rs b/beacon_node/operation_pool/src/bls_to_execution_changes.rs index cbab97e719..b36299b51a 100644 --- a/beacon_node/operation_pool/src/bls_to_execution_changes.rs +++ b/beacon_node/operation_pool/src/bls_to_execution_changes.rs @@ -112,7 +112,7 @@ impl BlsToExecutionChanges { head_state .validators() .get(validator_index as usize) - .map_or(true, |validator| { + .is_none_or(|validator| { let prune = validator.has_execution_withdrawal_credential(spec) && head_block .message() diff --git a/beacon_node/operation_pool/src/lib.rs b/beacon_node/operation_pool/src/lib.rs index 835133a059..584a5f9f32 100644 --- a/beacon_node/operation_pool/src/lib.rs +++ b/beacon_node/operation_pool/src/lib.rs @@ -767,7 +767,7 @@ fn prune_validator_hash_map( && head_state .validators() .get(validator_index as usize) - .map_or(true, |validator| !prune_if(validator_index, validator)) + .is_none_or(|validator| !prune_if(validator_index, validator)) }); } diff --git a/beacon_node/operation_pool/src/reward_cache.rs b/beacon_node/operation_pool/src/reward_cache.rs index dd9902353f..adedcb5e39 100644 --- a/beacon_node/operation_pool/src/reward_cache.rs +++ b/beacon_node/operation_pool/src/reward_cache.rs @@ -83,7 +83,7 @@ impl RewardCache { if self .initialization .as_ref() - .map_or(true, |init| *init != new_init) + .is_none_or(|init| *init != new_init) { self.update_previous_epoch_participation(state) .map_err(OpPoolError::RewardCacheUpdatePrevEpoch)?; diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 4c2daecdd3..9934d34493 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -1460,6 +1460,15 @@ pub fn cli_app() -> Command { .action(ArgAction::Set) .display_order(0) ) + .arg( + Arg::new("builder-disable-ssz") + .long("builder-disable-ssz") + .value_name("BOOLEAN") + .help("Disables sending requests using SSZ over the builder API.") + .requires("builder") + .action(ArgAction::SetTrue) + .display_order(0) + ) .arg( Arg::new("reset-payload-statuses") .long("reset-payload-statuses") @@ -1509,6 +1518,19 @@ pub fn cli_app() -> Command { .help_heading(FLAG_HEADER) .display_order(0) ) + .arg( + Arg::new("sync-tolerance-epochs") + .long("sync-tolerance-epochs") + .help("Overrides the default SYNC_TOLERANCE_EPOCHS. This flag is not intended \ + for production and MUST only be used in TESTING only. This is primarily used \ + for testing range sync, to prevent the node from producing a block before the \ + node is synced with the network which may result in the node getting \ + disconnected from peers immediately.") + .hide(true) + .requires("enable_http") + .action(ArgAction::Set) + .display_order(0) + ) .arg( Arg::new("gui") .long("gui") @@ -1599,5 +1621,30 @@ pub fn cli_app() -> Command { .action(ArgAction::Set) .display_order(0) ) + .arg( + Arg::new("delay-block-publishing") + .long("delay-block-publishing") + .value_name("SECONDS") + .action(ArgAction::Set) + .help_heading(FLAG_HEADER) + .help("TESTING ONLY: Artificially delay block publishing by the specified number of seconds. \ + This only works for if `BroadcastValidation::Gossip` is used (default). \ + DO NOT USE IN PRODUCTION.") + .hide(true) + .display_order(0) + ) + .arg( + Arg::new("delay-data-column-publishing") + .long("delay-data-column-publishing") + .value_name("SECONDS") + .action(ArgAction::Set) + .help_heading(FLAG_HEADER) + .help("TESTING ONLY: Artificially delay data column publishing by the specified number of seconds. \ + Limitation: If `delay-block-publishing` is also used, data columns will be delayed for a \ + minimum of `delay-block-publishing` seconds. + DO NOT USE IN PRODUCTION.") + .hide(true) + .display_order(0) + ) .group(ArgGroup::new("enable_http").args(["http", "gui", "staking"]).multiple(true)) } diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 24d569bea2..bcebc06c9c 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -18,7 +18,6 @@ use http_api::TlsConfig; use lighthouse_network::ListenAddress; use lighthouse_network::{multiaddr::Protocol, Enr, Multiaddr, NetworkConfig, PeerIdSerialized}; use sensitive_url::SensitiveUrl; -use slog::{info, warn, Logger}; use std::cmp::max; use std::fmt::Debug; use std::fs; @@ -29,6 +28,7 @@ use std::num::NonZeroU16; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; +use tracing::{error, info, warn}; use types::graffiti::GraffitiString; use types::{Checkpoint, Epoch, EthSpec, Hash256, PublicKeyBytes}; @@ -46,7 +46,6 @@ pub fn get_config( context: &RuntimeContext, ) -> Result { let spec = &context.eth2_config.spec; - let log = context.log(); let mut client_config = ClientConfig::default(); @@ -64,12 +63,10 @@ pub fn get_config( let stdin_inputs = cfg!(windows) || cli_args.get_flag(STDIN_INPUTS_FLAG); if std::io::stdin().is_terminal() || stdin_inputs { info!( - log, "You are about to delete the chain database. This is irreversable \ and you will need to resync the chain." ); info!( - log, "Type 'confirm' to delete the database. Any other input will leave \ the database intact and Lighthouse will exit." ); @@ -80,14 +77,13 @@ pub fn get_config( let freezer_db = client_config.get_freezer_db_path(); let blobs_db = client_config.get_blobs_db_path(); purge_db(chain_db, freezer_db, blobs_db)?; - info!(log, "Database was deleted."); + info!("Database was deleted."); } else { - info!(log, "Database was not deleted. Lighthouse will now close."); + info!("Database was not deleted. Lighthouse will now close."); std::process::exit(1); } } else { warn!( - log, "The `--purge-db` flag was passed, but Lighthouse is not running \ interactively. The database was not purged. Use `--purge-db-force` \ to purge the database without requiring confirmation." @@ -104,7 +100,7 @@ pub fn get_config( let mut log_dir = client_config.data_dir().clone(); // remove /beacon from the end log_dir.pop(); - info!(log, "Data directory initialised"; "datadir" => log_dir.into_os_string().into_string().expect("Datadir should be a valid os string")); + info!(datadir = %log_dir.into_os_string().into_string().expect("Datadir should be a valid os string"), "Data directory initialised"); /* * Networking @@ -112,7 +108,7 @@ pub fn get_config( let data_dir_ref = client_config.data_dir().clone(); - set_network_config(&mut client_config.network, cli_args, &data_dir_ref, log)?; + set_network_config(&mut client_config.network, cli_args, &data_dir_ref)?; /* * Staking flag @@ -174,14 +170,10 @@ pub fn get_config( client_config.http_api.duplicate_block_status_code = parse_required(cli_args, "http-duplicate-block-status")?; - - client_config.http_api.enable_light_client_server = - !cli_args.get_flag("disable-light-client-server"); } if cli_args.get_flag("light-client-server") { warn!( - log, "The --light-client-server flag is deprecated. The light client server is enabled \ by default" ); @@ -191,6 +183,12 @@ pub fn get_config( client_config.chain.enable_light_client_server = false; } + if let Some(sync_tolerance_epochs) = + clap_utils::parse_optional(cli_args, "sync-tolerance-epochs")? + { + client_config.chain.sync_tolerance_epochs = sync_tolerance_epochs; + } + if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? { client_config.chain.shuffling_cache_size = cache_size; } @@ -256,8 +254,8 @@ pub fn get_config( // (e.g. using the --staking flag). if cli_args.get_flag("staking") { warn!( - log, - "Running HTTP server on port {}", client_config.http_api.listen_port + "Running HTTP server on port {}", + client_config.http_api.listen_port ); } @@ -271,11 +269,11 @@ pub fn get_config( */ if cli_args.get_flag("dummy-eth1") { - warn!(log, "The --dummy-eth1 flag is deprecated"); + warn!("The --dummy-eth1 flag is deprecated"); } if cli_args.get_flag("eth1") { - warn!(log, "The --eth1 flag is deprecated"); + warn!("The --eth1 flag is deprecated"); } if let Some(val) = cli_args.get_one::("eth1-blocks-per-log-query") { @@ -303,18 +301,14 @@ pub fn get_config( endpoints.as_str(), SensitiveUrl::parse, "--execution-endpoint", - log, )?; // JWTs are required if `--execution-endpoint` is supplied. They can be either passed via // file_path or directly as string. - let secret_file: PathBuf; // Parse a single JWT secret from a given file_path, logging warnings if multiple are supplied. if let Some(secret_files) = cli_args.get_one::("execution-jwt") { - secret_file = - parse_only_one_value(secret_files, PathBuf::from_str, "--execution-jwt", log)?; - + secret_file = parse_only_one_value(secret_files, PathBuf::from_str, "--execution-jwt")?; // Check if the JWT secret key is passed directly via cli flag and persist it to the default // file location. } else if let Some(jwt_secret_key) = cli_args.get_one::("execution-jwt-secret-key") { @@ -337,8 +331,7 @@ pub fn get_config( // Parse and set the payload builder, if any. if let Some(endpoint) = cli_args.get_one::("builder") { - let payload_builder = - parse_only_one_value(endpoint, SensitiveUrl::parse, "--builder", log)?; + let payload_builder = parse_only_one_value(endpoint, SensitiveUrl::parse, "--builder")?; el_config.builder_url = Some(payload_builder); el_config.builder_user_agent = clap_utils::parse_optional(cli_args, "builder-user-agent")?; @@ -346,6 +339,8 @@ pub fn get_config( el_config.builder_header_timeout = clap_utils::parse_optional(cli_args, "builder-header-timeout")? .map(Duration::from_millis); + + el_config.disable_builder_ssz_requests = cli_args.get_flag("builder-disable-ssz"); } // Set config values from parse values. @@ -437,7 +432,7 @@ pub fn get_config( } if clap_utils::parse_optional::(cli_args, "slots-per-restore-point")?.is_some() { - warn!(log, "The slots-per-restore-point flag is deprecated"); + warn!("The slots-per-restore-point flag is deprecated"); } if let Some(backend) = clap_utils::parse_optional(cli_args, "beacon-node-backend")? { @@ -510,10 +505,9 @@ pub fn get_config( client_config.eth1.set_block_cache_truncation::(spec); info!( - log, - "Deposit contract"; - "deploy_block" => client_config.eth1.deposit_contract_deploy_block, - "address" => &client_config.eth1.deposit_contract_address + deploy_block = client_config.eth1.deposit_contract_deploy_block, + address = &client_config.eth1.deposit_contract_address, + "Deposit contract" ); // Only append network config bootnodes if discovery is not disabled @@ -895,14 +889,19 @@ pub fn get_config( .max_gossip_aggregate_batch_size = clap_utils::parse_required(cli_args, "beacon-processor-aggregate-batch-size")?; + if let Some(delay) = clap_utils::parse_optional(cli_args, "delay-block-publishing")? { + client_config.chain.block_publishing_delay = Some(Duration::from_secs_f64(delay)); + } + + if let Some(delay) = clap_utils::parse_optional(cli_args, "delay-data-column-publishing")? { + client_config.chain.data_column_publishing_delay = Some(Duration::from_secs_f64(delay)); + } + Ok(client_config) } /// Gets the listening_addresses for lighthouse based on the cli options. -pub fn parse_listening_addresses( - cli_args: &ArgMatches, - log: &Logger, -) -> Result { +pub fn parse_listening_addresses(cli_args: &ArgMatches) -> Result { let listen_addresses_str = cli_args .get_many::("listen-address") .unwrap_or_default(); @@ -1005,7 +1004,7 @@ pub fn parse_listening_addresses( (None, Some(ipv6)) => { // A single ipv6 address was provided. Set the ports if cli_args.value_source("port6") == Some(ValueSource::CommandLine) { - warn!(log, "When listening only over IPv6, use the --port flag. The value of --port6 will be ignored."); + warn!("When listening only over IPv6, use the --port flag. The value of --port6 will be ignored."); } // If we are only listening on ipv6 and the user has specified --port6, lets just use @@ -1019,11 +1018,11 @@ pub fn parse_listening_addresses( .unwrap_or(port); if maybe_disc6_port.is_some() { - warn!(log, "When listening only over IPv6, use the --discovery-port flag. The value of --discovery-port6 will be ignored.") + warn!("When listening only over IPv6, use the --discovery-port flag. The value of --discovery-port6 will be ignored.") } if maybe_quic6_port.is_some() { - warn!(log, "When listening only over IPv6, use the --quic-port flag. The value of --quic-port6 will be ignored.") + warn!("When listening only over IPv6, use the --quic-port flag. The value of --quic-port6 will be ignored.") } // use zero ports if required. If not, use the specific udp port. If none given, use @@ -1145,7 +1144,6 @@ pub fn set_network_config( config: &mut NetworkConfig, cli_args: &ArgMatches, data_dir: &Path, - log: &Logger, ) -> Result<(), String> { // If a network dir has been specified, override the `datadir` definition. if let Some(dir) = cli_args.get_one::("network-dir") { @@ -1170,7 +1168,7 @@ pub fn set_network_config( config.shutdown_after_sync = true; } - config.set_listening_addr(parse_listening_addresses(cli_args, log)?); + config.set_listening_addr(parse_listening_addresses(cli_args)?); // A custom target-peers command will overwrite the --proposer-only default. if let Some(target_peers_str) = cli_args.get_one::("target-peers") { @@ -1198,10 +1196,10 @@ pub fn set_network_config( .parse() .map_err(|_| format!("Not valid as ENR nor Multiaddr: {}", addr))?; if !multi.iter().any(|proto| matches!(proto, Protocol::Udp(_))) { - slog::error!(log, "Missing UDP in Multiaddr {}", multi.to_string()); + error!(multiaddr = multi.to_string(), "Missing UDP in Multiaddr"); } if !multi.iter().any(|proto| matches!(proto, Protocol::P2p(_))) { - slog::error!(log, "Missing P2P in Multiaddr {}", multi.to_string()); + error!(multiaddr = multi.to_string(), "Missing P2P in Multiaddr"); } multiaddrs.push(multi); } @@ -1236,7 +1234,7 @@ pub fn set_network_config( }) .collect::, _>>()?; if config.trusted_peers.len() >= config.target_peers { - slog::warn!(log, "More trusted peers than the target peer limit. This will prevent efficient peer selection criteria."; "target_peers" => config.target_peers, "trusted_peers" => config.trusted_peers.len()); + warn!( target_peers = config.target_peers, trusted_peers = config.trusted_peers.len(),"More trusted peers than the target peer limit. This will prevent efficient peer selection criteria."); } } @@ -1336,14 +1334,14 @@ pub fn set_network_config( match addr.parse::() { Ok(IpAddr::V4(v4_addr)) => { if let Some(used) = enr_ip4.as_ref() { - warn!(log, "More than one Ipv4 ENR address provided"; "used" => %used, "ignored" => %v4_addr) + warn!(used = %used, ignored = %v4_addr, "More than one Ipv4 ENR address provided") } else { enr_ip4 = Some(v4_addr) } } Ok(IpAddr::V6(v6_addr)) => { if let Some(used) = enr_ip6.as_ref() { - warn!(log, "More than one Ipv6 ENR address provided"; "used" => %used, "ignored" => %v6_addr) + warn!(used = %used, ignored = %v6_addr,"More than one Ipv6 ENR address provided") } else { enr_ip6 = Some(v6_addr) } @@ -1409,13 +1407,13 @@ pub fn set_network_config( } if parse_flag(cli_args, "disable-packet-filter") { - warn!(log, "Discv5 packet filter is disabled"); + warn!("Discv5 packet filter is disabled"); config.discv5_config.enable_packet_filter = false; } if parse_flag(cli_args, "disable-discovery") { config.disable_discovery = true; - warn!(log, "Discovery is disabled. New peers will not be found"); + warn!("Discovery is disabled. New peers will not be found"); } if parse_flag(cli_args, "disable-quic") { @@ -1462,7 +1460,10 @@ pub fn set_network_config( config.target_peers = 15; } config.proposer_only = true; - warn!(log, "Proposer-only mode enabled"; "info"=> "Do not connect a validator client to this node unless via the --proposer-nodes flag"); + warn!( + info = "Proposer-only mode enabled", + "Do not connect a validator client to this node unless via the --proposer-nodes flag" + ); } // The inbound rate limiter is enabled by default unless `disabled` via the // `disable-inbound-rate-limiter` flag. @@ -1520,7 +1521,6 @@ pub fn parse_only_one_value( cli_value: &str, parser: F, flag_name: &str, - log: &Logger, ) -> Result where F: Fn(&str) -> Result, @@ -1534,11 +1534,10 @@ where if values.len() > 1 { warn!( - log, - "Multiple values provided"; - "info" => "multiple values are deprecated, only the first value will be used", - "count" => values.len(), - "flag" => flag_name + info = "Multiple values provided", + count = values.len(), + flag = flag_name, + "multiple values are deprecated, only the first value will be used" ); } diff --git a/beacon_node/src/lib.rs b/beacon_node/src/lib.rs index e3802c837c..a7f92434ce 100644 --- a/beacon_node/src/lib.rs +++ b/beacon_node/src/lib.rs @@ -12,10 +12,10 @@ pub use config::{get_config, get_data_dir, set_network_config}; use environment::RuntimeContext; pub use eth2_config::Eth2Config; use slasher::{DatabaseBackendOverride, Slasher}; -use slog::{info, warn}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; use store::database::interface::BeaconNodeBackend; +use tracing::{info, warn}; use types::{ChainSpec, Epoch, EthSpec, ForkName}; /// A type-alias to the tighten the definition of a production-intended `Client`. @@ -63,7 +63,6 @@ impl ProductionBeaconNode { let spec = context.eth2_config().spec.clone(); let client_genesis = client_config.genesis.clone(); let store_config = client_config.store.clone(); - let log = context.log().clone(); let _datadir = client_config.create_data_dir()?; let db_path = client_config.create_db_path()?; let freezer_db_path = client_config.create_freezer_db_path()?; @@ -72,20 +71,18 @@ impl ProductionBeaconNode { if let Some(legacy_dir) = client_config.get_existing_legacy_data_dir() { warn!( - log, - "Legacy datadir location"; - "msg" => "this occurs when using relative paths for a datadir location", - "location" => ?legacy_dir, + msg = "this occurs when using relative paths for a datadir location", + location = ?legacy_dir, + "Legacy datadir location" ) } if let Err(misaligned_forks) = validator_fork_epochs(&spec) { warn!( - log, - "Fork boundaries are not well aligned / multiples of 256"; - "info" => "This may cause issues as fork boundaries do not align with the \ - start of sync committee period.", - "misaligned_forks" => ?misaligned_forks, + info = "This may cause issues as fork boundaries do not align with the \ + start of sync committee period.", + ?misaligned_forks, + "Fork boundaries are not well aligned / multiples of 256" ); } @@ -94,42 +91,30 @@ impl ProductionBeaconNode { .chain_spec(spec.clone()) .beacon_processor(client_config.beacon_processor.clone()) .http_api_config(client_config.http_api.clone()) - .disk_store( - &db_path, - &freezer_db_path, - &blobs_db_path, - store_config, - log.clone(), - )?; + .disk_store(&db_path, &freezer_db_path, &blobs_db_path, store_config)?; let builder = if let Some(mut slasher_config) = client_config.slasher.clone() { match slasher_config.override_backend() { DatabaseBackendOverride::Success(old_backend) => { info!( - log, - "Slasher backend overridden"; - "reason" => "database exists", - "configured_backend" => %old_backend, - "override_backend" => %slasher_config.backend, + reason = "database exists", + configured_backend = %old_backend, + override_backend = %slasher_config.backend, + "Slasher backend overridden" ); } DatabaseBackendOverride::Failure(path) => { warn!( - log, - "Slasher backend override failed"; - "advice" => "delete old MDBX database or enable MDBX backend", - "path" => path.display() + advice = "delete old MDBX database or enable MDBX backend", + path = %path.display(), + "Slasher backend override failed" ); } _ => {} } let slasher = Arc::new( - Slasher::open( - slasher_config, - spec, - log.new(slog::o!("service" => "slasher")), - ) - .map_err(|e| format!("Slasher open error: {:?}", e))?, + Slasher::open(slasher_config, spec) + .map_err(|e| format!("Slasher open error: {:?}", e))?, ); builder.slasher(slasher) } else { @@ -149,19 +134,17 @@ impl ProductionBeaconNode { .await?; let builder = if client_config.sync_eth1_chain { info!( - log, - "Block production enabled"; - "endpoint" => format!("{:?}", &client_config.eth1.endpoint), - "method" => "json rpc via http" + endpoint = ?client_config.eth1.endpoint, + method = "json rpc via http", + "Block production enabled" ); builder .caching_eth1_backend(client_config.eth1.clone()) .await? } else { info!( - log, - "Block production disabled"; - "reason" => "no eth1 backend configured" + reason = "no eth1 backend configured", + "Block production disabled" ); builder.no_eth1_backend()? }; diff --git a/beacon_node/store/Cargo.toml b/beacon_node/store/Cargo.toml index d2f3a5c562..d17a8f04d6 100644 --- a/beacon_node/store/Cargo.toml +++ b/beacon_node/store/Cargo.toml @@ -30,12 +30,12 @@ parking_lot = { workspace = true } redb = { version = "2.1.3", optional = true } safe_arith = { workspace = true } serde = { workspace = true } -slog = { workspace = true } -sloggers = { workspace = true } smallvec = { workspace = true } state_processing = { workspace = true } strum = { workspace = true } superstruct = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } xdelta3 = { workspace = true } zstd = { workspace = true } diff --git a/beacon_node/store/src/chunked_iter.rs b/beacon_node/store/src/chunked_iter.rs index 8f6682e758..f2821286ec 100644 --- a/beacon_node/store/src/chunked_iter.rs +++ b/beacon_node/store/src/chunked_iter.rs @@ -1,6 +1,6 @@ use crate::chunked_vector::{chunk_key, Chunk, Field}; use crate::{HotColdDB, ItemStore}; -use slog::error; +use tracing::error; use types::{ChainSpec, EthSpec, Slot}; /// Iterator over the values of a `BeaconState` vector field (like `block_roots`). @@ -82,9 +82,8 @@ where .cloned() .or_else(|| { error!( - self.store.log, - "Missing chunk value in forwards iterator"; - "vector index" => vindex + vector_index = vindex, + "Missing chunk value in forwards iterator" ); None })?; @@ -100,19 +99,17 @@ where ) .map_err(|e| { error!( - self.store.log, - "Database error in forwards iterator"; - "chunk index" => self.next_cindex, - "error" => format!("{:?}", e) + chunk_index = self.next_cindex, + error = ?e, + "Database error in forwards iterator" ); e }) .ok()? .or_else(|| { error!( - self.store.log, - "Missing chunk in forwards iterator"; - "chunk index" => self.next_cindex + chunk_index = self.next_cindex, + "Missing chunk in forwards iterator" ); None })?; diff --git a/beacon_node/store/src/garbage_collection.rs b/beacon_node/store/src/garbage_collection.rs index 06393f2d21..586db44c89 100644 --- a/beacon_node/store/src/garbage_collection.rs +++ b/beacon_node/store/src/garbage_collection.rs @@ -2,7 +2,7 @@ use crate::database::interface::BeaconNodeBackend; use crate::hot_cold_store::HotColdDB; use crate::{DBColumn, Error}; -use slog::debug; +use tracing::debug; use types::EthSpec; impl HotColdDB, BeaconNodeBackend> @@ -24,11 +24,7 @@ where } }); if !ops.is_empty() { - debug!( - self.log, - "Garbage collecting {} temporary states", - ops.len() - ); + debug!("Garbage collecting {} temporary states", ops.len()); self.delete_batch(DBColumn::BeaconState, ops.clone())?; self.delete_batch(DBColumn::BeaconStateSummary, ops.clone())?; diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index e4a857b799..0a545529ca 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -22,7 +22,6 @@ use lru::LruCache; use parking_lot::{Mutex, RwLock}; use safe_arith::SafeArith; use serde::{Deserialize, Serialize}; -use slog::{debug, error, info, trace, warn, Logger}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use state_processing::{ @@ -37,6 +36,7 @@ use std::num::NonZeroUsize; use std::path::Path; use std::sync::Arc; use std::time::Duration; +use tracing::{debug, error, info, trace, warn}; use types::data_column_sidecar::{ColumnIndex, DataColumnSidecar, DataColumnSidecarList}; use types::*; use zstd::{Decoder, Encoder}; @@ -81,8 +81,6 @@ pub struct HotColdDB, Cold: ItemStore> { historic_state_cache: Mutex>, /// Chain spec. pub(crate) spec: Arc, - /// Logger. - pub log: Logger, /// Mere vessel for E. _phantom: PhantomData, } @@ -203,7 +201,6 @@ impl HotColdDB, MemoryStore> { pub fn open_ephemeral( config: StoreConfig, spec: Arc, - log: Logger, ) -> Result, MemoryStore>, Error> { config.verify::()?; @@ -226,7 +223,6 @@ impl HotColdDB, MemoryStore> { config, hierarchy, spec, - log, _phantom: PhantomData, }; @@ -246,7 +242,6 @@ impl HotColdDB, BeaconNodeBackend> { migrate_schema: impl FnOnce(Arc, SchemaVersion, SchemaVersion) -> Result<(), Error>, config: StoreConfig, spec: Arc, - log: Logger, ) -> Result, Error> { config.verify::()?; @@ -272,10 +267,8 @@ impl HotColdDB, BeaconNodeBackend> { config, hierarchy, spec, - log, _phantom: PhantomData, }; - // Load the config from disk but don't error on a failed read because the config itself may // need migrating. let _ = db.load_config(); @@ -287,10 +280,9 @@ impl HotColdDB, BeaconNodeBackend> { *db.split.write() = split; info!( - db.log, - "Hot-Cold DB initialized"; - "split_slot" => split.slot, - "split_state" => ?split.state_root + %split.slot, + split_state = ?split.state_root, + "Hot-Cold DB initialized" ); } @@ -352,11 +344,10 @@ impl HotColdDB, BeaconNodeBackend> { )?; info!( - db.log, - "Blob DB initialized"; - "path" => ?blobs_db_path, - "oldest_blob_slot" => ?new_blob_info.oldest_blob_slot, - "oldest_data_column_slot" => ?new_data_column_info.oldest_data_column_slot, + path = ?blobs_db_path, + oldest_blob_slot = ?new_blob_info.oldest_blob_slot, + oldest_data_column_slot = ?new_data_column_info.oldest_data_column_slot, + "Blob DB initialized" ); // Ensure that the schema version of the on-disk database matches the software. @@ -364,10 +355,9 @@ impl HotColdDB, BeaconNodeBackend> { let db = Arc::new(db); if let Some(schema_version) = db.load_schema_version()? { debug!( - db.log, - "Attempting schema migration"; - "from_version" => schema_version.as_u64(), - "to_version" => CURRENT_SCHEMA_VERSION.as_u64(), + from_version = schema_version.as_u64(), + to_version = CURRENT_SCHEMA_VERSION.as_u64(), + "Attempting schema migration" ); migrate_schema(db.clone(), schema_version, CURRENT_SCHEMA_VERSION)?; } else { @@ -385,10 +375,9 @@ impl HotColdDB, BeaconNodeBackend> { if let Ok(hierarchy_config) = disk_config.hierarchy_config() { if &db.config.hierarchy_config != hierarchy_config { info!( - db.log, - "Updating historic state config"; - "previous_config" => %hierarchy_config, - "new_config" => %db.config.hierarchy_config, + previous_config = %hierarchy_config, + new_config = %db.config.hierarchy_config, + "Updating historic state config" ); } } @@ -400,9 +389,9 @@ impl HotColdDB, BeaconNodeBackend> { // If configured, run a foreground compaction pass. if db.config.compact_on_init { - info!(db.log, "Running foreground compaction"); + info!("Running foreground compaction"); db.compact()?; - info!(db.log, "Foreground compaction complete"); + info!("Foreground compaction complete"); } Ok(db) @@ -903,7 +892,7 @@ impl, Cold: ItemStore> HotColdDB state_root: &Hash256, summary: HotStateSummary, ) -> Result<(), Error> { - self.hot_db.put(state_root, &summary).map_err(Into::into) + self.hot_db.put(state_root, &summary) } /// Store a state in the store. @@ -991,12 +980,7 @@ impl, Cold: ItemStore> HotColdDB let split = self.split.read_recursive(); if state_root != split.state_root { - warn!( - self.log, - "State cache missed"; - "state_root" => ?state_root, - "block_root" => ?block_root, - ); + warn!(?state_root, ?block_root, "State cache missed"); } // Sanity check max-slot against the split slot. @@ -1025,10 +1009,9 @@ impl, Cold: ItemStore> HotColdDB .lock() .put_state(*state_root, block_root, state)?; debug!( - self.log, - "Cached state"; - "state_root" => ?state_root, - "slot" => state.slot(), + ?state_root, + slot = %state.slot(), + "Cached state" ); } drop(split); @@ -1248,7 +1231,7 @@ impl, Cold: ItemStore> HotColdDB state_root.as_slice().to_vec(), )); - if slot.map_or(true, |slot| slot % E::slots_per_epoch() == 0) { + if slot.is_none_or(|slot| slot % E::slots_per_epoch() == 0) { key_value_batch.push(KeyValueStoreOp::DeleteKey( DBColumn::BeaconState, state_root.as_slice().to_vec(), @@ -1308,9 +1291,9 @@ impl, Cold: ItemStore> HotColdDB Ok(BlobSidecarListFromRoot::NoBlobs | BlobSidecarListFromRoot::NoRoot) => {} Err(e) => { error!( - self.log, "Error getting blobs"; - "block_root" => %block_root, - "error" => ?e + %block_root, + error = ?e, + "Error getting blobs" ); } } @@ -1333,9 +1316,9 @@ impl, Cold: ItemStore> HotColdDB } Err(e) => { error!( - self.log, "Error getting data columns"; - "block_root" => %block_root, - "error" => ?e + %block_root, + error = ?e, + "Error getting data columns" ); } } @@ -1363,10 +1346,9 @@ impl, Cold: ItemStore> HotColdDB // Rollback on failure if let Err(e) = tx_res { error!( - self.log, - "Database write failed"; - "error" => ?e, - "action" => "reverting blob DB changes" + error = ?e, + action = "reverting blob DB changes", + "Database write failed" ); let mut blob_cache_ops = blob_cache_ops; for op in blob_cache_ops.iter_mut() { @@ -1475,10 +1457,9 @@ impl, Cold: ItemStore> HotColdDB .put_state(*state_root, block_root, state)? { debug!( - self.log, - "Skipping storage of cached state"; - "slot" => state.slot(), - "state_root" => ?state_root + slot = %state.slot(), + ?state_root, + "Skipping storage of cached state" ); return Ok(()); } @@ -1486,10 +1467,9 @@ impl, Cold: ItemStore> HotColdDB // On the epoch boundary, store the full state. if state.slot() % E::slots_per_epoch() == 0 { trace!( - self.log, - "Storing full state on epoch boundary"; - "slot" => state.slot().as_u64(), - "state_root" => format!("{:?}", state_root) + slot = %state.slot().as_u64(), + ?state_root, + "Storing full state on epoch boundary" ); store_full_state(state_root, state, ops)?; } @@ -1512,11 +1492,7 @@ impl, Cold: ItemStore> HotColdDB if *state_root != self.get_split_info().state_root { // Do not warn on start up when loading the split state. - warn!( - self.log, - "State cache missed"; - "state_root" => ?state_root, - ); + warn!(?state_root, "State cache missed"); } let state_from_disk = self.load_hot_state(state_root)?; @@ -1528,10 +1504,9 @@ impl, Cold: ItemStore> HotColdDB .lock() .put_state(*state_root, block_root, &state)?; debug!( - self.log, - "Cached state"; - "state_root" => ?state_root, - "slot" => state.slot(), + ?state_root, + slot = %state.slot(), + "Cached state" ); Ok(Some(state)) } else { @@ -1595,10 +1570,9 @@ impl, Cold: ItemStore> HotColdDB .put_state(state_root, latest_block_root, state)? { debug!( - self.log, - "Cached ancestor state"; - "state_root" => ?state_root, - "slot" => slot, + ?state_root, + %slot, + "Cached ancestor state" ); } Ok(()) @@ -1650,35 +1624,31 @@ impl, Cold: ItemStore> HotColdDB match self.hierarchy.storage_strategy(slot)? { StorageStrategy::ReplayFrom(from) => { debug!( - self.log, - "Storing cold state"; - "strategy" => "replay", - "from_slot" => from, - "slot" => state.slot(), + strategy = "replay", + from_slot = %from, + %slot, + "Storing cold state", ); // Already have persisted the state summary, don't persist anything else } StorageStrategy::Snapshot => { debug!( - self.log, - "Storing cold state"; - "strategy" => "snapshot", - "slot" => state.slot(), + strategy = "snapshot", + %slot, + "Storing cold state" ); self.store_cold_state_as_snapshot(state, ops)?; } StorageStrategy::DiffFrom(from) => { debug!( - self.log, - "Storing cold state"; - "strategy" => "diff", - "from_slot" => from, - "slot" => state.slot(), + strategy = "diff", + from_slot = %from, + %slot, + "Storing cold state" ); self.store_cold_state_as_diff(state, from, ops)?; } } - Ok(()) } @@ -1837,10 +1807,9 @@ impl, Cold: ItemStore> HotColdDB metrics::start_timer(&metrics::STORE_BEACON_COLD_BUILD_BEACON_CACHES_TIME); base_state.build_all_caches(&self.spec)?; debug!( - self.log, - "Built caches for historic state"; - "target_slot" => slot, - "build_time_ms" => metrics::stop_timer_with_duration(cache_timer).as_millis() + target_slot = %slot, + build_time_ms = metrics::stop_timer_with_duration(cache_timer).as_millis(), + "Built caches for historic state" ); self.historic_state_cache .lock() @@ -1862,10 +1831,9 @@ impl, Cold: ItemStore> HotColdDB })?; let state = self.replay_blocks(base_state, blocks, slot, Some(state_root_iter), None)?; debug!( - self.log, - "Replayed blocks for historic state"; - "target_slot" => slot, - "replay_time_ms" => metrics::stop_timer_with_duration(replay_timer).as_millis() + target_slot = %slot, + replay_time_ms = metrics::stop_timer_with_duration(replay_timer).as_millis(), + "Replayed blocks for historic state" ); self.historic_state_cache @@ -1893,9 +1861,8 @@ impl, Cold: ItemStore> HotColdDB fn load_hdiff_buffer_for_slot(&self, slot: Slot) -> Result<(Slot, HDiffBuffer), Error> { if let Some(buffer) = self.historic_state_cache.lock().get_hdiff_buffer(slot) { debug!( - self.log, - "Hit hdiff buffer cache"; - "slot" => slot + %slot, + "Hit hdiff buffer cache" ); metrics::inc_counter(&metrics::STORE_BEACON_HDIFF_BUFFER_CACHE_HIT); return Ok((slot, buffer)); @@ -1919,10 +1886,9 @@ impl, Cold: ItemStore> HotColdDB let load_time_ms = t.elapsed().as_millis(); debug!( - self.log, - "Cached state and hdiff buffer"; - "load_time_ms" => load_time_ms, - "slot" => slot + load_time_ms, + %slot, + "Cached state and hdiff buffer" ); Ok((slot, buffer)) @@ -1945,10 +1911,9 @@ impl, Cold: ItemStore> HotColdDB let load_time_ms = t.elapsed().as_millis(); debug!( - self.log, - "Cached hdiff buffer"; - "load_time_ms" => load_time_ms, - "slot" => slot + load_time_ms, + %slot, + "Cached hdiff buffer" ); Ok((slot, buffer)) @@ -2052,9 +2017,8 @@ impl, Cold: ItemStore> HotColdDB .map(|block_replayer| { if have_state_root_iterator && block_replayer.state_root_miss() { warn!( - self.log, - "State root cache miss during block replay"; - "slot" => target_slot, + slot = %target_slot, + "State root cache miss during block replay" ); } block_replayer.into_state() @@ -2180,11 +2144,6 @@ impl, Cold: ItemStore> HotColdDB &self.spec } - /// Get a reference to the `Logger` used by the database. - pub fn logger(&self) -> &Logger { - &self.log - } - /// Fetch a copy of the current split slot from memory. pub fn get_split_slot(&self) -> Slot { self.split.read_recursive().slot @@ -2579,17 +2538,9 @@ impl, Cold: ItemStore> HotColdDB columns.extend(previous_schema_columns); for column in columns { - info!( - self.log, - "Starting compaction"; - "column" => ?column - ); + info!(?column, "Starting compaction"); self.cold_db.compact_column(column)?; - info!( - self.log, - "Finishing compaction"; - "column" => ?column - ); + info!(?column, "Finishing compaction"); } Ok(()) } @@ -2690,16 +2641,15 @@ impl, Cold: ItemStore> HotColdDB })??; if already_pruned && !force { - info!(self.log, "Execution payloads are pruned"); + info!("Execution payloads are pruned"); return Ok(()); } // Iterate block roots backwards to the Bellatrix fork or the anchor slot, whichever comes // first. warn!( - self.log, - "Pruning finalized payloads"; - "info" => "you may notice degraded I/O performance while this runs" + info = "you may notice degraded I/O performance while this runs", + "Pruning finalized payloads" ); let anchor_info = self.get_anchor_info(); @@ -2713,58 +2663,41 @@ impl, Cold: ItemStore> HotColdDB Ok(tuple) => tuple, Err(e) => { warn!( - self.log, - "Stopping payload pruning early"; - "error" => ?e, + error = ?e, + "Stopping payload pruning early" ); break; } }; if slot < bellatrix_fork_slot { - info!( - self.log, - "Payload pruning reached Bellatrix boundary"; - ); + info!("Payload pruning reached Bellatrix boundary"); break; } if Some(block_root) != last_pruned_block_root && self.execution_payload_exists(&block_root)? { - debug!( - self.log, - "Pruning execution payload"; - "slot" => slot, - "block_root" => ?block_root, - ); + debug!(%slot, ?block_root, "Pruning execution payload"); last_pruned_block_root = Some(block_root); ops.push(StoreOp::DeleteExecutionPayload(block_root)); } if slot <= anchor_info.oldest_block_slot { - info!( - self.log, - "Payload pruning reached anchor oldest block slot"; - "slot" => slot - ); + info!(%slot, "Payload pruning reached anchor oldest block slot"); break; } } let payloads_pruned = ops.len(); self.do_atomically_with_block_and_blobs_cache(ops)?; - info!( - self.log, - "Execution payload pruning complete"; - "payloads_pruned" => payloads_pruned, - ); + info!(%payloads_pruned, "Execution payload pruning complete"); Ok(()) } /// Try to prune blobs, approximating the current epoch from the split slot. pub fn try_prune_most_blobs(&self, force: bool) -> Result<(), Error> { let Some(deneb_fork_epoch) = self.spec.deneb_fork_epoch else { - debug!(self.log, "Deneb fork is disabled"); + debug!("Deneb fork is disabled"); return Ok(()); }; // The current epoch is >= split_epoch + 2. It could be greater if the database is @@ -2795,7 +2728,7 @@ impl, Cold: ItemStore> HotColdDB data_availability_boundary: Epoch, ) -> Result<(), Error> { if self.spec.deneb_fork_epoch.is_none() { - debug!(self.log, "Deneb fork is disabled"); + debug!("Deneb fork is disabled"); return Ok(()); } @@ -2804,17 +2737,13 @@ impl, Cold: ItemStore> HotColdDB let epochs_per_blob_prune = self.get_config().epochs_per_blob_prune; if !force && !pruning_enabled { - debug!( - self.log, - "Blob pruning is disabled"; - "prune_blobs" => pruning_enabled - ); + debug!(prune_blobs = pruning_enabled, "Blob pruning is disabled"); return Ok(()); } let blob_info = self.get_blob_info(); let Some(oldest_blob_slot) = blob_info.oldest_blob_slot else { - error!(self.log, "Slot of oldest blob is not known"); + error!("Slot of oldest blob is not known"); return Err(HotColdDBError::BlobPruneLogicError.into()); }; @@ -2837,13 +2766,12 @@ impl, Cold: ItemStore> HotColdDB if !force && !should_prune || !can_prune { debug!( - self.log, - "Blobs are pruned"; - "oldest_blob_slot" => oldest_blob_slot, - "data_availability_boundary" => data_availability_boundary, - "split_slot" => split.slot, - "end_epoch" => end_epoch, - "start_epoch" => start_epoch, + %oldest_blob_slot, + %data_availability_boundary, + %split.slot, + %end_epoch, + %start_epoch, + "Blobs are pruned" ); return Ok(()); } @@ -2852,21 +2780,19 @@ impl, Cold: ItemStore> HotColdDB let anchor = self.get_anchor_info(); if oldest_blob_slot < anchor.oldest_block_slot { error!( - self.log, - "Oldest blob is older than oldest block"; - "oldest_blob_slot" => oldest_blob_slot, - "oldest_block_slot" => anchor.oldest_block_slot + %oldest_blob_slot, + oldest_block_slot = %anchor.oldest_block_slot, + "Oldest blob is older than oldest block" ); return Err(HotColdDBError::BlobPruneLogicError.into()); } // Iterate block roots forwards from the oldest blob slot. debug!( - self.log, - "Pruning blobs"; - "start_epoch" => start_epoch, - "end_epoch" => end_epoch, - "data_availability_boundary" => data_availability_boundary, + %start_epoch, + %end_epoch, + %data_availability_boundary, + "Pruning blobs" ); // We collect block roots of deleted blobs in memory. Even for 10y of blob history this @@ -2922,10 +2848,7 @@ impl, Cold: ItemStore> HotColdDB let op = self.compare_and_set_blob_info(blob_info, new_blob_info)?; self.do_atomically_with_block_and_blobs_cache(vec![StoreOp::KeyValueOp(op)])?; - debug!( - self.log, - "Blob pruning complete"; - ); + debug!("Blob pruning complete"); Ok(()) } @@ -2995,18 +2918,13 @@ impl, Cold: ItemStore> HotColdDB // If we just deleted the genesis state, re-store it using the current* schema. if self.get_split_slot() > 0 { info!( - self.log, - "Re-storing genesis state"; - "state_root" => ?genesis_state_root, + state_root = ?genesis_state_root, + "Re-storing genesis state" ); self.store_cold_state(&genesis_state_root, genesis_state, &mut cold_ops)?; } - info!( - self.log, - "Deleting historic states"; - "delete_ops" => delete_ops, - ); + info!(delete_ops, "Deleting historic states"); self.cold_db.do_atomically(cold_ops)?; // In order to reclaim space, we need to compact the freezer DB as well. @@ -3022,9 +2940,8 @@ impl, Cold: ItemStore> HotColdDB pub fn prune_old_hot_states(&self) -> Result<(), Error> { let split = self.get_split_info(); debug!( - self.log, - "Database state pruning started"; - "split_slot" => split.slot, + %split.slot, + "Database state pruning started" ); let mut state_delete_batch = vec![]; for res in self @@ -3046,11 +2963,10 @@ impl, Cold: ItemStore> HotColdDB "non-canonical" }; debug!( - self.log, - "Deleting state"; - "state_root" => ?state_root, - "slot" => summary.slot, - "reason" => reason, + ?state_root, + slot = %summary.slot, + %reason, + "Deleting state" ); state_delete_batch.push(StoreOp::DeleteState(state_root, Some(summary.slot))); } @@ -3058,11 +2974,7 @@ impl, Cold: ItemStore> HotColdDB } let num_deleted_states = state_delete_batch.len(); self.do_atomically_with_block_and_blobs_cache(state_delete_batch)?; - debug!( - self.log, - "Database state pruning complete"; - "num_deleted_states" => num_deleted_states, - ); + debug!(%num_deleted_states, "Database state pruning complete"); Ok(()) } } @@ -3075,9 +2987,8 @@ pub fn migrate_database, Cold: ItemStore>( finalized_state: &BeaconState, ) -> Result<(), Error> { debug!( - store.log, - "Freezer migration started"; - "slot" => finalized_state.slot() + slot = %finalized_state.slot(), + "Freezer migration started" ); // 0. Check that the migration is sensible. @@ -3153,7 +3064,7 @@ pub fn migrate_database, Cold: ItemStore>( // stored (see `STATE_UPPER_LIMIT_NO_RETAIN`). Make an exception for the genesis state // which always needs to be copied from the hot DB to the freezer and should not be deleted. if slot != 0 && slot < anchor_info.state_upper_limit { - debug!(store.log, "Pruning finalized state"; "slot" => slot); + debug!(%slot, "Pruning finalized state"); continue; } @@ -3213,10 +3124,9 @@ pub fn migrate_database, Cold: ItemStore>( // place in code. if latest_split_slot != current_split_slot { error!( - store.log, - "Race condition detected: Split point changed while moving states to the freezer"; - "previous split slot" => current_split_slot, - "current split slot" => latest_split_slot, + previous_split_slot = %current_split_slot, + current_split_slot = %latest_split_slot, + "Race condition detected: Split point changed while moving states to the freezer" ); // Assume the freezing procedure will be retried in case this happens. @@ -3252,9 +3162,8 @@ pub fn migrate_database, Cold: ItemStore>( )?; debug!( - store.log, - "Freezer migration complete"; - "slot" => finalized_state.slot() + slot = %finalized_state.slot(), + "Freezer migration complete" ); Ok(()) diff --git a/beacon_node/store/src/iter.rs b/beacon_node/store/src/iter.rs index 97a88c01c8..0d12bbdd60 100644 --- a/beacon_node/store/src/iter.rs +++ b/beacon_node/store/src/iter.rs @@ -382,7 +382,6 @@ mod test { use crate::StoreConfig as Config; use beacon_chain::test_utils::BeaconChainHarness; use beacon_chain::types::{ChainSpec, MainnetEthSpec}; - use sloggers::{null::NullLoggerBuilder, Build}; use std::sync::Arc; use types::FixedBytesExtended; @@ -398,10 +397,8 @@ mod test { #[test] fn block_root_iter() { - let log = NullLoggerBuilder.build().unwrap(); let store = - HotColdDB::open_ephemeral(Config::default(), Arc::new(ChainSpec::minimal()), log) - .unwrap(); + HotColdDB::open_ephemeral(Config::default(), Arc::new(ChainSpec::minimal())).unwrap(); let slots_per_historical_root = MainnetEthSpec::slots_per_historical_root(); let mut state_a: BeaconState = get_state(); @@ -447,10 +444,8 @@ mod test { #[test] fn state_root_iter() { - let log = NullLoggerBuilder.build().unwrap(); let store = - HotColdDB::open_ephemeral(Config::default(), Arc::new(ChainSpec::minimal()), log) - .unwrap(); + HotColdDB::open_ephemeral(Config::default(), Arc::new(ChainSpec::minimal())).unwrap(); let slots_per_historical_root = MainnetEthSpec::slots_per_historical_root(); let mut state_a: BeaconState = get_state(); diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 0cfc42ab15..2b5be03489 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -195,7 +195,6 @@ pub trait ItemStore: KeyValueStore + Sync + Send + Sized + 'stati let key = key.as_slice(); self.put_bytes(column, key, &item.as_store_bytes()) - .map_err(Into::into) } fn put_sync(&self, key: &Hash256, item: &I) -> Result<(), Error> { @@ -203,7 +202,6 @@ pub trait ItemStore: KeyValueStore + Sync + Send + Sized + 'stati let key = key.as_slice(); self.put_bytes_sync(column, key, &item.as_store_bytes()) - .map_err(Into::into) } /// Retrieve an item from `Self`. diff --git a/beacon_node/store/src/reconstruct.rs b/beacon_node/store/src/reconstruct.rs index 2a3b208aae..30df552b7b 100644 --- a/beacon_node/store/src/reconstruct.rs +++ b/beacon_node/store/src/reconstruct.rs @@ -4,12 +4,12 @@ use crate::metadata::ANCHOR_FOR_ARCHIVE_NODE; use crate::metrics; use crate::{Error, ItemStore}; use itertools::{process_results, Itertools}; -use slog::{debug, info}; use state_processing::{ per_block_processing, per_slot_processing, BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, }; use std::sync::Arc; +use tracing::{debug, info}; use types::EthSpec; impl HotColdDB @@ -37,9 +37,8 @@ where } debug!( - self.log, - "Starting state reconstruction batch"; - "start_slot" => anchor.state_lower_limit, + start_slot = %anchor.state_lower_limit, + "Starting state reconstruction batch" ); let _t = metrics::start_timer(&metrics::STORE_BEACON_RECONSTRUCTION_TIME); @@ -124,10 +123,9 @@ where || reconstruction_complete { info!( - self.log, - "State reconstruction in progress"; - "slot" => slot, - "remaining" => upper_limit_slot - 1 - slot + %slot, + remaining = %(upper_limit_slot - 1 - slot), + "State reconstruction in progress" ); self.cold_db.do_atomically(std::mem::take(&mut io_batch))?; @@ -164,10 +162,9 @@ where // batch when there is idle capacity. if batch_complete { debug!( - self.log, - "Finished state reconstruction batch"; - "start_slot" => lower_limit_slot, - "end_slot" => slot, + start_slot = %lower_limit_slot, + end_slot = %slot, + "Finished state reconstruction batch" ); return Ok(()); } diff --git a/beacon_node/tests/test.rs b/beacon_node/tests/test.rs index 0738b12ec0..0d448e6c06 100644 --- a/beacon_node/tests/test.rs +++ b/beacon_node/tests/test.rs @@ -25,8 +25,6 @@ fn build_node(env: &mut Environment) -> LocalBeaconNode { #[test] fn http_server_genesis_state() { let mut env = env_builder() - .test_logger() - .expect("should build env logger") .multi_threaded_tokio_runtime() .expect("should start tokio runtime") .build() diff --git a/beacon_node/timer/Cargo.toml b/beacon_node/timer/Cargo.toml index 546cc2ed41..53fa2c0132 100644 --- a/beacon_node/timer/Cargo.toml +++ b/beacon_node/timer/Cargo.toml @@ -6,7 +6,7 @@ edition = { workspace = true } [dependencies] beacon_chain = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } diff --git a/beacon_node/timer/src/lib.rs b/beacon_node/timer/src/lib.rs index 7c2db69604..1bd1c1e8ea 100644 --- a/beacon_node/timer/src/lib.rs +++ b/beacon_node/timer/src/lib.rs @@ -3,22 +3,21 @@ //! This service allows task execution on the beacon node for various functionality. use beacon_chain::{BeaconChain, BeaconChainTypes}; -use slog::{info, warn}; use slot_clock::SlotClock; use std::sync::Arc; use tokio::time::sleep; +use tracing::{info, warn}; /// Spawns a timer service which periodically executes tasks for the beacon chain pub fn spawn_timer( executor: task_executor::TaskExecutor, beacon_chain: Arc>, ) -> Result<(), &'static str> { - let log = executor.log().clone(); let timer_future = async move { loop { let Some(duration_to_next_slot) = beacon_chain.slot_clock.duration_to_next_slot() else { - warn!(log, "Unable to determine duration to next slot"); + warn!("Unable to determine duration to next slot"); return; }; @@ -28,7 +27,7 @@ pub fn spawn_timer( }; executor.spawn(timer_future, "timer"); - info!(executor.log(), "Timer service started"); + info!("Timer service started"); Ok(()) } diff --git a/book/src/help_bn.md b/book/src/help_bn.md index cbcb1ec5a3..4c76647c0c 100644 --- a/book/src/help_bn.md +++ b/book/src/help_bn.md @@ -28,6 +28,8 @@ Options: network. Multiaddr is also supported. --builder The URL of a service compatible with the MEV-boost API. + --builder-disable-ssz + Disables sending requests using SSZ over the builder API. --builder-fallback-epochs-since-finalization If this node is proposing a block and the chain has not finalized within this number of epochs, it will NOT query any connected @@ -243,15 +245,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: debug] [possible values: info, debug, trace, warn, error, crit] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -515,8 +513,13 @@ Flags: all attestations are received for import. --light-client-server DEPRECATED - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_general.md b/book/src/help_general.md index 996b048d10..4d0d4104d4 100644 --- a/book/src/help_general.md +++ b/book/src/help_general.md @@ -56,15 +56,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: debug] [possible values: info, debug, trace, warn, error, crit] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -93,8 +89,13 @@ Flags: debugging specific memory allocation issues. -h, --help Prints help information - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_vc.md b/book/src/help_vc.md index 948a09f44d..7fb655910f 100644 --- a/book/src/help_vc.md +++ b/book/src/help_vc.md @@ -77,15 +77,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: debug] [possible values: info, debug, trace, warn, error, crit] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -175,6 +171,10 @@ Flags: If this flag is set, Lighthouse will query the Beacon Node for only block headers during proposals and will sign over headers. Useful for outsourcing execution payload construction during proposals. + --disable-attesting + Disable the performance of attestation duties (and sync committee + duties). This flag should only be used in emergencies to prioritise + block proposal duties. --disable-auto-discover If present, do not attempt to discover new validators in the validators-dir. Validators will need to be manually added to the @@ -236,8 +236,13 @@ Flags: database will have been initialized when you imported your validator keys. If you misplace your database and then run with this flag you risk being slashed. - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. @@ -247,6 +252,13 @@ Flags: contain sensitive information about your validator and so this flag should be used with caution. For Windows users, the log file permissions will be inherited from the parent folder. + --long-timeouts-multiplier + If present, the validator client will use a multiplier for the timeout + when making requests to the beacon node. This only takes effect when + the `--use-long-timeouts` flag is present. The timeouts will be the + slot duration multiplied by this value. This flag is generally not + recommended, longer timeouts can cause missed duties when fallbacks + are used. [default: 1] --metrics Enable the Prometheus metrics HTTP server. Disabled by default. --prefer-builder-proposals diff --git a/book/src/help_vm.md b/book/src/help_vm.md index 50c204f371..0d9d2a2e4b 100644 --- a/book/src/help_vm.md +++ b/book/src/help_vm.md @@ -53,15 +53,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: debug] [possible values: info, debug, trace, warn, error, crit] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -88,8 +84,13 @@ Flags: debugging specific memory allocation issues. -h, --help Prints help information - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_vm_create.md b/book/src/help_vm_create.md index 2743117eae..4f3774df10 100644 --- a/book/src/help_vm_create.md +++ b/book/src/help_vm_create.md @@ -60,15 +60,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: debug] [possible values: info, debug, trace, warn, error, crit] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -118,8 +114,13 @@ Flags: address. This is not recommended. -h, --help Prints help information - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_vm_import.md b/book/src/help_vm_import.md index 68aab768ae..28690d3a11 100644 --- a/book/src/help_vm_import.md +++ b/book/src/help_vm_import.md @@ -45,15 +45,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: debug] [possible values: info, debug, trace, warn, error, crit] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -104,8 +100,13 @@ Flags: directly cause slashable conditions, it might be an indicator that something is amiss. Users should also be careful to avoid submitting duplicate deposits for validators that already exist on the VC. - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/help_vm_move.md b/book/src/help_vm_move.md index 99eee32c78..af4a1a4d6d 100644 --- a/book/src/help_vm_move.md +++ b/book/src/help_vm_move.md @@ -49,15 +49,11 @@ Options: --log-format Specifies the log format used when emitting logs to the terminal. [possible values: JSON] - --logfile - File path where the log file will be stored. Once it grows to the - value specified in `--logfile-max-size` a new log file is generated - where future logs are stored. Once the number of log files exceeds the - value specified in `--logfile-max-number` the oldest log file will be - overwritten. --logfile-debug-level The verbosity level used when emitting logs to the log file. [default: debug] [possible values: info, debug, trace, warn, error, crit] + --logfile-dir + Directory path where the log file will be stored --logfile-format Specifies the log format used when emitting logs to the logfile. [possible values: DEFAULT, JSON] @@ -100,8 +96,13 @@ Flags: debugging specific memory allocation issues. -h, --help Prints help information - --log-color - Force outputting colors when emitting logs to the terminal. + --log-color [] + Enables/Disables colors for logs in terminal. Set it to false to + disable colors. [default: true] [possible values: true, false] + --log-extra-info + If present, show module,file,line in logs + --logfile-color + Enables colors in logfile. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. diff --git a/book/src/setup.md b/book/src/setup.md index d3da68f97c..7143c8f0fb 100644 --- a/book/src/setup.md +++ b/book/src/setup.md @@ -17,9 +17,6 @@ The additional requirements for developers are: some dependencies. See [`Installation Guide`](./installation.md) for more info. - [`java 17 runtime`](https://openjdk.java.net/projects/jdk/). 17 is the minimum, used by web3signer_tests. -- [`libpq-dev`](https://www.postgresql.org/docs/devel/libpq.html). Also known as - `libpq-devel` on some systems. -- [`docker`](https://www.docker.com/). Some tests need docker installed and **running**. ## Using `make` diff --git a/boot_node/Cargo.toml b/boot_node/Cargo.toml index 7c8d2b16fd..362b598c9f 100644 --- a/boot_node/Cargo.toml +++ b/boot_node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "boot_node" -version = "6.0.1" +version = "7.0.0-beta.0" authors = ["Sigma Prime "] edition = { workspace = true } @@ -16,9 +16,7 @@ lighthouse_network = { workspace = true } log = { workspace = true } logging = { workspace = true } serde = { workspace = true } -slog = { workspace = true } -slog-async = { workspace = true } -slog-scope = "4.3.0" -slog-term = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } diff --git a/boot_node/src/config.rs b/boot_node/src/config.rs index bb7678631f..c43a8b397b 100644 --- a/boot_node/src/config.rs +++ b/boot_node/src/config.rs @@ -53,9 +53,7 @@ impl BootNodeConfig { let mut network_config = NetworkConfig::default(); - let logger = slog_scope::logger(); - - set_network_config(&mut network_config, matches, &data_dir, &logger)?; + set_network_config(&mut network_config, matches, &data_dir)?; // Set the Enr Discovery ports to the listening ports if not present. if let Some(listening_addr_v4) = network_config.listen_addrs().v4() { @@ -85,7 +83,7 @@ impl BootNodeConfig { network_config.discv5_config.enr_update = false; } - let private_key = load_private_key(&network_config, &logger); + let private_key = load_private_key(&network_config); let local_key = CombinedKey::from_libp2p(private_key)?; let local_enr = if let Some(dir) = matches.get_one::("network-dir") { @@ -104,7 +102,7 @@ impl BootNodeConfig { if eth2_network_config.genesis_state_is_known() { let mut genesis_state = eth2_network_config - .genesis_state::(genesis_state_url.as_deref(), genesis_state_url_timeout, &logger).await? + .genesis_state::(genesis_state_url.as_deref(), genesis_state_url_timeout).await? .ok_or_else(|| { "The genesis state for this network is not known, this is an unsupported mode" .to_string() @@ -113,7 +111,7 @@ impl BootNodeConfig { let genesis_state_root = genesis_state .canonical_root() .map_err(|e| format!("Error hashing genesis state: {e:?}"))?; - slog::info!(logger, "Genesis state found"; "root" => ?genesis_state_root); + tracing::info!(root = ?genesis_state_root, "Genesis state found"); let enr_fork = spec.enr_fork_id::( types::Slot::from(0u64), genesis_state.genesis_validators_root(), @@ -121,10 +119,7 @@ impl BootNodeConfig { Some(enr_fork.as_ssz_bytes()) } else { - slog::warn!( - logger, - "No genesis state provided. No Eth2 field added to the ENR" - ); + tracing::warn!("No genesis state provided. No Eth2 field added to the ENR"); None } }; @@ -160,7 +155,7 @@ impl BootNodeConfig { .map_err(|e| format!("Failed to build ENR: {:?}", e))? }; - use_or_load_enr(&local_key, &mut local_enr, &network_config, &logger)?; + use_or_load_enr(&local_key, &mut local_enr, &network_config)?; local_enr }; diff --git a/boot_node/src/lib.rs b/boot_node/src/lib.rs index 669b126bd3..70a45b2f92 100644 --- a/boot_node/src/lib.rs +++ b/boot_node/src/lib.rs @@ -1,6 +1,5 @@ //! Creates a simple DISCV5 server which can be used to bootstrap an Eth2 network. use clap::ArgMatches; -use slog::{o, Drain, Level, Logger}; use eth2_network_config::Eth2NetworkConfig; mod cli; @@ -8,10 +7,9 @@ pub mod config; mod server; pub use cli::cli_app; use config::BootNodeConfig; +use tracing_subscriber::EnvFilter; use types::{EthSpec, EthSpecId}; -const LOG_CHANNEL_SIZE: usize = 2048; - /// Run the bootnode given the CLI configuration. pub fn run( lh_matches: &ArgMatches, @@ -20,49 +18,27 @@ pub fn run( eth2_network_config: &Eth2NetworkConfig, debug_level: String, ) { - let debug_level = match debug_level.as_str() { - "trace" => log::Level::Trace, - "debug" => log::Level::Debug, - "info" => log::Level::Info, - "warn" => log::Level::Warn, - "error" => log::Level::Error, - "crit" => log::Level::Error, - _ => unreachable!(), - }; + let filter_layer = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new(debug_level.to_string().to_lowercase())) + .unwrap(); - // Setting up the initial logger format and building it. - let drain = { - let decorator = slog_term::TermDecorator::new().build(); - let decorator = logging::AlignedTermDecorator::new(decorator, logging::MAX_MESSAGE_WIDTH); - let drain = slog_term::FullFormat::new(decorator).build().fuse(); - slog_async::Async::new(drain) - .chan_size(LOG_CHANNEL_SIZE) - .build() - }; - - let drain = match debug_level { - log::Level::Info => drain.filter_level(Level::Info), - log::Level::Debug => drain.filter_level(Level::Debug), - log::Level::Trace => drain.filter_level(Level::Trace), - log::Level::Warn => drain.filter_level(Level::Warning), - log::Level::Error => drain.filter_level(Level::Error), - }; - - let log = Logger::root(drain.fuse(), o!()); + tracing_subscriber::fmt() + .with_env_filter(filter_layer) + .init(); // Run the main function emitting any errors if let Err(e) = match eth_spec_id { EthSpecId::Minimal => { - main::(lh_matches, bn_matches, eth2_network_config, log) + main::(lh_matches, bn_matches, eth2_network_config) } EthSpecId::Mainnet => { - main::(lh_matches, bn_matches, eth2_network_config, log) + main::(lh_matches, bn_matches, eth2_network_config) } EthSpecId::Gnosis => { - main::(lh_matches, bn_matches, eth2_network_config, log) + main::(lh_matches, bn_matches, eth2_network_config) } } { - slog::crit!(slog_scope::logger(), "{}", e); + logging::crit!(?e); } } @@ -70,7 +46,6 @@ fn main( lh_matches: &ArgMatches, bn_matches: &ArgMatches, eth2_network_config: &Eth2NetworkConfig, - log: slog::Logger, ) -> Result<(), String> { // Builds a custom executor for the bootnode let runtime = tokio::runtime::Builder::new_multi_thread() @@ -83,7 +58,6 @@ fn main( lh_matches, bn_matches, eth2_network_config, - log, ))?; Ok(()) diff --git a/boot_node/src/server.rs b/boot_node/src/server.rs index 96032dddcc..d96ac0c726 100644 --- a/boot_node/src/server.rs +++ b/boot_node/src/server.rs @@ -8,14 +8,13 @@ use lighthouse_network::{ discv5::{self, enr::NodeId, Discv5}, EnrExt, Eth2Enr, }; -use slog::info; +use tracing::{info, warn}; use types::EthSpec; pub async fn run( lh_matches: &ArgMatches, bn_matches: &ArgMatches, eth2_network_config: &Eth2NetworkConfig, - log: slog::Logger, ) -> Result<(), String> { // parse the CLI args into a useable config let config: BootNodeConfig = BootNodeConfig::new(bn_matches, eth2_network_config).await?; @@ -52,19 +51,19 @@ pub async fn run( let pretty_v4_socket = enr_v4_socket.as_ref().map(|addr| addr.to_string()); let pretty_v6_socket = enr_v6_socket.as_ref().map(|addr| addr.to_string()); info!( - log, "Configuration parameters"; - "listening_address" => ?discv5_config.listen_config, - "advertised_v4_address" => ?pretty_v4_socket, - "advertised_v6_address" => ?pretty_v6_socket, - "eth2" => eth2_field + listening_address = ?discv5_config.listen_config, + advertised_v4_address = ?pretty_v4_socket, + advertised_v6_address = ?pretty_v6_socket, + eth2 = eth2_field, + "Configuration parameters" ); - info!(log, "Identity established"; "peer_id" => %local_enr.peer_id(), "node_id" => %local_enr.node_id()); + info!(peer_id = %local_enr.peer_id(), node_id = %local_enr.node_id(), "Identity established"); // build the contactable multiaddr list, adding the p2p protocol - info!(log, "Contact information"; "enr" => local_enr.to_base64()); - info!(log, "Enr details"; "enr" => ?local_enr); - info!(log, "Contact information"; "multiaddrs" => ?local_enr.multiaddr_p2p()); + info!(enr = local_enr.to_base64(), "Contact information"); + info!(enr = ?local_enr, "Enr details"); + info!(multiaddrs = ?local_enr.multiaddr_p2p(), "Contact information"); // construct the discv5 server let mut discv5: Discv5 = Discv5::new(local_enr.clone(), local_key, discv5_config).unwrap(); @@ -72,16 +71,15 @@ pub async fn run( // If there are any bootnodes add them to the routing table for enr in boot_nodes { info!( - log, - "Adding bootnode"; - "ipv4_address" => ?enr.udp4_socket(), - "ipv6_address" => ?enr.udp6_socket(), - "peer_id" => ?enr.peer_id(), - "node_id" => ?enr.node_id() + ipv4_address = ?enr.udp4_socket(), + ipv6_address = ?enr.udp6_socket(), + peer_id = ?enr.peer_id(), + node_id = ?enr.node_id(), + "Adding bootnode" ); if enr != local_enr { if let Err(e) = discv5.add_enr(enr) { - slog::warn!(log, "Failed adding ENR"; "error" => ?e); + warn!(error = ?e, "Failed adding ENR"); } } } @@ -93,7 +91,7 @@ pub async fn run( // if there are peers in the local routing table, establish a session by running a query if !discv5.table_entries_id().is_empty() { - info!(log, "Executing bootstrap query..."); + info!("Executing bootstrap query..."); let _ = discv5.find_node(NodeId::random()).await; } @@ -131,14 +129,14 @@ pub async fn run( // display server metrics let metrics = discv5.metrics(); info!( - log, "Server metrics"; - "connected_peers" => discv5.connected_peers(), - "active_sessions" => metrics.active_sessions, - "requests/s" => format_args!("{:.2}", metrics.unsolicited_requests_per_second), - "ipv4_nodes" => ipv4_only_reachable, - "ipv6_only_nodes" => ipv6_only_reachable, - "dual_stack_nodes" => ipv4_ipv6_reachable, - "unreachable_nodes" => unreachable_nodes, + connected_peers = discv5.connected_peers(), + active_sessions = metrics.active_sessions, + "requests/s" = format_args!("{:.2}", metrics.unsolicited_requests_per_second), + ipv4_nodes = ipv4_only_reachable, + ipv6_only_nodes = ipv6_only_reachable, + dual_stack_nodes = ipv4_ipv6_reachable, + unreachable_nodes, + "Server metrics", ); } @@ -149,7 +147,7 @@ pub async fn run( // Ignore these events here } discv5::Event::SocketUpdated(socket_addr) => { - info!(log, "Advertised socket address updated"; "socket_addr" => %socket_addr); + info!(%socket_addr, "Advertised socket address updated"); } _ => {} // Ignore } diff --git a/common/account_utils/Cargo.toml b/common/account_utils/Cargo.toml index 3ab6034688..00c74a1303 100644 --- a/common/account_utils/Cargo.toml +++ b/common/account_utils/Cargo.toml @@ -14,7 +14,7 @@ regex = { workspace = true } rpassword = "5.0.0" serde = { workspace = true } serde_yaml = { workspace = true } -slog = { workspace = true } +tracing = { workspace = true } types = { workspace = true } validator_dir = { workspace = true } zeroize = { workspace = true } diff --git a/common/account_utils/src/validator_definitions.rs b/common/account_utils/src/validator_definitions.rs index 7337d6dfb4..4c253283fe 100644 --- a/common/account_utils/src/validator_definitions.rs +++ b/common/account_utils/src/validator_definitions.rs @@ -7,11 +7,11 @@ use crate::{default_keystore_password_path, read_password_string, write_file_via use eth2_keystore::Keystore; use regex::Regex; use serde::{Deserialize, Serialize}; -use slog::{error, Logger}; use std::collections::HashSet; use std::fs::{self, create_dir_all, File}; use std::io; use std::path::{Path, PathBuf}; +use tracing::error; use types::{graffiti::GraffitiString, Address, PublicKey}; use validator_dir::VOTING_KEYSTORE_FILE; use zeroize::Zeroizing; @@ -115,7 +115,6 @@ impl SigningDefinition { voting_keystore_password_path: Some(path), .. } => read_password_string(path) - .map(Into::into) .map(Option::Some) .map_err(Error::UnableToReadKeystorePassword), SigningDefinition::LocalKeystore { .. } => Err(Error::KeystoreWithoutPassword), @@ -267,7 +266,6 @@ impl ValidatorDefinitions { &mut self, validators_dir: P, secrets_dir: P, - log: &Logger, ) -> Result { let mut keystore_paths = vec![]; recursively_find_voting_keystores(validators_dir, &mut keystore_paths) @@ -312,10 +310,9 @@ impl ValidatorDefinitions { Ok(keystore) => keystore, Err(e) => { error!( - log, - "Unable to read validator keystore"; - "error" => e, - "keystore" => format!("{:?}", voting_keystore_path) + error = ?e, + keystore = ?voting_keystore_path, + "Unable to read validator keystore" ); return None; } @@ -337,9 +334,8 @@ impl ValidatorDefinitions { } None => { error!( - log, - "Invalid keystore public key"; - "keystore" => format!("{:?}", voting_keystore_path) + keystore = ?voting_keystore_path, + "Invalid keystore public key" ); return None; } diff --git a/common/eth2/Cargo.toml b/common/eth2/Cargo.toml index 7644d1aef4..a1bc9d025b 100644 --- a/common/eth2/Cargo.toml +++ b/common/eth2/Cargo.toml @@ -8,16 +8,14 @@ edition = { workspace = true } [dependencies] derivative = { workspace = true } either = { workspace = true } -enr = { version = "0.13.0", features = ["ed25519"] } eth2_keystore = { workspace = true } ethereum_serde_utils = { workspace = true } ethereum_ssz = { workspace = true } ethereum_ssz_derive = { workspace = true } futures = { workspace = true } futures-util = "0.3.8" -libp2p-identity = { version = "0.2", features = ["peerid"] } +lighthouse_network = { workspace = true } mediatype = "0.19.13" -multiaddr = "0.18.2" pretty_reqwest_error = { workspace = true } proto_array = { workspace = true } reqwest = { workspace = true } @@ -27,6 +25,7 @@ serde = { workspace = true } serde_json = { workspace = true } slashing_protection = { workspace = true } ssz_types = { workspace = true } +store = { workspace = true } types = { workspace = true } zeroize = { workspace = true } diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 7eb96d9770..73e9d57abc 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -16,12 +16,11 @@ pub mod types; use self::mixin::{RequestAccept, ResponseOptional}; use self::types::{Error as ResponseError, *}; -use ::types::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedResponse; use derivative::Derivative; use either::Either; use futures::Stream; use futures_util::StreamExt; -use libp2p_identity::PeerId; +use lighthouse_network::PeerId; use pretty_reqwest_error::PrettyReqwestError; pub use reqwest; use reqwest::{ @@ -37,6 +36,7 @@ use std::fmt; use std::future::Future; use std::path::PathBuf; use std::time::Duration; +use store::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedResponse; pub const V1: EndpointVersion = EndpointVersion(1); pub const V2: EndpointVersion = EndpointVersion(2); diff --git a/common/eth2/src/lighthouse.rs b/common/eth2/src/lighthouse.rs index 89079acf58..badc4857c4 100644 --- a/common/eth2/src/lighthouse.rs +++ b/common/eth2/src/lighthouse.rs @@ -6,17 +6,18 @@ mod block_packing_efficiency; mod block_rewards; mod standard_block_rewards; mod sync_committee_rewards; -pub mod sync_state; -use crate::lighthouse::sync_state::SyncState; use crate::{ - types::{DepositTreeSnapshot, Epoch, FinalizedExecutionBlock, GenericResponse, ValidatorId}, + types::{ + DepositTreeSnapshot, Epoch, EthSpec, FinalizedExecutionBlock, GenericResponse, ValidatorId, + }, BeaconNodeHttpClient, DepositData, Error, Eth1Data, Hash256, Slot, }; use proto_array::core::ProtoArray; use serde::{Deserialize, Serialize}; use ssz::four_byte_option_impl; use ssz_derive::{Decode, Encode}; +use store::{AnchorInfo, BlobInfo, Split, StoreConfig}; pub use attestation_performance::{ AttestationPerformance, AttestationPerformanceQuery, AttestationPerformanceStatistics, @@ -26,6 +27,7 @@ pub use block_packing_efficiency::{ BlockPackingEfficiency, BlockPackingEfficiencyQuery, ProposerInfo, UniqueAttestation, }; pub use block_rewards::{AttestationRewards, BlockReward, BlockRewardMeta, BlockRewardsQuery}; +pub use lighthouse_network::{types::SyncState, PeerInfo}; pub use standard_block_rewards::StandardBlockReward; pub use sync_committee_rewards::SyncCommitteeReward; @@ -35,12 +37,14 @@ four_byte_option_impl!(four_byte_option_u64, u64); four_byte_option_impl!(four_byte_option_hash256, Hash256); /// Information returned by `peers` and `connected_peers`. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Peer { +// TODO: this should be deserializable.. +#[derive(Debug, Clone, Serialize)] +#[serde(bound = "E: EthSpec")] +pub struct Peer { /// The Peer's ID pub peer_id: String, /// The PeerInfo associated with the peer. - pub peer_info: serde_json::Value, + pub peer_info: PeerInfo, } /// The results of validators voting during an epoch. @@ -230,6 +234,15 @@ impl From for FinalizedExecutionBlock { } } +#[derive(Debug, Serialize, Deserialize)] +pub struct DatabaseInfo { + pub schema_version: u64, + pub config: StoreConfig, + pub split: Split, + pub anchor: AnchorInfo, + pub blob_info: BlobInfo, +} + impl BeaconNodeHttpClient { /// `GET lighthouse/health` pub async fn get_lighthouse_health(&self) -> Result, Error> { @@ -368,7 +381,7 @@ impl BeaconNodeHttpClient { } /// `GET lighthouse/database/info` - pub async fn get_lighthouse_database_info(&self) -> Result { + pub async fn get_lighthouse_database_info(&self) -> Result { let mut path = self.server.full.clone(); path.path_segments_mut() diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 36086454f2..59374f629d 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -5,9 +5,8 @@ use crate::{ Error as ServerError, CONSENSUS_BLOCK_VALUE_HEADER, CONSENSUS_VERSION_HEADER, EXECUTION_PAYLOAD_BLINDED_HEADER, EXECUTION_PAYLOAD_VALUE_HEADER, }; -use enr::{CombinedKey, Enr}; +use lighthouse_network::{ConnectionDirection, Enr, Multiaddr, PeerConnectionStatus}; use mediatype::{names, MediaType, MediaTypeList}; -use multiaddr::Multiaddr; use reqwest::header::HeaderMap; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; @@ -579,7 +578,7 @@ pub struct ChainHeadData { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct IdentityData { pub peer_id: String, - pub enr: Enr, + pub enr: Enr, pub p2p_addresses: Vec, pub discovery_addresses: Vec, pub metadata: MetaData, @@ -862,6 +861,19 @@ pub enum PeerState { Disconnecting, } +impl PeerState { + pub fn from_peer_connection_status(status: &PeerConnectionStatus) -> Self { + match status { + PeerConnectionStatus::Connected { .. } => PeerState::Connected, + PeerConnectionStatus::Dialing { .. } => PeerState::Connecting, + PeerConnectionStatus::Disconnecting { .. } => PeerState::Disconnecting, + PeerConnectionStatus::Disconnected { .. } + | PeerConnectionStatus::Banned { .. } + | PeerConnectionStatus::Unknown => PeerState::Disconnected, + } + } +} + impl FromStr for PeerState { type Err = String; @@ -894,6 +906,15 @@ pub enum PeerDirection { Outbound, } +impl PeerDirection { + pub fn from_connection_direction(direction: &ConnectionDirection) -> Self { + match direction { + ConnectionDirection::Incoming => PeerDirection::Inbound, + ConnectionDirection::Outgoing => PeerDirection::Outbound, + } + } +} + impl FromStr for PeerDirection { type Err = String; diff --git a/common/eth2_network_config/Cargo.toml b/common/eth2_network_config/Cargo.toml index a255e04229..da6c4dfd95 100644 --- a/common/eth2_network_config/Cargo.toml +++ b/common/eth2_network_config/Cargo.toml @@ -20,12 +20,11 @@ bytes = { workspace = true } discv5 = { workspace = true } eth2_config = { workspace = true } kzg = { workspace = true } -logging = { workspace = true } pretty_reqwest_error = { workspace = true } reqwest = { workspace = true } sensitive_url = { workspace = true } serde_yaml = { workspace = true } sha2 = { workspace = true } -slog = { workspace = true } +tracing = { workspace = true } types = { workspace = true } url = { workspace = true } diff --git a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml index 35ba3af28b..1455ec5f63 100644 --- a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml @@ -46,7 +46,7 @@ DENEB_FORK_VERSION: 0x0400006f DENEB_FORK_EPOCH: 516608 # Wed Jan 31 2024 18:15:40 GMT+0000 # Electra ELECTRA_FORK_VERSION: 0x0500006f -ELECTRA_FORK_EPOCH: 18446744073709551615 +ELECTRA_FORK_EPOCH: 948224 # Thu Mar 6 2025 09:43:40 GMT+0000 # Fulu FULU_FORK_VERSION: 0x0600006f FULU_FORK_EPOCH: 18446744073709551615 @@ -138,6 +138,18 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # `uint64(6)` MAX_BLOBS_PER_BLOCK: 6 +# Electra +# 2**7 * 10**9 (= 128,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**6 * 10**9 (= 64,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 64000000000 +# `2` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 2 +# `uint64(2)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 2 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 256 + # DAS NUMBER_OF_COLUMNS: 128 NUMBER_OF_CUSTODY_GROUPS: 128 diff --git a/common/eth2_network_config/src/lib.rs b/common/eth2_network_config/src/lib.rs index 5d5a50574b..0bb12c4187 100644 --- a/common/eth2_network_config/src/lib.rs +++ b/common/eth2_network_config/src/lib.rs @@ -19,12 +19,12 @@ use pretty_reqwest_error::PrettyReqwestError; use reqwest::{Client, Error}; use sensitive_url::SensitiveUrl; use sha2::{Digest, Sha256}; -use slog::{info, warn, Logger}; use std::fs::{create_dir_all, File}; use std::io::{Read, Write}; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; +use tracing::{info, warn}; use types::{BeaconState, ChainSpec, Config, EthSpec, EthSpecId, Hash256}; use url::Url; @@ -198,7 +198,6 @@ impl Eth2NetworkConfig { &self, genesis_state_url: Option<&str>, timeout: Duration, - log: &Logger, ) -> Result>, String> { let spec = self.chain_spec::()?; match &self.genesis_state_source { @@ -217,9 +216,9 @@ impl Eth2NetworkConfig { format!("Unable to parse genesis state bytes checksum: {:?}", e) })?; let bytes = if let Some(specified_url) = genesis_state_url { - download_genesis_state(&[specified_url], timeout, checksum, log).await + download_genesis_state(&[specified_url], timeout, checksum).await } else { - download_genesis_state(built_in_urls, timeout, checksum, log).await + download_genesis_state(built_in_urls, timeout, checksum).await }?; let state = BeaconState::from_ssz_bytes(bytes.as_ref(), &spec).map_err(|e| { format!("Downloaded genesis state SSZ bytes are invalid: {:?}", e) @@ -387,7 +386,6 @@ async fn download_genesis_state( urls: &[&str], timeout: Duration, checksum: Hash256, - log: &Logger, ) -> Result, String> { if urls.is_empty() { return Err( @@ -407,11 +405,10 @@ async fn download_genesis_state( .unwrap_or_else(|_| "".to_string()); info!( - log, - "Downloading genesis state"; - "server" => &redacted_url, - "timeout" => ?timeout, - "info" => "this may take some time on testnets with large validator counts" + server = &redacted_url, + timeout = ?timeout, + info = "this may take some time on testnets with large validator counts", + "Downloading genesis state" ); let client = Client::new(); @@ -424,10 +421,9 @@ async fn download_genesis_state( return Ok(bytes.into()); } else { warn!( - log, - "Genesis state download failed"; - "server" => &redacted_url, - "timeout" => ?timeout, + server = &redacted_url, + timeout = ?timeout, + "Genesis state download failed" ); errors.push(format!( "Response from {} did not match local checksum", @@ -505,7 +501,7 @@ mod tests { async fn mainnet_genesis_state() { let config = Eth2NetworkConfig::from_hardcoded_net(&MAINNET).unwrap(); config - .genesis_state::(None, Duration::from_secs(1), &logging::test_logger()) + .genesis_state::(None, Duration::from_secs(1)) .await .expect("beacon state can decode"); } diff --git a/common/lighthouse_version/Cargo.toml b/common/lighthouse_version/Cargo.toml index 164e3e47a7..cb4a43e407 100644 --- a/common/lighthouse_version/Cargo.toml +++ b/common/lighthouse_version/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lighthouse_version" version = "0.1.0" -authors = ["Paul Hauner "] +authors = ["Sigma Prime "] edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/lighthouse_version/src/lib.rs b/common/lighthouse_version/src/lib.rs index a35b8c42c1..cfffdbbb09 100644 --- a/common/lighthouse_version/src/lib.rs +++ b/common/lighthouse_version/src/lib.rs @@ -17,8 +17,8 @@ pub const VERSION: &str = git_version!( // NOTE: using --match instead of --exclude for compatibility with old Git "--match=thiswillnevermatchlol" ], - prefix = "Lighthouse/v6.0.1-", - fallback = "Lighthouse/v6.0.1" + prefix = "Lighthouse/v7.0.0-beta.0-", + fallback = "Lighthouse/v7.0.0-beta.0" ); /// Returns the first eight characters of the latest commit hash for this build. @@ -54,7 +54,7 @@ pub fn version_with_platform() -> String { /// /// `1.5.1` pub fn version() -> &'static str { - "6.0.1" + "7.0.0-beta.0" } /// Returns the name of the current client running. @@ -71,9 +71,10 @@ mod test { #[test] fn version_formatting() { - let re = - Regex::new(r"^Lighthouse/v[0-9]+\.[0-9]+\.[0-9]+(-rc.[0-9])?(-[[:xdigit:]]{7})?\+?$") - .unwrap(); + let re = Regex::new( + r"^Lighthouse/v[0-9]+\.[0-9]+\.[0-9]+(-(rc|beta).[0-9])?(-[[:xdigit:]]{7})?\+?$", + ) + .unwrap(); assert!( re.is_match(VERSION), "version doesn't match regex: {}", diff --git a/common/logging/Cargo.toml b/common/logging/Cargo.toml index b2829a48d8..a69bc6ab23 100644 --- a/common/logging/Cargo.toml +++ b/common/logging/Cargo.toml @@ -9,14 +9,12 @@ test_logger = [] # Print log output to stderr when running tests instead of drop [dependencies] chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } +logroller = { workspace = true } metrics = { workspace = true } +once_cell = "1.17.1" parking_lot = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -slog = { workspace = true } -slog-term = { workspace = true } -sloggers = { workspace = true } -take_mut = "0.2.2" tokio = { workspace = true, features = [ "time" ] } tracing = "0.1" tracing-appender = { workspace = true } diff --git a/common/logging/src/async_record.rs b/common/logging/src/async_record.rs deleted file mode 100644 index 7a97fa1a75..0000000000 --- a/common/logging/src/async_record.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! An object that can be used to pass through a channel and be cloned. It can therefore be used -//! via the broadcast channel. - -use parking_lot::Mutex; -use serde::ser::SerializeMap; -use serde::serde_if_integer128; -use serde::Serialize; -use slog::{BorrowedKV, Key, Level, OwnedKVList, Record, RecordStatic, Serializer, SingleKV, KV}; -use std::cell::RefCell; -use std::fmt; -use std::fmt::Write; -use std::sync::Arc; -use take_mut::take; - -thread_local! { - static TL_BUF: RefCell = RefCell::new(String::with_capacity(128)) -} - -/// Serialized record. -#[derive(Clone)] -pub struct AsyncRecord { - msg: String, - level: Level, - location: Box, - tag: String, - logger_values: OwnedKVList, - kv: Arc>, -} - -impl AsyncRecord { - /// Serializes a `Record` and an `OwnedKVList`. - pub fn from(record: &Record, logger_values: &OwnedKVList) -> Self { - let mut ser = ToSendSerializer::new(); - record - .kv() - .serialize(record, &mut ser) - .expect("`ToSendSerializer` can't fail"); - - AsyncRecord { - msg: fmt::format(*record.msg()), - level: record.level(), - location: Box::new(*record.location()), - tag: String::from(record.tag()), - logger_values: logger_values.clone(), - kv: Arc::new(Mutex::new(ser.finish())), - } - } - - pub fn to_json_string(&self) -> Result { - serde_json::to_string(&self).map_err(|e| format!("{:?}", e)) - } -} - -pub struct ToSendSerializer { - kv: Box, -} - -impl ToSendSerializer { - fn new() -> Self { - ToSendSerializer { kv: Box::new(()) } - } - - fn finish(self) -> Box { - self.kv - } -} - -impl Serializer for ToSendSerializer { - fn emit_bool(&mut self, key: Key, val: bool) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_unit(&mut self, key: Key) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, ())))); - Ok(()) - } - fn emit_none(&mut self, key: Key) -> slog::Result { - let val: Option<()> = None; - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_char(&mut self, key: Key, val: char) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u8(&mut self, key: Key, val: u8) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i8(&mut self, key: Key, val: i8) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u16(&mut self, key: Key, val: u16) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i16(&mut self, key: Key, val: i16) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u32(&mut self, key: Key, val: u32) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i32(&mut self, key: Key, val: i32) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_f32(&mut self, key: Key, val: f32) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u64(&mut self, key: Key, val: u64) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i64(&mut self, key: Key, val: i64) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_f64(&mut self, key: Key, val: f64) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_u128(&mut self, key: Key, val: u128) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_i128(&mut self, key: Key, val: i128) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_usize(&mut self, key: Key, val: usize) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_isize(&mut self, key: Key, val: isize) -> slog::Result { - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_str(&mut self, key: Key, val: &str) -> slog::Result { - let val = val.to_owned(); - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } - fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> slog::Result { - let val = fmt::format(*val); - take(&mut self.kv, |kv| Box::new((kv, SingleKV(key, val)))); - Ok(()) - } -} - -impl Serialize for AsyncRecord { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // Get the current time - let dt = chrono::Local::now().format("%b %e %T").to_string(); - - let rs = RecordStatic { - location: &self.location, - level: self.level, - tag: &self.tag, - }; - let mut map_serializer = SerdeSerializer::new(serializer)?; - - // Serialize the time and log level first - map_serializer.serialize_entry("time", &dt)?; - map_serializer.serialize_entry("level", self.level.as_short_str())?; - - let kv = self.kv.lock(); - - // Convoluted pattern to avoid binding `format_args!` to a temporary. - // See: https://stackoverflow.com/questions/56304313/cannot-use-format-args-due-to-temporary-value-is-freed-at-the-end-of-this-state - let mut f = |msg: std::fmt::Arguments| { - map_serializer.serialize_entry("msg", msg.to_string())?; - - let record = Record::new(&rs, &msg, BorrowedKV(&(*kv))); - self.logger_values - .serialize(&record, &mut map_serializer) - .map_err(serde::ser::Error::custom)?; - record - .kv() - .serialize(&record, &mut map_serializer) - .map_err(serde::ser::Error::custom) - }; - f(format_args!("{}", self.msg))?; - map_serializer.end() - } -} - -struct SerdeSerializer { - /// Current state of map serializing: `serde::Serializer::MapState` - ser_map: S::SerializeMap, -} - -impl SerdeSerializer { - fn new(ser: S) -> Result { - let ser_map = ser.serialize_map(None)?; - Ok(SerdeSerializer { ser_map }) - } - - fn serialize_entry(&mut self, key: K, value: V) -> Result<(), S::Error> - where - K: serde::Serialize, - V: serde::Serialize, - { - self.ser_map.serialize_entry(&key, &value) - } - - /// Finish serialization, and return the serializer - fn end(self) -> Result { - self.ser_map.end() - } -} - -// NOTE: This is borrowed from slog_json -macro_rules! impl_m( - ($s:expr, $key:expr, $val:expr) => ({ - let k_s: &str = $key.as_ref(); - $s.ser_map.serialize_entry(k_s, $val) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("serde serialization error: {}", e)))?; - Ok(()) - }); -); - -impl slog::Serializer for SerdeSerializer -where - S: serde::Serializer, -{ - fn emit_bool(&mut self, key: Key, val: bool) -> slog::Result { - impl_m!(self, key, &val) - } - - fn emit_unit(&mut self, key: Key) -> slog::Result { - impl_m!(self, key, &()) - } - - fn emit_char(&mut self, key: Key, val: char) -> slog::Result { - impl_m!(self, key, &val) - } - - fn emit_none(&mut self, key: Key) -> slog::Result { - let val: Option<()> = None; - impl_m!(self, key, &val) - } - fn emit_u8(&mut self, key: Key, val: u8) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i8(&mut self, key: Key, val: i8) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_u16(&mut self, key: Key, val: u16) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i16(&mut self, key: Key, val: i16) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_usize(&mut self, key: Key, val: usize) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_isize(&mut self, key: Key, val: isize) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_u32(&mut self, key: Key, val: u32) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i32(&mut self, key: Key, val: i32) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_f32(&mut self, key: Key, val: f32) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_u64(&mut self, key: Key, val: u64) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i64(&mut self, key: Key, val: i64) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_f64(&mut self, key: Key, val: f64) -> slog::Result { - impl_m!(self, key, &val) - } - serde_if_integer128! { - fn emit_u128(&mut self, key: Key, val: u128) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_i128(&mut self, key: Key, val: i128) -> slog::Result { - impl_m!(self, key, &val) - } - } - fn emit_str(&mut self, key: Key, val: &str) -> slog::Result { - impl_m!(self, key, &val) - } - fn emit_arguments(&mut self, key: Key, val: &fmt::Arguments) -> slog::Result { - TL_BUF.with(|buf| { - let mut buf = buf.borrow_mut(); - - buf.write_fmt(*val).unwrap(); - - let res = { || impl_m!(self, key, &*buf) }(); - buf.clear(); - res - }) - } -} diff --git a/common/logging/src/lib.rs b/common/logging/src/lib.rs index 0d0bda05a2..39615cd656 100644 --- a/common/logging/src/lib.rs +++ b/common/logging/src/lib.rs @@ -1,21 +1,20 @@ -use metrics::{inc_counter, try_create_int_counter, IntCounter, Result as MetricsResult}; -use slog::Logger; -use slog_term::Decorator; -use std::io::{Result, Write}; +use chrono::Local; +use logroller::{Compression, LogRollerBuilder, Rotation, RotationSize}; +use metrics::{try_create_int_counter, IntCounter, Result as MetricsResult}; +use std::io::Write; use std::path::PathBuf; use std::sync::LazyLock; use std::time::{Duration, Instant}; -use tracing_appender::non_blocking::NonBlocking; -use tracing_appender::rolling::{RollingFileAppender, Rotation}; -use tracing_logging_layer::LoggingLayer; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +use tracing::Subscriber; +use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; +use tracing_subscriber::layer::Context; +use tracing_subscriber::{EnvFilter, Layer}; pub const MAX_MESSAGE_WIDTH: usize = 40; -pub mod async_record; pub mod macros; mod sse_logging_components; -mod tracing_logging_layer; +pub mod tracing_logging_layer; mod tracing_metrics_layer; pub use sse_logging_components::SSELoggingComponents; @@ -33,169 +32,6 @@ pub static ERRORS_TOTAL: LazyLock> = pub static CRITS_TOTAL: LazyLock> = LazyLock::new(|| try_create_int_counter("crit_total", "Count of crits logged")); -pub struct AlignedTermDecorator { - wrapped: D, - message_width: usize, -} - -impl AlignedTermDecorator { - pub fn new(decorator: D, message_width: usize) -> Self { - AlignedTermDecorator { - wrapped: decorator, - message_width, - } - } -} - -impl Decorator for AlignedTermDecorator { - fn with_record( - &self, - record: &slog::Record, - _logger_values: &slog::OwnedKVList, - f: F, - ) -> Result<()> - where - F: FnOnce(&mut dyn slog_term::RecordDecorator) -> std::io::Result<()>, - { - match record.level() { - slog::Level::Info => inc_counter(&INFOS_TOTAL), - slog::Level::Warning => inc_counter(&WARNS_TOTAL), - slog::Level::Error => inc_counter(&ERRORS_TOTAL), - slog::Level::Critical => inc_counter(&CRITS_TOTAL), - _ => (), - } - - self.wrapped.with_record(record, _logger_values, |deco| { - f(&mut AlignedRecordDecorator::new(deco, self.message_width)) - }) - } -} - -struct AlignedRecordDecorator<'a> { - wrapped: &'a mut dyn slog_term::RecordDecorator, - message_count: usize, - message_active: bool, - ignore_comma: bool, - message_width: usize, -} - -impl<'a> AlignedRecordDecorator<'a> { - fn new( - decorator: &'a mut dyn slog_term::RecordDecorator, - message_width: usize, - ) -> AlignedRecordDecorator<'a> { - AlignedRecordDecorator { - wrapped: decorator, - message_count: 0, - ignore_comma: false, - message_active: false, - message_width, - } - } - - fn filtered_write(&mut self, buf: &[u8]) -> Result { - if self.ignore_comma { - //don't write comma - self.ignore_comma = false; - Ok(buf.len()) - } else if self.message_active { - self.wrapped.write(buf).inspect(|n| self.message_count += n) - } else { - self.wrapped.write(buf) - } - } -} - -impl Write for AlignedRecordDecorator<'_> { - fn write(&mut self, buf: &[u8]) -> Result { - if buf.iter().any(u8::is_ascii_control) { - let filtered = buf - .iter() - .cloned() - .map(|c| if !is_ascii_control(&c) { c } else { b'_' }) - .collect::>(); - self.filtered_write(&filtered) - } else { - self.filtered_write(buf) - } - } - - fn flush(&mut self) -> Result<()> { - self.wrapped.flush() - } -} - -impl slog_term::RecordDecorator for AlignedRecordDecorator<'_> { - fn reset(&mut self) -> Result<()> { - self.message_active = false; - self.message_count = 0; - self.ignore_comma = false; - self.wrapped.reset() - } - - fn start_whitespace(&mut self) -> Result<()> { - self.wrapped.start_whitespace() - } - - fn start_msg(&mut self) -> Result<()> { - self.message_active = true; - self.ignore_comma = false; - self.wrapped.start_msg() - } - - fn start_timestamp(&mut self) -> Result<()> { - self.wrapped.start_timestamp() - } - - fn start_level(&mut self) -> Result<()> { - self.wrapped.start_level() - } - - fn start_comma(&mut self) -> Result<()> { - if self.message_active && self.message_count + 1 < self.message_width { - self.ignore_comma = true; - } - self.wrapped.start_comma() - } - - fn start_key(&mut self) -> Result<()> { - if self.message_active && self.message_count + 1 < self.message_width { - write!( - self, - "{}", - " ".repeat(self.message_width - self.message_count) - )?; - self.message_active = false; - self.message_count = 0; - self.ignore_comma = false; - } - self.wrapped.start_key() - } - - fn start_value(&mut self) -> Result<()> { - self.wrapped.start_value() - } - - fn start_separator(&mut self) -> Result<()> { - self.wrapped.start_separator() - } -} - -/// Function to filter out ascii control codes. -/// -/// This helps to keep log formatting consistent. -/// Whitespace and padding control codes are excluded. -fn is_ascii_control(character: &u8) -> bool { - matches!( - character, - b'\x00'..=b'\x08' | - b'\x0b'..=b'\x0c' | - b'\x0e'..=b'\x1f' | - b'\x7f' | - b'\x81'..=b'\x9f' - ) -} - /// Provides de-bounce functionality for logging. #[derive(Default)] pub struct TimeLatch(Option); @@ -215,75 +51,127 @@ impl TimeLatch { } } -pub fn create_tracing_layer(base_tracing_log_path: PathBuf) { - let mut tracing_log_path = PathBuf::new(); +pub struct Libp2pDiscv5TracingLayer { + pub libp2p_non_blocking_writer: NonBlocking, + pub _libp2p_guard: WorkerGuard, + pub discv5_non_blocking_writer: NonBlocking, + pub _discv5_guard: WorkerGuard, +} - // Ensure that `tracing_log_path` only contains directories. - for p in base_tracing_log_path.iter() { - tracing_log_path = tracing_log_path.join(p); - if let Ok(metadata) = tracing_log_path.metadata() { - if !metadata.is_dir() { - tracing_log_path.pop(); - break; - } +impl Layer for Libp2pDiscv5TracingLayer +where + S: Subscriber, +{ + fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context) { + let meta = event.metadata(); + let log_level = meta.level(); + let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); + + let target = match meta.target().split_once("::") { + Some((crate_name, _)) => crate_name, + None => "unknown", + }; + + let mut writer = match target { + "gossipsub" => self.libp2p_non_blocking_writer.clone(), + "discv5" => self.discv5_non_blocking_writer.clone(), + _ => return, + }; + + let mut visitor = LogMessageExtractor { + message: String::default(), + }; + + event.record(&mut visitor); + let message = format!("{} {} {}\n", timestamp, log_level, visitor.message); + + if let Err(e) = writer.write_all(message.as_bytes()) { + eprintln!("Failed to write log: {}", e); } } - - let filter_layer = match tracing_subscriber::EnvFilter::try_from_default_env() - .or_else(|_| tracing_subscriber::EnvFilter::try_new("warn")) - { - Ok(filter) => filter, - Err(e) => { - eprintln!("Failed to initialize dependency logging {e}"); - return; - } - }; - - let Ok(libp2p_writer) = RollingFileAppender::builder() - .rotation(Rotation::DAILY) - .max_log_files(2) - .filename_prefix("libp2p") - .filename_suffix("log") - .build(tracing_log_path.clone()) - else { - eprintln!("Failed to initialize libp2p rolling file appender"); - return; - }; - - let Ok(discv5_writer) = RollingFileAppender::builder() - .rotation(Rotation::DAILY) - .max_log_files(2) - .filename_prefix("discv5") - .filename_suffix("log") - .build(tracing_log_path) - else { - eprintln!("Failed to initialize discv5 rolling file appender"); - return; - }; - - let (libp2p_non_blocking_writer, _libp2p_guard) = NonBlocking::new(libp2p_writer); - let (discv5_non_blocking_writer, _discv5_guard) = NonBlocking::new(discv5_writer); - - let custom_layer = LoggingLayer { - libp2p_non_blocking_writer, - _libp2p_guard, - discv5_non_blocking_writer, - _discv5_guard, - }; - - if let Err(e) = tracing_subscriber::fmt() - .with_env_filter(filter_layer) - .with_writer(std::io::sink) - .finish() - .with(MetricsLayer) - .with(custom_layer) - .try_init() - { - eprintln!("Failed to initialize dependency logging {e}"); - } } -/// Return a logger suitable for test usage. +struct LogMessageExtractor { + message: String, +} + +impl tracing_core::field::Visit for LogMessageExtractor { + fn record_debug(&mut self, _: &tracing_core::Field, value: &dyn std::fmt::Debug) { + self.message = format!("{} {:?}", self.message, value); + } +} + +pub fn create_libp2p_discv5_tracing_layer( + base_tracing_log_path: Option, + max_log_size: u64, + compression: bool, + max_log_number: usize, +) -> Libp2pDiscv5TracingLayer { + if let Some(mut tracing_log_path) = base_tracing_log_path { + // Ensure that `tracing_log_path` only contains directories. + for p in tracing_log_path.clone().iter() { + tracing_log_path = tracing_log_path.join(p); + if let Ok(metadata) = tracing_log_path.metadata() { + if !metadata.is_dir() { + tracing_log_path.pop(); + break; + } + } + } + + let mut libp2p_writer = + LogRollerBuilder::new(tracing_log_path.clone(), PathBuf::from("libp2p.log")) + .rotation(Rotation::SizeBased(RotationSize::MB(max_log_size))) + .max_keep_files(max_log_number.try_into().unwrap_or_else(|e| { + eprintln!("Failed to convert max_log_number to u64: {}", e); + 10 + })); + + let mut discv5_writer = + LogRollerBuilder::new(tracing_log_path.clone(), PathBuf::from("discv5.log")) + .rotation(Rotation::SizeBased(RotationSize::MB(max_log_size))) + .max_keep_files(max_log_number.try_into().unwrap_or_else(|e| { + eprintln!("Failed to convert max_log_number to u64: {}", e); + 10 + })); + + if compression { + libp2p_writer = libp2p_writer.compression(Compression::Gzip); + discv5_writer = discv5_writer.compression(Compression::Gzip); + } + + let Ok(libp2p_writer) = libp2p_writer.build() else { + eprintln!("Failed to initialize libp2p rolling file appender"); + std::process::exit(1); + }; + + let Ok(discv5_writer) = discv5_writer.build() else { + eprintln!("Failed to initialize discv5 rolling file appender"); + std::process::exit(1); + }; + + let (libp2p_non_blocking_writer, _libp2p_guard) = NonBlocking::new(libp2p_writer); + let (discv5_non_blocking_writer, _discv5_guard) = NonBlocking::new(discv5_writer); + + Libp2pDiscv5TracingLayer { + libp2p_non_blocking_writer, + _libp2p_guard, + discv5_non_blocking_writer, + _discv5_guard, + } + } else { + let (libp2p_non_blocking_writer, _libp2p_guard) = NonBlocking::new(std::io::sink()); + let (discv5_non_blocking_writer, _discv5_guard) = NonBlocking::new(std::io::sink()); + Libp2pDiscv5TracingLayer { + libp2p_non_blocking_writer, + _libp2p_guard, + discv5_non_blocking_writer, + _discv5_guard, + } + } +} + +/// Return a tracing subscriber suitable for test usage. /// /// By default no logs will be printed, but they can be enabled via /// the `test_logger` feature. This feature can be enabled for any @@ -291,17 +179,10 @@ pub fn create_tracing_layer(base_tracing_log_path: PathBuf) { /// ```bash /// cargo test -p beacon_chain --features logging/test_logger /// ``` -pub fn test_logger() -> Logger { - use sloggers::Build; - +pub fn create_test_tracing_subscriber() { if cfg!(feature = "test_logger") { - sloggers::terminal::TerminalLoggerBuilder::new() - .level(sloggers::types::Severity::Debug) - .build() - .expect("Should build TerminalLoggerBuilder") - } else { - sloggers::null::NullLoggerBuilder - .build() - .expect("Should build NullLoggerBuilder") + let _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::try_new("debug").unwrap()) + .try_init(); } } diff --git a/common/logging/src/sse_logging_components.rs b/common/logging/src/sse_logging_components.rs index 244d09fbd1..e358fde6c6 100644 --- a/common/logging/src/sse_logging_components.rs +++ b/common/logging/src/sse_logging_components.rs @@ -1,46 +1,108 @@ //! This module provides an implementation of `slog::Drain` that optionally writes to a channel if //! there are subscribers to a HTTP SSE stream. -use crate::async_record::AsyncRecord; -use slog::{Drain, OwnedKVList, Record}; -use std::panic::AssertUnwindSafe; +use serde_json::json; +use serde_json::Value; use std::sync::Arc; use tokio::sync::broadcast::Sender; +use tracing::field::{Field, Visit}; +use tracing::{Event, Subscriber}; +use tracing_subscriber::layer::{Context, Layer}; /// Default log level for SSE Events. // NOTE: Made this a constant. Debug level seems to be pretty intense. Can make this // configurable later if needed. -const LOG_LEVEL: slog::Level = slog::Level::Info; +const LOG_LEVEL: tracing::Level = tracing::Level::INFO; /// The components required in the HTTP API task to receive logged events. #[derive(Clone)] pub struct SSELoggingComponents { /// The channel to receive events from. - pub sender: Arc>>, + pub sender: Arc>>, } impl SSELoggingComponents { - /// Create a new SSE drain. pub fn new(channel_size: usize) -> Self { let (sender, _receiver) = tokio::sync::broadcast::channel(channel_size); - let sender = Arc::new(AssertUnwindSafe(sender)); - SSELoggingComponents { sender } + SSELoggingComponents { + sender: Arc::new(sender), + } } } -impl Drain for SSELoggingComponents { - type Ok = (); - type Err = &'static str; +impl Layer for SSELoggingComponents { + fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { + if *event.metadata().level() > LOG_LEVEL { + return; + } - fn log(&self, record: &Record, logger_values: &OwnedKVList) -> Result { - if record.level().is_at_least(LOG_LEVEL) { - // Attempt to send the logs - match self.sender.send(AsyncRecord::from(record, logger_values)) { - Ok(_num_sent) => {} // Everything got sent - Err(_err) => {} // There are no subscribers, do nothing + let mut visitor = TracingEventVisitor::new(); + event.record(&mut visitor); + let mut log_entry = visitor.finish(event.metadata()); + + if let Some(error_type) = log_entry + .get("fields") + .and_then(|fields| fields.get("error_type")) + .and_then(|val| val.as_str()) + { + if error_type.eq_ignore_ascii_case("crit") { + log_entry["level"] = json!("CRIT"); + + if let Some(Value::Object(ref mut map)) = log_entry.get_mut("fields") { + map.remove("error_type"); + } } } - Ok(()) + + let _ = self.sender.send(Arc::new(log_entry)); + } +} +struct TracingEventVisitor { + fields: serde_json::Map, +} + +impl TracingEventVisitor { + fn new() -> Self { + TracingEventVisitor { + fields: serde_json::Map::new(), + } + } + + fn finish(self, metadata: &tracing::Metadata<'_>) -> Value { + let mut log_entry = serde_json::Map::new(); + log_entry.insert( + "time".to_string(), + json!(chrono::Local::now() + .format("%b %d %H:%M:%S%.3f") + .to_string()), + ); + log_entry.insert("level".to_string(), json!(metadata.level().to_string())); + log_entry.insert("target".to_string(), json!(metadata.target())); + log_entry.insert("fields".to_string(), Value::Object(self.fields)); + Value::Object(log_entry) + } +} + +impl Visit for TracingEventVisitor { + fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { + self.fields + .insert(field.name().to_string(), json!(format!("{:?}", value))); + } + + fn record_str(&mut self, field: &Field, value: &str) { + self.fields.insert(field.name().to_string(), json!(value)); + } + + fn record_i64(&mut self, field: &Field, value: i64) { + self.fields.insert(field.name().to_string(), json!(value)); + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.fields.insert(field.name().to_string(), json!(value)); + } + + fn record_bool(&mut self, field: &Field, value: bool) { + self.fields.insert(field.name().to_string(), json!(value)); } } diff --git a/common/logging/src/tracing_logging_layer.rs b/common/logging/src/tracing_logging_layer.rs index a9ddae828a..4478e1facb 100644 --- a/common/logging/src/tracing_logging_layer.rs +++ b/common/logging/src/tracing_logging_layer.rs @@ -1,56 +1,531 @@ use chrono::prelude::*; +use serde_json::{Map, Value}; +use std::collections::HashMap; use std::io::Write; +use std::sync::{Arc, Mutex}; +use tracing::field::Field; +use tracing::span::Id; use tracing::Subscriber; use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; use tracing_subscriber::layer::Context; +use tracing_subscriber::registry::LookupSpan; use tracing_subscriber::Layer; pub struct LoggingLayer { - pub libp2p_non_blocking_writer: NonBlocking, - pub _libp2p_guard: WorkerGuard, - pub discv5_non_blocking_writer: NonBlocking, - pub _discv5_guard: WorkerGuard, + pub non_blocking_writer: NonBlocking, + pub guard: WorkerGuard, + pub disable_log_timestamp: bool, + pub log_color: bool, + pub logfile_color: bool, + pub log_format: Option, + pub logfile_format: Option, + pub extra_info: bool, + pub dep_logs: bool, + span_fields: Arc>>, +} + +impl LoggingLayer { + #[allow(clippy::too_many_arguments)] + pub fn new( + non_blocking_writer: NonBlocking, + guard: WorkerGuard, + disable_log_timestamp: bool, + log_color: bool, + logfile_color: bool, + log_format: Option, + logfile_format: Option, + extra_info: bool, + dep_logs: bool, + ) -> Self { + Self { + non_blocking_writer, + guard, + disable_log_timestamp, + log_color, + logfile_color, + log_format, + logfile_format, + extra_info, + dep_logs, + span_fields: Arc::new(Mutex::new(HashMap::new())), + } + } } impl Layer for LoggingLayer where - S: Subscriber, + S: Subscriber + for<'a> LookupSpan<'a>, { - fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context) { + fn on_new_span(&self, attrs: &tracing::span::Attributes<'_>, id: &Id, _ctx: Context) { + let metadata = attrs.metadata(); + let span_name = metadata.name(); + + let mut visitor = SpanFieldsExtractor::default(); + attrs.record(&mut visitor); + + let span_data = SpanData { + name: span_name.to_string(), + fields: visitor.fields, + }; + + let mut span_fields = match self.span_fields.lock() { + Ok(guard) => guard, + Err(poisoned) => poisoned.into_inner(), + }; + span_fields.insert(id.clone(), span_data); + } + + fn on_event(&self, event: &tracing::Event<'_>, ctx: Context) { let meta = event.metadata(); let log_level = meta.level(); - let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); - - let target = match meta.target().split_once("::") { - Some((crate_name, _)) => crate_name, - None => "unknown", + let timestamp = if !self.disable_log_timestamp { + Local::now().format("%b %d %H:%M:%S%.3f").to_string() + } else { + String::new() }; - let mut writer = match target { - "gossipsub" => self.libp2p_non_blocking_writer.clone(), - "discv5" => self.discv5_non_blocking_writer.clone(), - _ => return, - }; + if !self.dep_logs { + if let Some(file) = meta.file() { + if file.contains("/.cargo/") { + return; + } + } else { + return; + } + } + + let mut writer = self.non_blocking_writer.clone(); let mut visitor = LogMessageExtractor { - message: String::default(), + message: String::new(), + fields: Vec::new(), + is_crit: false, + }; + event.record(&mut visitor); + + // Remove ascii control codes from message. + // All following formatting and logs components are predetermined or known. + if visitor.message.as_bytes().iter().any(u8::is_ascii_control) { + let filtered = visitor + .message + .as_bytes() + .iter() + .map(|c| if is_ascii_control(c) { b'_' } else { *c }) + .collect::>(); + visitor.message = String::from_utf8(filtered).unwrap_or_default(); }; - event.record(&mut visitor); - let message = format!("{} {} {}\n", timestamp, log_level, visitor.message); + let module = meta.module_path().unwrap_or(""); + let file = meta.file().unwrap_or(""); + let line = match meta.line() { + Some(line) => line.to_string(), + None => "".to_string(), + }; - if let Err(e) = writer.write_all(message.as_bytes()) { - eprintln!("Failed to write log: {}", e); + if module.contains("discv5") { + visitor + .fields + .push(("service".to_string(), "\"discv5\"".to_string())); } + + let gray = "\x1b[90m"; + let reset = "\x1b[0m"; + let location = if self.extra_info { + if self.logfile_color { + format!("{}{}::{}:{}{}", gray, module, file, line, reset) + } else { + format!("{}::{}:{}", module, file, line) + } + } else { + String::new() + }; + + let plain_level_str = if visitor.is_crit { + "CRIT" + } else { + match *log_level { + tracing::Level::ERROR => "ERROR", + tracing::Level::WARN => "WARN", + tracing::Level::INFO => "INFO", + tracing::Level::DEBUG => "DEBUG", + tracing::Level::TRACE => "TRACE", + } + }; + + let color_level_str = if visitor.is_crit { + "\x1b[35mCRIT\x1b[0m" + } else { + match *log_level { + tracing::Level::ERROR => "\x1b[31mERROR\x1b[0m", + tracing::Level::WARN => "\x1b[33mWARN\x1b[0m", + tracing::Level::INFO => "\x1b[32mINFO\x1b[0m", + tracing::Level::DEBUG => "\x1b[34mDEBUG\x1b[0m", + tracing::Level::TRACE => "\x1b[35mTRACE\x1b[0m", + } + }; + + if self.dep_logs { + if self.logfile_format.as_deref() == Some("JSON") { + build_json_log_file( + &visitor, + plain_level_str, + meta, + &ctx, + &self.span_fields, + event, + &mut writer, + ); + } else { + build_log_text( + &visitor, + plain_level_str, + ×tamp, + &ctx, + &self.span_fields, + event, + &location, + color_level_str, + self.logfile_color, + &mut writer, + ); + } + } else if self.log_format.as_deref() == Some("JSON") { + build_json_log_stdout(&visitor, plain_level_str, ×tamp, &mut writer); + } else { + build_log_text( + &visitor, + plain_level_str, + ×tamp, + &ctx, + &self.span_fields, + event, + &location, + color_level_str, + self.log_color, + &mut writer, + ); + } + } +} + +struct SpanData { + name: String, + fields: Vec<(String, String)>, +} + +#[derive(Default)] +struct SpanFieldsExtractor { + fields: Vec<(String, String)>, +} + +impl tracing_core::field::Visit for SpanFieldsExtractor { + fn record_str(&mut self, field: &Field, value: &str) { + self.fields + .push((field.name().to_string(), format!("\"{}\"", value))); + } + + fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { + self.fields + .push((field.name().to_string(), format!("{:?}", value))); + } + + fn record_i64(&mut self, field: &Field, value: i64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_bool(&mut self, field: &Field, value: bool) { + self.fields + .push((field.name().to_string(), value.to_string())); } } struct LogMessageExtractor { message: String, + fields: Vec<(String, String)>, + is_crit: bool, } impl tracing_core::field::Visit for LogMessageExtractor { - fn record_debug(&mut self, _: &tracing_core::Field, value: &dyn std::fmt::Debug) { - self.message = format!("{} {:?}", self.message, value); + fn record_str(&mut self, field: &Field, value: &str) { + if field.name() == "message" { + if self.message.is_empty() { + self.message = value.to_string(); + } else { + self.fields + .push(("msg_id".to_string(), format!("\"{}\"", value))); + } + } else if field.name() == "error_type" && value == "crit" { + self.is_crit = true; + } else { + self.fields + .push((field.name().to_string(), format!("\"{}\"", value))); + } + } + + fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { + if field.name() == "message" { + if self.message.is_empty() { + self.message = format!("{:?}", value); + } else { + self.fields + .push(("msg_id".to_string(), format!("{:?}", value))); + } + } else if field.name() == "error_type" && format!("{:?}", value) == "\"crit\"" { + self.is_crit = true; + } else { + self.fields + .push((field.name().to_string(), format!("{:?}", value))); + } + } + + fn record_i64(&mut self, field: &Field, value: i64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_u64(&mut self, field: &Field, value: u64) { + self.fields + .push((field.name().to_string(), value.to_string())); + } + + fn record_bool(&mut self, field: &Field, value: bool) { + self.fields + .push((field.name().to_string(), value.to_string())); } } + +/// Function to filter out ascii control codes. +/// +/// This helps to keep log formatting consistent. +/// Whitespace and padding control codes are excluded. +fn is_ascii_control(character: &u8) -> bool { + matches!( + character, + b'\x00'..=b'\x08' | + b'\x0b'..=b'\x0c' | + b'\x0e'..=b'\x1f' | + b'\x7f' | + b'\x81'..=b'\x9f' + ) +} + +fn build_json_log_stdout( + visitor: &LogMessageExtractor, + plain_level_str: &str, + timestamp: &str, + writer: &mut impl Write, +) { + let mut log_map = Map::new(); + log_map.insert("msg".to_string(), Value::String(visitor.message.clone())); + log_map.insert( + "level".to_string(), + Value::String(plain_level_str.to_string()), + ); + log_map.insert("ts".to_string(), Value::String(timestamp.to_string())); + + for (key, val) in visitor.fields.clone().into_iter() { + let parsed_val = parse_field(&val); + log_map.insert(key, parsed_val); + } + + let json_obj = Value::Object(log_map); + let output = format!("{}\n", json_obj); + + if let Err(e) = writer.write_all(output.as_bytes()) { + eprintln!("Failed to write log: {}", e); + } +} + +fn build_json_log_file<'a, S>( + visitor: &LogMessageExtractor, + plain_level_str: &str, + meta: &tracing::Metadata<'_>, + ctx: &Context<'_, S>, + span_fields: &Arc>>, + event: &tracing::Event<'_>, + writer: &mut impl Write, +) where + S: Subscriber + for<'lookup> LookupSpan<'lookup>, +{ + let utc_timestamp = Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, true); + let mut log_map = Map::new(); + + log_map.insert("msg".to_string(), Value::String(visitor.message.clone())); + log_map.insert( + "level".to_string(), + Value::String(plain_level_str.to_string()), + ); + log_map.insert("ts".to_string(), Value::String(utc_timestamp)); + + let module_path = meta.module_path().unwrap_or(""); + let line_number = meta + .line() + .map_or("".to_string(), |l| l.to_string()); + let module_field = format!("{}:{}", module_path, line_number); + log_map.insert("module".to_string(), Value::String(module_field)); + + for (key, val) in visitor.fields.clone().into_iter() { + let cleaned_value = if val.starts_with('\"') && val.ends_with('\"') && val.len() >= 2 { + &val[1..val.len() - 1] + } else { + &val + }; + let parsed_val = + serde_json::from_str(cleaned_value).unwrap_or(Value::String(cleaned_value.to_string())); + log_map.insert(key, parsed_val); + } + + if let Some(scope) = ctx.event_scope(event) { + let guard = span_fields.lock().ok(); + if let Some(span_map) = guard { + for span in scope { + let id = span.id(); + if let Some(span_data) = span_map.get(&id) { + for (key, val) in &span_data.fields { + let parsed_span_val = parse_field(val); + log_map.insert(key.clone(), parsed_span_val); + } + } + } + } + } + + let json_obj = Value::Object(log_map); + let output = format!("{}\n", json_obj); + + if let Err(e) = writer.write_all(output.as_bytes()) { + eprintln!("Failed to write log: {}", e); + } +} + +#[allow(clippy::too_many_arguments)] +fn build_log_text<'a, S>( + visitor: &LogMessageExtractor, + plain_level_str: &str, + timestamp: &str, + ctx: &Context<'_, S>, + span_fields: &Arc>>, + event: &tracing::Event<'_>, + location: &str, + color_level_str: &str, + use_color: bool, + writer: &mut impl Write, +) where + S: Subscriber + for<'lookup> LookupSpan<'lookup>, +{ + let bold_start = "\x1b[1m"; + let bold_end = "\x1b[0m"; + let mut collected_span_fields = Vec::new(); + + if let Some(scope) = ctx.event_scope(event) { + for span in scope { + let id = span.id(); + let span_fields_map = span_fields.lock().unwrap(); + if let Some(span_data) = span_fields_map.get(&id) { + collected_span_fields.push((span_data.name.clone(), span_data.fields.clone())); + } + } + } + + let mut formatted_spans = String::new(); + for (_, fields) in collected_span_fields.iter().rev() { + for (i, (field_name, field_value)) in fields.iter().enumerate() { + if i > 0 && !visitor.fields.is_empty() { + formatted_spans.push_str(", "); + } + if use_color { + formatted_spans.push_str(&format!( + "{}{}{}: {}", + bold_start, field_name, bold_end, field_value + )); + } else { + formatted_spans.push_str(&format!("{}: {}", field_name, field_value)); + } + } + } + + let level_str = if use_color { + color_level_str + } else { + plain_level_str + }; + + let fixed_message_width = 44; + let message_len = visitor.message.len(); + + let message_content = if use_color { + format!("{}{}{}", bold_start, visitor.message, bold_end) + } else { + visitor.message.clone() + }; + + let padded_message = if message_len < fixed_message_width { + let extra_color_len = if use_color { + bold_start.len() + bold_end.len() + } else { + 0 + }; + format!( + "{: 0 { + formatted_fields.push_str(", "); + } + if use_color { + formatted_fields.push_str(&format!( + "{}{}{}: {}", + bold_start, field_name, bold_end, field_value + )); + } else { + formatted_fields.push_str(&format!("{}: {}", field_name, field_value)); + } + if i == visitor.fields.len() - 1 && !collected_span_fields.is_empty() { + formatted_fields.push(','); + } + } + + let full_message = if !formatted_fields.is_empty() { + format!("{} {}", padded_message, formatted_fields) + } else { + padded_message.to_string() + }; + + let message = if !location.is_empty() { + format!( + "{} {} {} {} {}\n", + timestamp, level_str, location, full_message, formatted_spans + ) + } else { + format!( + "{} {} {} {}\n", + timestamp, level_str, full_message, formatted_spans + ) + }; + + if let Err(e) = writer.write_all(message.as_bytes()) { + eprintln!("Failed to write log: {}", e); + } +} + +fn parse_field(val: &str) -> Value { + let cleaned = if val.starts_with('"') && val.ends_with('"') && val.len() >= 2 { + &val[1..val.len() - 1] + } else { + val + }; + serde_json::from_str(cleaned).unwrap_or(Value::String(cleaned.to_string())) +} diff --git a/common/logging/tests/test.rs b/common/logging/tests/test.rs deleted file mode 100644 index f39f2b6d5a..0000000000 --- a/common/logging/tests/test.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::env; -use std::process::Command; -use std::process::Output; - -fn run_cmd(cmd_line: &str) -> Result { - if cfg!(target_os = "windows") { - Command::new(r#"cmd"#).args(["/C", cmd_line]).output() - } else { - Command::new(r#"sh"#).args(["-c", cmd_line]).output() - } -} - -#[test] -fn test_test_logger_with_feature_test_logger() { - let cur_dir = env::current_dir().unwrap(); - let test_dir = cur_dir - .join("..") - .join("..") - .join("testing") - .join("test-test_logger"); - let cmd_line = format!( - "cd {} && cargo test --features logging/test_logger", - test_dir.to_str().unwrap() - ); - - let output = run_cmd(&cmd_line); - - // Assert output data DOES contain "INFO hi, " - let data = String::from_utf8(output.unwrap().stderr).unwrap(); - println!("data={}", data); - assert!(data.contains("INFO hi, ")); -} - -#[test] -fn test_test_logger_no_features() { - // Test without features - let cur_dir = env::current_dir().unwrap(); - let test_dir = cur_dir - .join("..") - .join("..") - .join("testing") - .join("test-test_logger"); - let cmd_line = format!("cd {} && cargo test", test_dir.to_str().unwrap()); - - let output = run_cmd(&cmd_line); - - // Assert output data DOES contain "INFO hi, " - let data = String::from_utf8(output.unwrap().stderr).unwrap(); - println!("data={}", data); - assert!(!data.contains("INFO hi, ")); -} diff --git a/common/malloc_utils/src/jemalloc.rs b/common/malloc_utils/src/jemalloc.rs index f3a35fc41c..2e90c0ddf3 100644 --- a/common/malloc_utils/src/jemalloc.rs +++ b/common/malloc_utils/src/jemalloc.rs @@ -7,9 +7,11 @@ //! //! A) `JEMALLOC_SYS_WITH_MALLOC_CONF` at compile-time. //! B) `_RJEM_MALLOC_CONF` at runtime. -use metrics::{set_gauge, try_create_int_gauge, IntGauge}; +use metrics::{ + set_gauge, set_gauge_vec, try_create_int_gauge, try_create_int_gauge_vec, IntGauge, IntGaugeVec, +}; use std::sync::LazyLock; -use tikv_jemalloc_ctl::{arenas, epoch, stats, Access, AsName, Error}; +use tikv_jemalloc_ctl::{arenas, epoch, raw, stats, Access, AsName, Error}; #[global_allocator] static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; @@ -33,6 +35,38 @@ pub static BYTES_RESIDENT: LazyLock> = LazyLock::new(| pub static BYTES_RETAINED: LazyLock> = LazyLock::new(|| { try_create_int_gauge("jemalloc_bytes_retained", "Equivalent to stats.retained") }); +pub static JEMALLOC_ARENAS_SMALL_NMALLOC: LazyLock> = + LazyLock::new(|| { + try_create_int_gauge_vec( + "jemalloc_arenas_small_nmalloc", + "Equivalent to stats.arenas..small.nmalloc", + &["arena"], + ) + }); +pub static JEMALLOC_ARENAS_SMALL_NDALLOC: LazyLock> = + LazyLock::new(|| { + try_create_int_gauge_vec( + "jemalloc_arenas_small_ndalloc", + "Equivalent to stats.arenas..small.ndalloc", + &["arena"], + ) + }); +pub static JEMALLOC_ARENAS_LARGE_NMALLOC: LazyLock> = + LazyLock::new(|| { + try_create_int_gauge_vec( + "jemalloc_arenas_large_nmalloc", + "Equivalent to stats.arenas..large.nmalloc", + &["arena"], + ) + }); +pub static JEMALLOC_ARENAS_LARGE_NDALLOC: LazyLock> = + LazyLock::new(|| { + try_create_int_gauge_vec( + "jemalloc_arenas_large_ndalloc", + "Equivalent to stats.arenas..large.ndalloc", + &["arena"], + ) + }); pub fn scrape_jemalloc_metrics() { scrape_jemalloc_metrics_fallible().unwrap() @@ -42,7 +76,8 @@ pub fn scrape_jemalloc_metrics_fallible() -> Result<(), Error> { // Advance the epoch so that the underlying statistics are updated. epoch::advance()?; - set_gauge(&NUM_ARENAS, arenas::narenas::read()? as i64); + let num_arenas = arenas::narenas::read()?; + set_gauge(&NUM_ARENAS, num_arenas as i64); set_gauge(&BYTES_ALLOCATED, stats::allocated::read()? as i64); set_gauge(&BYTES_ACTIVE, stats::active::read()? as i64); set_gauge(&BYTES_MAPPED, stats::mapped::read()? as i64); @@ -50,9 +85,40 @@ pub fn scrape_jemalloc_metrics_fallible() -> Result<(), Error> { set_gauge(&BYTES_RESIDENT, stats::resident::read()? as i64); set_gauge(&BYTES_RETAINED, stats::retained::read()? as i64); + for arena in 0..num_arenas { + unsafe { + set_stats_gauge( + &JEMALLOC_ARENAS_SMALL_NMALLOC, + arena, + &format!("stats.arenas.{arena}.small.nmalloc\0"), + ); + set_stats_gauge( + &JEMALLOC_ARENAS_SMALL_NDALLOC, + arena, + &format!("stats.arenas.{arena}.small.ndalloc\0"), + ); + set_stats_gauge( + &JEMALLOC_ARENAS_LARGE_NMALLOC, + arena, + &format!("stats.arenas.{arena}.large.nmalloc\0"), + ); + set_stats_gauge( + &JEMALLOC_ARENAS_LARGE_NDALLOC, + arena, + &format!("stats.arenas.{arena}.large.ndalloc\0"), + ); + } + } + Ok(()) } +unsafe fn set_stats_gauge(metric: &metrics::Result, arena: u32, stat: &str) { + if let Ok(val) = raw::read::(stat.as_bytes()) { + set_gauge_vec(metric, &[&format!("arena_{arena}")], val as i64); + } +} + pub fn page_size() -> Result { // Full list of keys: https://jemalloc.net/jemalloc.3.html "arenas.page\0".name().read() diff --git a/common/monitoring_api/Cargo.toml b/common/monitoring_api/Cargo.toml index cb52cff29a..9e2c36e2c7 100644 --- a/common/monitoring_api/Cargo.toml +++ b/common/monitoring_api/Cargo.toml @@ -15,7 +15,7 @@ reqwest = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -slog = { workspace = true } store = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } diff --git a/common/monitoring_api/src/lib.rs b/common/monitoring_api/src/lib.rs index 6f919971b0..966a1a3054 100644 --- a/common/monitoring_api/src/lib.rs +++ b/common/monitoring_api/src/lib.rs @@ -9,9 +9,9 @@ use reqwest::{IntoUrl, Response}; pub use reqwest::{StatusCode, Url}; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{debug, error, info}; use task_executor::TaskExecutor; use tokio::time::{interval_at, Instant}; +use tracing::{debug, error, info}; use types::*; pub use types::ProcessType; @@ -69,11 +69,10 @@ pub struct MonitoringHttpClient { freezer_db_path: Option, update_period: Duration, monitoring_endpoint: SensitiveUrl, - log: slog::Logger, } impl MonitoringHttpClient { - pub fn new(config: &Config, log: slog::Logger) -> Result { + pub fn new(config: &Config) -> Result { Ok(Self { client: reqwest::Client::new(), db_path: config.db_path.clone(), @@ -83,7 +82,6 @@ impl MonitoringHttpClient { ), monitoring_endpoint: SensitiveUrl::parse(&config.monitoring_endpoint) .map_err(|e| format!("Invalid monitoring endpoint: {:?}", e))?, - log, }) } @@ -111,10 +109,9 @@ impl MonitoringHttpClient { ); info!( - self.log, - "Starting monitoring API"; - "endpoint" => %self.monitoring_endpoint, - "update_period" => format!("{}s", self.update_period.as_secs()), + endpoint = %self.monitoring_endpoint, + update_period = format!("{}s", self.update_period.as_secs()), + "Starting monitoring API" ); let update_future = async move { @@ -122,10 +119,10 @@ impl MonitoringHttpClient { interval.tick().await; match self.send_metrics(&processes).await { Ok(()) => { - debug!(self.log, "Metrics sent to remote server"; "endpoint" => %self.monitoring_endpoint); + debug!(endpoint = %self.monitoring_endpoint, "Metrics sent to remote server"); } Err(e) => { - error!(self.log, "Failed to send metrics to remote endpoint"; "error" => %e) + error!(error = %e, "Failed to send metrics to remote endpoint") } } } @@ -187,18 +184,16 @@ impl MonitoringHttpClient { for process in processes { match self.get_metrics(process).await { Err(e) => error!( - self.log, - "Failed to get metrics"; - "process_type" => ?process, - "error" => %e + process_type = ?process, + error = %e, + "Failed to get metrics" ), Ok(metric) => metrics.push(metric), } } info!( - self.log, - "Sending metrics to remote endpoint"; - "endpoint" => %self.monitoring_endpoint + endpoint = %self.monitoring_endpoint, + "Sending metrics to remote endpoint" ); self.post(self.monitoring_endpoint.full.clone(), &metrics) .await diff --git a/common/task_executor/Cargo.toml b/common/task_executor/Cargo.toml index c1ac4b55a9..4224f00acc 100644 --- a/common/task_executor/Cargo.toml +++ b/common/task_executor/Cargo.toml @@ -4,17 +4,9 @@ version = "0.1.0" authors = ["Sigma Prime "] edition = { workspace = true } -[features] -default = ["slog"] -slog = ["dep:slog", "dep:sloggers", "dep:logging"] -tracing = ["dep:tracing"] - [dependencies] async-channel = { workspace = true } futures = { workspace = true } -logging = { workspace = true, optional = true } metrics = { workspace = true } -slog = { workspace = true, optional = true } -sloggers = { workspace = true, optional = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -tracing = { workspace = true, optional = true } +tracing = { workspace = true } diff --git a/common/task_executor/src/lib.rs b/common/task_executor/src/lib.rs index 92ddb7c0be..dbdac600f3 100644 --- a/common/task_executor/src/lib.rs +++ b/common/task_executor/src/lib.rs @@ -1,20 +1,14 @@ mod metrics; -#[cfg(not(feature = "tracing"))] pub mod test_utils; use futures::channel::mpsc::Sender; use futures::prelude::*; use std::sync::Weak; use tokio::runtime::{Handle, Runtime}; +use tracing::{debug, instrument}; pub use tokio::task::JoinHandle; -// Set up logging framework -#[cfg(not(feature = "tracing"))] -use slog::{debug, o}; -#[cfg(feature = "tracing")] -use tracing::debug; - /// Provides a reason when Lighthouse is shut down. #[derive(Copy, Clone, Debug, PartialEq)] pub enum ShutdownReason { @@ -85,8 +79,9 @@ pub struct TaskExecutor { /// /// The task must provide a reason for shutting down. signal_tx: Sender, - #[cfg(not(feature = "tracing"))] - log: slog::Logger, + + /// The name of the service for inclusion in the logger output. + service_name: String, } impl TaskExecutor { @@ -97,39 +92,29 @@ impl TaskExecutor { /// This function should only be used during testing. In production, prefer to obtain an /// instance of `Self` via a `environment::RuntimeContext` (see the `lighthouse/environment` /// crate). + #[instrument(parent = None,level = "info", fields(service = service_name), name = "task_executor", skip_all)] pub fn new>( handle: T, exit: async_channel::Receiver<()>, - #[cfg(not(feature = "tracing"))] log: slog::Logger, signal_tx: Sender, + service_name: String, ) -> Self { Self { handle_provider: handle.into(), exit, signal_tx, - #[cfg(not(feature = "tracing"))] - log, + service_name, } } /// Clones the task executor adding a service name. - #[cfg(not(feature = "tracing"))] + #[instrument(parent = None,level = "info", fields(service = service_name), name = "task_executor", skip_all)] pub fn clone_with_name(&self, service_name: String) -> Self { TaskExecutor { handle_provider: self.handle_provider.clone(), exit: self.exit.clone(), signal_tx: self.signal_tx.clone(), - log: self.log.new(o!("service" => service_name)), - } - } - - /// Clones the task executor adding a service name. - #[cfg(feature = "tracing")] - pub fn clone(&self) -> Self { - TaskExecutor { - handle_provider: self.handle_provider.clone(), - exit: self.exit.clone(), - signal_tx: self.signal_tx.clone(), + service_name, } } @@ -139,6 +124,7 @@ impl TaskExecutor { /// The purpose of this function is to create a compile error if some function which previously /// returned `()` starts returning something else. Such a case may otherwise result in /// accidental error suppression. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn_ignoring_error( &self, task: impl Future> + Send + 'static, @@ -150,6 +136,7 @@ impl TaskExecutor { /// Spawn a task to monitor the completion of another task. /// /// If the other task exits by panicking, then the monitor task will shut down the executor. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] fn spawn_monitor( &self, task_handle: impl Future> + Send + 'static, @@ -168,13 +155,7 @@ impl TaskExecutor { drop(timer); }); } else { - #[cfg(not(feature = "tracing"))] - debug!( - self.log, - "Couldn't spawn monitor task. Runtime shutting down" - ); - #[cfg(feature = "tracing")] - debug!("Couldn't spawn monitor task. Runtime shutting down"); + debug!("Couldn't spawn monitor task. Runtime shutting down") } } @@ -187,6 +168,7 @@ impl TaskExecutor { /// of a panic, the executor will be shut down via `self.signal_tx`. /// /// This function generates prometheus metrics on number of tasks and task duration. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn(&self, task: impl Future + Send + 'static, name: &'static str) { if let Some(task_handle) = self.spawn_handle(task, name) { self.spawn_monitor(task_handle, name) @@ -202,6 +184,7 @@ impl TaskExecutor { /// This is useful in cases where the future to be spawned needs to do additional cleanup work when /// the task is completed/canceled (e.g. writing local variables to disk) or the task is created from /// some framework which does its own cleanup (e.g. a hyper server). + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn_without_exit( &self, task: impl Future + Send + 'static, @@ -218,9 +201,6 @@ impl TaskExecutor { if let Some(handle) = self.handle() { handle.spawn(future); } else { - #[cfg(not(feature = "tracing"))] - debug!(self.log, "Couldn't spawn task. Runtime shutting down"); - #[cfg(feature = "tracing")] debug!("Couldn't spawn task. Runtime shutting down"); } } @@ -242,16 +222,13 @@ impl TaskExecutor { /// The task is cancelled when the corresponding async-channel is dropped. /// /// This function generates prometheus metrics on number of tasks and task duration. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn_handle( &self, task: impl Future + Send + 'static, name: &'static str, ) -> Option>> { let exit = self.exit(); - - #[cfg(not(feature = "tracing"))] - let log = self.log.clone(); - if let Some(int_gauge) = metrics::get_int_gauge(&metrics::ASYNC_TASKS_COUNT, &[name]) { // Task is shutdown before it completes if `exit` receives let int_gauge_1 = int_gauge.clone(); @@ -262,9 +239,6 @@ impl TaskExecutor { let result = match future::select(Box::pin(task), exit).await { future::Either::Left((value, _)) => Some(value), future::Either::Right(_) => { - #[cfg(not(feature = "tracing"))] - debug!(log, "Async task shutdown, exit received"; "task" => name); - #[cfg(feature = "tracing")] debug!(task = name, "Async task shutdown, exit received"); None } @@ -273,9 +247,6 @@ impl TaskExecutor { result })) } else { - #[cfg(not(feature = "tracing"))] - debug!(log, "Couldn't spawn task. Runtime shutting down"); - #[cfg(feature = "tracing")] debug!("Couldn't spawn task. Runtime shutting down"); None } @@ -290,6 +261,7 @@ impl TaskExecutor { /// The Future returned behaves like the standard JoinHandle which can return an error if the /// task failed. /// This function generates prometheus metrics on number of tasks and task duration. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn spawn_blocking_handle( &self, task: F, @@ -299,18 +271,12 @@ impl TaskExecutor { F: FnOnce() -> R + Send + 'static, R: Send + 'static, { - #[cfg(not(feature = "tracing"))] - let log = self.log.clone(); - let timer = metrics::start_timer_vec(&metrics::BLOCKING_TASKS_HISTOGRAM, &[name]); metrics::inc_gauge_vec(&metrics::BLOCKING_TASKS_COUNT, &[name]); let join_handle = if let Some(handle) = self.handle() { handle.spawn_blocking(task) } else { - #[cfg(not(feature = "tracing"))] - debug!(self.log, "Couldn't spawn task. Runtime shutting down"); - #[cfg(feature = "tracing")] debug!("Couldn't spawn task. Runtime shutting down"); return None; }; @@ -319,9 +285,6 @@ impl TaskExecutor { let result = match join_handle.await { Ok(result) => Ok(result), Err(error) => { - #[cfg(not(feature = "tracing"))] - debug!(log, "Blocking task ended unexpectedly"; "error" => %error); - #[cfg(feature = "tracing")] debug!(%error, "Blocking task ended unexpectedly"); Err(error) } @@ -347,6 +310,7 @@ impl TaskExecutor { /// a `tokio` context present in the thread-local storage due to some `rayon` funkiness. Talk to /// @paulhauner if you plan to use this function in production. He has put metrics in here to /// track any use of it, so don't think you can pull a sneaky one on him. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn block_on_dangerous( &self, future: F, @@ -354,44 +318,20 @@ impl TaskExecutor { ) -> Option { let timer = metrics::start_timer_vec(&metrics::BLOCK_ON_TASKS_HISTOGRAM, &[name]); metrics::inc_gauge_vec(&metrics::BLOCK_ON_TASKS_COUNT, &[name]); - #[cfg(not(feature = "tracing"))] - let log = self.log.clone(); let handle = self.handle()?; let exit = self.exit(); - #[cfg(not(feature = "tracing"))] - debug!( - log, - "Starting block_on task"; - "name" => name - ); - - #[cfg(feature = "tracing")] debug!(name, "Starting block_on task"); handle.block_on(async { let output = tokio::select! { output = future => { - #[cfg(not(feature = "tracing"))] - debug!( - log, - "Completed block_on task"; - "name" => name - ); - #[cfg(feature = "tracing")] debug!( name, "Completed block_on task" ); Some(output) - }, + } _ = exit => { - #[cfg(not(feature = "tracing"))] - debug!( - log, - "Cancelled block_on task"; - "name" => name, - ); - #[cfg(feature = "tracing")] debug!( name, "Cancelled block_on task" @@ -406,6 +346,7 @@ impl TaskExecutor { } /// Returns a `Handle` to the current runtime. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn handle(&self) -> Option { self.handle_provider.handle() } @@ -420,13 +361,8 @@ impl TaskExecutor { } /// Get a channel to request shutting down. + #[instrument(parent = None,level = "info", fields(service = self.service_name), name = "task_executor", skip_all)] pub fn shutdown_sender(&self) -> Sender { self.signal_tx.clone() } - - /// Returns a reference to the logger. - #[cfg(not(feature = "tracing"))] - pub fn log(&self) -> &slog::Logger { - &self.log - } } diff --git a/common/task_executor/src/test_utils.rs b/common/task_executor/src/test_utils.rs index 46fbff7eac..698152f6c1 100644 --- a/common/task_executor/src/test_utils.rs +++ b/common/task_executor/src/test_utils.rs @@ -1,6 +1,4 @@ use crate::TaskExecutor; -pub use logging::test_logger; -use slog::Logger; use std::sync::Arc; use tokio::runtime; @@ -16,7 +14,6 @@ pub struct TestRuntime { runtime: Option>, _runtime_shutdown: async_channel::Sender<()>, pub task_executor: TaskExecutor, - pub log: Logger, } impl Default for TestRuntime { @@ -26,7 +23,6 @@ impl Default for TestRuntime { fn default() -> Self { let (runtime_shutdown, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let log = test_logger(); let (runtime, handle) = if let Ok(handle) = runtime::Handle::try_current() { (None, handle) @@ -41,13 +37,12 @@ impl Default for TestRuntime { (Some(runtime), handle) }; - let task_executor = TaskExecutor::new(handle, exit, log.clone(), shutdown_tx); + let task_executor = TaskExecutor::new(handle, exit, shutdown_tx, "test".to_string()); Self { runtime, _runtime_shutdown: runtime_shutdown, task_executor, - log, } } } @@ -59,10 +54,3 @@ impl Drop for TestRuntime { } } } - -impl TestRuntime { - pub fn set_logger(&mut self, log: Logger) { - self.log = log.clone(); - self.task_executor.log = log; - } -} diff --git a/consensus/fork_choice/Cargo.toml b/consensus/fork_choice/Cargo.toml index 3bd18e922a..5c009a5e78 100644 --- a/consensus/fork_choice/Cargo.toml +++ b/consensus/fork_choice/Cargo.toml @@ -8,10 +8,11 @@ edition = { workspace = true } [dependencies] ethereum_ssz = { workspace = true } ethereum_ssz_derive = { workspace = true } +logging = { workspace = true } metrics = { workspace = true } proto_array = { workspace = true } -slog = { workspace = true } state_processing = { workspace = true } +tracing = { workspace = true } types = { workspace = true } [dev-dependencies] diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index 4c25be950b..28a3ecdd02 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -1,10 +1,10 @@ use crate::metrics::{self, scrape_for_metrics}; use crate::{ForkChoiceStore, InvalidationOperation}; +use logging::crit; use proto_array::{ Block as ProtoBlock, DisallowedReOrgOffsets, ExecutionStatus, ProposerHeadError, ProposerHeadInfo, ProtoArrayForkChoice, ReOrgThreshold, }; -use slog::{crit, debug, warn, Logger}; use ssz_derive::{Decode, Encode}; use state_processing::{ per_block_processing::errors::AttesterSlashingValidationError, per_epoch_processing, @@ -13,6 +13,7 @@ use std::cmp::Ordering; use std::collections::BTreeSet; use std::marker::PhantomData; use std::time::Duration; +use tracing::{debug, warn}; use types::{ consts::bellatrix::INTERVALS_PER_SLOT, AbstractExecPayload, AttestationShufflingId, AttesterSlashingRef, BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, Checkpoint, @@ -1365,17 +1366,14 @@ where persisted: &PersistedForkChoice, reset_payload_statuses: ResetPayloadStatuses, spec: &ChainSpec, - log: &Logger, ) -> Result> { let mut proto_array = ProtoArrayForkChoice::from_bytes(&persisted.proto_array_bytes) .map_err(Error::InvalidProtoArrayBytes)?; let contains_invalid_payloads = proto_array.contains_invalid_payloads(); debug!( - log, - "Restoring fork choice from persisted"; - "reset_payload_statuses" => ?reset_payload_statuses, - "contains_invalid_payloads" => contains_invalid_payloads, + ?reset_payload_statuses, + contains_invalid_payloads, "Restoring fork choice from persisted" ); // Exit early if there are no "invalid" payloads, if requested. @@ -1394,18 +1392,14 @@ where // back to a proto-array which does not have the reset applied. This indicates a // significant error in Lighthouse and warrants detailed investigation. crit!( - log, - "Failed to reset payload statuses"; - "error" => e, - "info" => "please report this error", + error = ?e, + info = "please report this error", + "Failed to reset payload statuses" ); ProtoArrayForkChoice::from_bytes(&persisted.proto_array_bytes) .map_err(Error::InvalidProtoArrayBytes) } else { - debug!( - log, - "Successfully reset all payload statuses"; - ); + debug!("Successfully reset all payload statuses"); Ok(proto_array) } } @@ -1417,10 +1411,9 @@ where reset_payload_statuses: ResetPayloadStatuses, fc_store: T, spec: &ChainSpec, - log: &Logger, ) -> Result> { let proto_array = - Self::proto_array_from_persisted(&persisted, reset_payload_statuses, spec, log)?; + Self::proto_array_from_persisted(&persisted, reset_payload_statuses, spec)?; let current_slot = fc_store.get_current_slot(); @@ -1444,10 +1437,9 @@ where // an optimistic status so that we can have a head to start from. if let Err(e) = fork_choice.get_head(current_slot, spec) { warn!( - log, - "Could not find head on persisted FC"; - "info" => "resetting all payload statuses and retrying", - "error" => ?e + info = "resetting all payload statuses and retrying", + error = ?e, + "Could not find head on persisted FC" ); // Although we may have already made this call whilst loading `proto_array`, try it // again since we may have mutated the `proto_array` during `get_head` and therefore may diff --git a/consensus/merkle_proof/src/lib.rs b/consensus/merkle_proof/src/lib.rs index b01f3f4429..271e676df1 100644 --- a/consensus/merkle_proof/src/lib.rs +++ b/consensus/merkle_proof/src/lib.rs @@ -34,6 +34,8 @@ pub enum MerkleTree { pub enum MerkleTreeError { // Trying to push in a leaf LeafReached, + // Trying to generate a proof for a non-leaf node + NonLeafProof, // No more space in the MerkleTree MerkleTreeFull, // MerkleTree is invalid @@ -313,8 +315,17 @@ impl MerkleTree { current_depth -= 1; } - debug_assert_eq!(proof.len(), depth); - debug_assert!(current_node.is_leaf()); + if proof.len() != depth { + // This should be unreachable regardless of how the method is called, because we push + // one proof element for each layer of `depth`. + return Err(MerkleTreeError::PleaseNotifyTheDevs); + } + + // Generating a proof for a non-leaf node is invalid and indicates an error on the part of + // the caller. + if !current_node.is_leaf() { + return Err(MerkleTreeError::NonLeafProof); + } // Put proof in bottom-up order. proof.reverse(); diff --git a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs index 24cb51d755..8d4a544196 100644 --- a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs +++ b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs @@ -293,7 +293,6 @@ where )?); Ok(()) }) - .map_err(Error::into) } /// Includes all signatures in `self.block.body.voluntary_exits` for verification. diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index fdeec6f08c..ff7c0204e2 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -60,6 +60,7 @@ pub enum BlockProcessingError { SignatureSetError(SignatureSetError), SszTypesError(ssz_types::Error), SszDecodeError(DecodeError), + BitfieldError(ssz::BitfieldError), MerkleTreeError(MerkleTreeError), ArithError(ArithError), InconsistentBlockFork(InconsistentFork), @@ -153,6 +154,7 @@ impl From> for BlockProcessingError { BlockOperationError::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e), BlockOperationError::SignatureSetError(e) => BlockProcessingError::SignatureSetError(e), BlockOperationError::SszTypesError(e) => BlockProcessingError::SszTypesError(e), + BlockOperationError::BitfieldError(e) => BlockProcessingError::BitfieldError(e), BlockOperationError::ConsensusContext(e) => BlockProcessingError::ConsensusContext(e), BlockOperationError::ArithError(e) => BlockProcessingError::ArithError(e), } @@ -181,6 +183,7 @@ macro_rules! impl_into_block_processing_error_with_index { BlockOperationError::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e), BlockOperationError::SignatureSetError(e) => BlockProcessingError::SignatureSetError(e), BlockOperationError::SszTypesError(e) => BlockProcessingError::SszTypesError(e), + BlockOperationError::BitfieldError(e) => BlockProcessingError::BitfieldError(e), BlockOperationError::ConsensusContext(e) => BlockProcessingError::ConsensusContext(e), BlockOperationError::ArithError(e) => BlockProcessingError::ArithError(e), } @@ -215,6 +218,7 @@ pub enum BlockOperationError { BeaconStateError(BeaconStateError), SignatureSetError(SignatureSetError), SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), ConsensusContext(ContextError), ArithError(ArithError), } @@ -242,6 +246,12 @@ impl From for BlockOperationError { } } +impl From for BlockOperationError { + fn from(error: ssz::BitfieldError) -> Self { + BlockOperationError::BitfieldError(error) + } +} + impl From for BlockOperationError { fn from(e: ArithError) -> Self { BlockOperationError::ArithError(e) @@ -367,6 +377,7 @@ impl From> BlockOperationError::BeaconStateError(e) => BlockOperationError::BeaconStateError(e), BlockOperationError::SignatureSetError(e) => BlockOperationError::SignatureSetError(e), BlockOperationError::SszTypesError(e) => BlockOperationError::SszTypesError(e), + BlockOperationError::BitfieldError(e) => BlockOperationError::BitfieldError(e), BlockOperationError::ConsensusContext(e) => BlockOperationError::ConsensusContext(e), BlockOperationError::ArithError(e) => BlockOperationError::ArithError(e), } diff --git a/consensus/state_processing/src/per_epoch_processing/errors.rs b/consensus/state_processing/src/per_epoch_processing/errors.rs index f45c55a7ac..7485e365ec 100644 --- a/consensus/state_processing/src/per_epoch_processing/errors.rs +++ b/consensus/state_processing/src/per_epoch_processing/errors.rs @@ -19,9 +19,10 @@ pub enum EpochProcessingError { BeaconStateError(BeaconStateError), InclusionError(InclusionError), SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), ArithError(safe_arith::ArithError), InconsistentStateFork(InconsistentFork), - InvalidJustificationBit(ssz_types::Error), + InvalidJustificationBit(ssz::BitfieldError), InvalidFlagIndex(usize), MilhouseError(milhouse::Error), EpochCache(EpochCacheError), @@ -49,6 +50,12 @@ impl From for EpochProcessingError { } } +impl From for EpochProcessingError { + fn from(e: ssz::BitfieldError) -> EpochProcessingError { + EpochProcessingError::BitfieldError(e) + } +} + impl From for EpochProcessingError { fn from(e: safe_arith::ArithError) -> EpochProcessingError { EpochProcessingError::ArithError(e) diff --git a/consensus/types/Cargo.toml b/consensus/types/Cargo.toml index 79beb81282..b31485600d 100644 --- a/consensus/types/Cargo.toml +++ b/consensus/types/Cargo.toml @@ -28,7 +28,6 @@ hex = { workspace = true } int_to_bytes = { workspace = true } itertools = { workspace = true } kzg = { workspace = true } -log = { workspace = true } maplit = { workspace = true } merkle_proof = { workspace = true } metastruct = "0.1.0" @@ -44,13 +43,13 @@ safe_arith = { workspace = true } serde = { workspace = true, features = ["rc"] } serde_json = { workspace = true } serde_yaml = { workspace = true } -slog = { workspace = true } smallvec = { workspace = true } ssz_types = { workspace = true, features = ["arbitrary"] } superstruct = { workspace = true } swap_or_not_shuffle = { workspace = true, features = ["arbitrary"] } tempfile = { workspace = true } test_random_derive = { path = "../../common/test_random_derive" } +tracing = { workspace = true } tree_hash = { workspace = true } tree_hash_derive = { workspace = true } diff --git a/consensus/types/src/attestation.rs b/consensus/types/src/attestation.rs index a0b083cdf2..5d147f1e86 100644 --- a/consensus/types/src/attestation.rs +++ b/consensus/types/src/attestation.rs @@ -15,9 +15,10 @@ use super::{ Signature, SignedRoot, }; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq)] pub enum Error { SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), AlreadySigned(usize), IncorrectStateVariant, InvalidCommitteeLength, @@ -223,7 +224,7 @@ impl Attestation { } } - pub fn get_aggregation_bit(&self, index: usize) -> Result { + pub fn get_aggregation_bit(&self, index: usize) -> Result { match self { Attestation::Base(att) => att.aggregation_bits.get(index), Attestation::Electra(att) => att.aggregation_bits.get(index), @@ -353,13 +354,13 @@ impl AttestationElectra { if self .aggregation_bits .get(committee_position) - .map_err(Error::SszTypesError)? + .map_err(Error::BitfieldError)? { Err(Error::AlreadySigned(committee_position)) } else { self.aggregation_bits .set(committee_position, true) - .map_err(Error::SszTypesError)?; + .map_err(Error::BitfieldError)?; self.signature.add_assign(signature); @@ -427,13 +428,13 @@ impl AttestationBase { if self .aggregation_bits .get(committee_position) - .map_err(Error::SszTypesError)? + .map_err(Error::BitfieldError)? { Err(Error::AlreadySigned(committee_position)) } else { self.aggregation_bits .set(committee_position, true) - .map_err(Error::SszTypesError)?; + .map_err(Error::BitfieldError)?; self.signature.add_assign(signature); @@ -443,7 +444,7 @@ impl AttestationBase { pub fn extend_aggregation_bits( &self, - ) -> Result, ssz_types::Error> { + ) -> Result, ssz::BitfieldError> { self.aggregation_bits.resize::() } } @@ -600,12 +601,12 @@ mod tests { let attestation_data = size_of::(); let signature = size_of::(); - assert_eq!(aggregation_bits, 56); + assert_eq!(aggregation_bits, 152); assert_eq!(attestation_data, 128); assert_eq!(signature, 288 + 16); let attestation_expected = aggregation_bits + attestation_data + signature; - assert_eq!(attestation_expected, 488); + assert_eq!(attestation_expected, 584); assert_eq!( size_of::>(), attestation_expected @@ -623,13 +624,13 @@ mod tests { size_of::::MaxCommitteesPerSlot>>(); let signature = size_of::(); - assert_eq!(aggregation_bits, 56); - assert_eq!(committee_bits, 56); + assert_eq!(aggregation_bits, 152); + assert_eq!(committee_bits, 152); assert_eq!(attestation_data, 128); assert_eq!(signature, 288 + 16); let attestation_expected = aggregation_bits + committee_bits + attestation_data + signature; - assert_eq!(attestation_expected, 544); + assert_eq!(attestation_expected, 736); assert_eq!( size_of::>(), attestation_expected diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 3f75790a35..10c1a11ede 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -277,9 +277,9 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, // https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#beaconblockbody generalized_index .checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) - .ok_or(Error::IndexNotSupported(generalized_index))? + .ok_or(Error::GeneralizedIndexNotSupported(generalized_index))? } - _ => return Err(Error::IndexNotSupported(generalized_index)), + _ => return Err(Error::GeneralizedIndexNotSupported(generalized_index)), }; let leaves = self.body_merkle_leaves(); @@ -971,6 +971,7 @@ impl From>> Option>, ) { + #[allow(clippy::useless_conversion)] // Not a useless conversion fn from(body: BeaconBlockBody>) -> Self { map_beacon_block_body!(body, |inner, cons| { let (block, payload) = inner.into(); diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 157271b227..4aed79898d 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -157,6 +157,7 @@ pub enum Error { current_fork: ForkName, }, TotalActiveBalanceDiffUninitialized, + GeneralizedIndexNotSupported(usize), IndexNotSupported(usize), InvalidFlagIndex(usize), MerkleTreeError(merkle_proof::MerkleTreeError), @@ -2580,11 +2581,12 @@ impl BeaconState { // for the internal nodes. Result should be 22 or 23, the field offset of the committee // in the `BeaconState`: // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate - let field_index = if self.fork_name_unchecked().electra_enabled() { + let field_gindex = if self.fork_name_unchecked().electra_enabled() { light_client_update::CURRENT_SYNC_COMMITTEE_INDEX_ELECTRA } else { light_client_update::CURRENT_SYNC_COMMITTEE_INDEX }; + let field_index = field_gindex.safe_sub(self.num_fields_pow2())?; let leaves = self.get_beacon_state_leaves(); self.generate_proof(field_index, &leaves) } @@ -2594,11 +2596,12 @@ impl BeaconState { // for the internal nodes. Result should be 22 or 23, the field offset of the committee // in the `BeaconState`: // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate - let field_index = if self.fork_name_unchecked().electra_enabled() { + let field_gindex = if self.fork_name_unchecked().electra_enabled() { light_client_update::NEXT_SYNC_COMMITTEE_INDEX_ELECTRA } else { light_client_update::NEXT_SYNC_COMMITTEE_INDEX }; + let field_index = field_gindex.safe_sub(self.num_fields_pow2())?; let leaves = self.get_beacon_state_leaves(); self.generate_proof(field_index, &leaves) } @@ -2606,17 +2609,24 @@ impl BeaconState { pub fn compute_finalized_root_proof(&self) -> Result, Error> { // Finalized root is the right child of `finalized_checkpoint`, divide by two to get // the generalized index of `state.finalized_checkpoint`. - let field_index = if self.fork_name_unchecked().electra_enabled() { - // Index should be 169/2 - 64 = 20 which matches the position - // of `finalized_checkpoint` in `BeaconState` + let checkpoint_root_gindex = if self.fork_name_unchecked().electra_enabled() { light_client_update::FINALIZED_ROOT_INDEX_ELECTRA } else { - // Index should be 105/2 - 32 = 20 which matches the position - // of `finalized_checkpoint` in `BeaconState` light_client_update::FINALIZED_ROOT_INDEX }; + let checkpoint_gindex = checkpoint_root_gindex / 2; + + // Convert gindex to index by subtracting 2**depth (gindex = 2**depth + index). + // + // After Electra, the index should be 169/2 - 64 = 20 which matches the position + // of `finalized_checkpoint` in `BeaconState`. + // + // Prior to Electra, the index should be 105/2 - 32 = 20 which matches the position + // of `finalized_checkpoint` in `BeaconState`. + let checkpoint_index = checkpoint_gindex.safe_sub(self.num_fields_pow2())?; + let leaves = self.get_beacon_state_leaves(); - let mut proof = self.generate_proof(field_index, &leaves)?; + let mut proof = self.generate_proof(checkpoint_index, &leaves)?; proof.insert(0, self.finalized_checkpoint().epoch.tree_hash_root()); Ok(proof) } @@ -2626,6 +2636,10 @@ impl BeaconState { field_index: usize, leaves: &[Hash256], ) -> Result, Error> { + if field_index >= leaves.len() { + return Err(Error::IndexNotSupported(field_index)); + } + let depth = self.num_fields_pow2().ilog2() as usize; let tree = merkle_proof::MerkleTree::create(leaves, depth); let (_, proof) = tree.generate_proof(field_index, depth)?; diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index 230805e86c..1650001db6 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -712,6 +712,10 @@ impl ChainSpec { } } + pub fn all_data_column_sidecar_subnets(&self) -> impl Iterator { + (0..self.data_column_sidecar_subnet_count).map(DataColumnSubnetId::new) + } + /// Returns a `ChainSpec` compatible with the Ethereum Foundation specification. pub fn mainnet() -> Self { Self { diff --git a/consensus/types/src/data_column_custody_group.rs b/consensus/types/src/data_column_custody_group.rs index bb204c34a2..9e9505da9f 100644 --- a/consensus/types/src/data_column_custody_group.rs +++ b/consensus/types/src/data_column_custody_group.rs @@ -17,6 +17,8 @@ pub enum DataColumnCustodyGroupError { /// The `get_custody_groups` function is used to determine the custody groups that a node is /// assigned to. /// +/// Note: `get_custody_groups(node_id, x)` is a subset of `get_custody_groups(node_id, y)` if `x < y`. +/// /// spec: https://github.com/ethereum/consensus-specs/blob/8e0d0d48e81d6c7c5a8253ab61340f5ea5bac66a/specs/fulu/das-core.md#get_custody_groups pub fn get_custody_groups( raw_node_id: [u8; 32], diff --git a/consensus/types/src/payload.rs b/consensus/types/src/payload.rs index c0262a2cf8..abc9afd34c 100644 --- a/consensus/types/src/payload.rs +++ b/consensus/types/src/payload.rs @@ -85,7 +85,6 @@ pub trait AbstractExecPayload: + TryInto + TryInto + TryInto - + Sync { type Ref<'a>: ExecPayload + Copy @@ -98,28 +97,23 @@ pub trait AbstractExecPayload: type Bellatrix: OwnedExecPayload + Into + for<'a> From>> - + TryFrom> - + Sync; + + TryFrom>; type Capella: OwnedExecPayload + Into + for<'a> From>> - + TryFrom> - + Sync; + + TryFrom>; type Deneb: OwnedExecPayload + Into + for<'a> From>> - + TryFrom> - + Sync; + + TryFrom>; type Electra: OwnedExecPayload + Into + for<'a> From>> - + TryFrom> - + Sync; + + TryFrom>; type Fulu: OwnedExecPayload + Into + for<'a> From>> - + TryFrom> - + Sync; + + TryFrom>; } #[superstruct( diff --git a/consensus/types/src/runtime_var_list.rs b/consensus/types/src/runtime_var_list.rs index 857073b3b8..d6b1c10e99 100644 --- a/consensus/types/src/runtime_var_list.rs +++ b/consensus/types/src/runtime_var_list.rs @@ -134,13 +134,13 @@ impl RuntimeVariableList { ))); } - bytes - .chunks(::ssz_fixed_len()) - .try_fold(Vec::with_capacity(num_items), |mut vec, chunk| { + bytes.chunks(::ssz_fixed_len()).try_fold( + Vec::with_capacity(num_items), + |mut vec, chunk| { vec.push(::from_ssz_bytes(chunk)?); Ok(vec) - }) - .map(Into::into)? + }, + )? } else { ssz::decode_list_of_variable_length_items(bytes, Some(max_len))? }; diff --git a/consensus/types/src/slot_epoch_macros.rs b/consensus/types/src/slot_epoch_macros.rs index 42e7a0f2ee..eee267355a 100644 --- a/consensus/types/src/slot_epoch_macros.rs +++ b/consensus/types/src/slot_epoch_macros.rs @@ -227,17 +227,6 @@ macro_rules! impl_display { write!(f, "{}", self.0) } } - - impl slog::Value for $type { - fn serialize( - &self, - record: &slog::Record, - key: slog::Key, - serializer: &mut dyn slog::Serializer, - ) -> slog::Result { - slog::Value::serialize(&self.0, record, key, serializer) - } - } }; } diff --git a/consensus/types/src/sync_aggregate.rs b/consensus/types/src/sync_aggregate.rs index 43f72a3924..12b91501ae 100644 --- a/consensus/types/src/sync_aggregate.rs +++ b/consensus/types/src/sync_aggregate.rs @@ -11,6 +11,7 @@ use tree_hash_derive::TreeHash; #[derive(Debug, PartialEq)] pub enum Error { SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), ArithError(ArithError), } @@ -68,7 +69,7 @@ impl SyncAggregate { sync_aggregate .sync_committee_bits .set(participant_index, true) - .map_err(Error::SszTypesError)?; + .map_err(Error::BitfieldError)?; } } sync_aggregate diff --git a/consensus/types/src/sync_committee_contribution.rs b/consensus/types/src/sync_committee_contribution.rs index 9bae770fe5..58983d26ec 100644 --- a/consensus/types/src/sync_committee_contribution.rs +++ b/consensus/types/src/sync_committee_contribution.rs @@ -9,6 +9,7 @@ use tree_hash_derive::TreeHash; #[derive(Debug, PartialEq)] pub enum Error { SszTypesError(ssz_types::Error), + BitfieldError(ssz::BitfieldError), AlreadySigned(usize), } @@ -51,7 +52,7 @@ impl SyncCommitteeContribution { ) -> Result { let mut bits = BitVector::new(); bits.set(validator_sync_committee_index, true) - .map_err(Error::SszTypesError)?; + .map_err(Error::BitfieldError)?; Ok(Self { slot: message.slot, beacon_block_root: message.beacon_block_root, diff --git a/consensus/types/src/test_utils/generate_deterministic_keypairs.rs b/consensus/types/src/test_utils/generate_deterministic_keypairs.rs index 92534369ee..f30afda257 100644 --- a/consensus/types/src/test_utils/generate_deterministic_keypairs.rs +++ b/consensus/types/src/test_utils/generate_deterministic_keypairs.rs @@ -1,8 +1,8 @@ use crate::*; use eth2_interop_keypairs::{keypair, keypairs_from_yaml_file}; -use log::debug; use rayon::prelude::*; use std::path::PathBuf; +use tracing::debug; /// Generates `validator_count` keypairs where the secret key is derived solely from the index of /// the validator. diff --git a/consensus/types/src/validator_registration_data.rs b/consensus/types/src/validator_registration_data.rs index cdafd355e7..345771074c 100644 --- a/consensus/types/src/validator_registration_data.rs +++ b/consensus/types/src/validator_registration_data.rs @@ -4,7 +4,7 @@ use ssz_derive::{Decode, Encode}; use tree_hash_derive::TreeHash; /// Validator registration, for use in interacting with servers implementing the builder API. -#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] +#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)] pub struct SignedValidatorRegistrationData { pub message: ValidatorRegistrationData, pub signature: Signature, diff --git a/crypto/bls/src/generic_secret_key.rs b/crypto/bls/src/generic_secret_key.rs index c48fdc4198..62bfc1467d 100644 --- a/crypto/bls/src/generic_secret_key.rs +++ b/crypto/bls/src/generic_secret_key.rs @@ -66,15 +66,6 @@ where &self.point } - /// Instantiates `Self` from a `point`. - pub fn from_point(point: Sec) -> Self { - Self { - point, - _phantom_signature: PhantomData, - _phantom_public_key: PhantomData, - } - } - /// Serialize `self` as compressed bytes. /// /// ## Note @@ -103,3 +94,20 @@ where } } } + +impl GenericSecretKey +where + Sig: TSignature, + Pub: TPublicKey, + Sec: TSecretKey + Clone, +{ + /// Instantiates `Self` from a `point`. + /// Takes a reference, as moves might accidentally leave behind key material + pub fn from_point(point: &Sec) -> Self { + Self { + point: point.clone(), + _phantom_signature: PhantomData, + _phantom_public_key: PhantomData, + } + } +} diff --git a/crypto/bls/src/generic_signature.rs b/crypto/bls/src/generic_signature.rs index 355f17aef1..0b375d3edd 100644 --- a/crypto/bls/src/generic_signature.rs +++ b/crypto/bls/src/generic_signature.rs @@ -25,6 +25,16 @@ pub const INFINITY_SIGNATURE: [u8; SIGNATURE_BYTES_LEN] = [ 0, ]; +pub const INFINITY_SIGNATURE_UNCOMPRESSED: [u8; SIGNATURE_UNCOMPRESSED_BYTES_LEN] = [ + 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]; + /// The compressed bytes used to represent `GenericSignature::empty()`. pub const NONE_SIGNATURE: [u8; SIGNATURE_BYTES_LEN] = [0; SIGNATURE_BYTES_LEN]; @@ -148,9 +158,11 @@ where /// Deserialize `self` from uncompressed bytes. pub fn deserialize_uncompressed(bytes: &[u8]) -> Result { + // The "none signature" is a beacon chain concept. As we never directly deal with + // uncompressed signatures on the beacon chain, it does not apply here. Ok(Self { point: Some(Sig::deserialize_uncompressed(bytes)?), - is_infinity: false, // todo + is_infinity: bytes == &INFINITY_SIGNATURE_UNCOMPRESSED[..], _phantom: PhantomData, }) } diff --git a/crypto/bls/src/lib.rs b/crypto/bls/src/lib.rs index 6ea85548c0..13b6dc2f2c 100644 --- a/crypto/bls/src/lib.rs +++ b/crypto/bls/src/lib.rs @@ -37,7 +37,10 @@ pub use generic_public_key::{ INFINITY_PUBLIC_KEY, PUBLIC_KEY_BYTES_LEN, PUBLIC_KEY_UNCOMPRESSED_BYTES_LEN, }; pub use generic_secret_key::SECRET_KEY_BYTES_LEN; -pub use generic_signature::{INFINITY_SIGNATURE, SIGNATURE_BYTES_LEN}; +pub use generic_signature::{ + INFINITY_SIGNATURE, INFINITY_SIGNATURE_UNCOMPRESSED, SIGNATURE_BYTES_LEN, + SIGNATURE_UNCOMPRESSED_BYTES_LEN, +}; pub use get_withdrawal_credentials::get_withdrawal_credentials; pub use zeroize_hash::ZeroizeHash; diff --git a/crypto/bls/tests/tests.rs b/crypto/bls/tests/tests.rs index 26215771b5..611dabbd64 100644 --- a/crypto/bls/tests/tests.rs +++ b/crypto/bls/tests/tests.rs @@ -1,4 +1,7 @@ -use bls::{FixedBytesExtended, Hash256, INFINITY_SIGNATURE, SECRET_KEY_BYTES_LEN}; +use bls::{ + FixedBytesExtended, Hash256, INFINITY_SIGNATURE, INFINITY_SIGNATURE_UNCOMPRESSED, + SECRET_KEY_BYTES_LEN, +}; use ssz::{Decode, Encode}; use std::borrow::Cow; use std::fmt::Debug; @@ -37,6 +40,18 @@ macro_rules! test_suite { assert!(AggregateSignature::infinity().is_infinity()); } + #[test] + fn infinity_sig_serializations_match() { + let sig = Signature::deserialize(&INFINITY_SIGNATURE).unwrap(); + assert_eq!( + sig.serialize_uncompressed().unwrap(), + INFINITY_SIGNATURE_UNCOMPRESSED + ); + let sig = + Signature::deserialize_uncompressed(&INFINITY_SIGNATURE_UNCOMPRESSED).unwrap(); + assert_eq!(sig.serialize(), INFINITY_SIGNATURE); + } + #[test] fn ssz_round_trip_multiple_types() { let mut agg_sig = AggregateSignature::infinity(); diff --git a/database_manager/Cargo.toml b/database_manager/Cargo.toml index a7a54b1416..99bef75a72 100644 --- a/database_manager/Cargo.toml +++ b/database_manager/Cargo.toml @@ -11,7 +11,7 @@ clap_utils = { workspace = true } environment = { workspace = true } hex = { workspace = true } serde = { workspace = true } -slog = { workspace = true } store = { workspace = true } strum = { workspace = true } +tracing = { workspace = true } types = { workspace = true } diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index bed90df9df..f38c28d8b0 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -12,7 +12,6 @@ use clap::ValueEnum; use cli::{Compact, Inspect}; use environment::{Environment, RuntimeContext}; use serde::{Deserialize, Serialize}; -use slog::{info, warn, Logger}; use std::fs; use std::io::Write; use std::path::PathBuf; @@ -24,6 +23,7 @@ use store::{ DBColumn, HotColdDB, }; use strum::{EnumString, EnumVariantNames}; +use tracing::{info, warn}; use types::{BeaconState, EthSpec, Slot}; fn parse_client_config( @@ -49,7 +49,6 @@ fn parse_client_config( pub fn display_db_version( client_config: ClientConfig, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), Error> { let spec = runtime_context.eth2_config.spec.clone(); let hot_path = client_config.get_db_path(); @@ -67,16 +66,14 @@ pub fn display_db_version( }, client_config.store, spec, - log.clone(), )?; - info!(log, "Database version: {}", version.as_u64()); + info!(version = version.as_u64(), "Database"); if version != CURRENT_SCHEMA_VERSION { info!( - log, - "Latest schema version: {}", - CURRENT_SCHEMA_VERSION.as_u64(), + current_schema_version = CURRENT_SCHEMA_VERSION.as_u64(), + "Latest schema" ); } @@ -260,7 +257,6 @@ fn parse_compact_config(compact_config: &Compact) -> Result( compact_config: CompactConfig, client_config: ClientConfig, - log: Logger, ) -> Result<(), Error> { let hot_path = client_config.get_db_path(); let cold_path = client_config.get_freezer_db_path(); @@ -284,10 +280,9 @@ pub fn compact_db( ) }; info!( - log, - "Compacting database"; - "db" => db_name, - "column" => ?column + db = db_name, + column = ?column, + "Compacting database" ); sub_db.compact_column(column)?; Ok(()) @@ -308,7 +303,6 @@ pub fn migrate_db( client_config: ClientConfig, mut genesis_state: BeaconState, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), Error> { let spec = runtime_context.eth2_config.spec.clone(); let hot_path = client_config.get_db_path(); @@ -327,14 +321,12 @@ pub fn migrate_db( }, client_config.store.clone(), spec.clone(), - log.clone(), )?; info!( - log, - "Migrating database schema"; - "from" => from.as_u64(), - "to" => to.as_u64(), + from = from.as_u64(), + to = to.as_u64(), + "Migrating database schema" ); let genesis_state_root = genesis_state.canonical_root()?; @@ -343,14 +335,12 @@ pub fn migrate_db( Some(genesis_state_root), from, to, - log, ) } pub fn prune_payloads( client_config: ClientConfig, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), Error> { let spec = &runtime_context.eth2_config.spec; let hot_path = client_config.get_db_path(); @@ -364,7 +354,6 @@ pub fn prune_payloads( |_, _, _| Ok(()), client_config.store, spec.clone(), - log, )?; // If we're trigging a prune manually then ignore the check on the split's parent that bails @@ -376,7 +365,6 @@ pub fn prune_payloads( pub fn prune_blobs( client_config: ClientConfig, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), Error> { let spec = &runtime_context.eth2_config.spec; let hot_path = client_config.get_db_path(); @@ -390,7 +378,6 @@ pub fn prune_blobs( |_, _, _| Ok(()), client_config.store, spec.clone(), - log, )?; // If we're triggering a prune manually then ignore the check on `epochs_per_blob_prune` that @@ -413,7 +400,6 @@ pub fn prune_states( prune_config: PruneStatesConfig, mut genesis_state: BeaconState, runtime_context: &RuntimeContext, - log: Logger, ) -> Result<(), String> { let spec = &runtime_context.eth2_config.spec; let hot_path = client_config.get_db_path(); @@ -427,7 +413,6 @@ pub fn prune_states( |_, _, _| Ok(()), client_config.store, spec.clone(), - log.clone(), ) .map_err(|e| format!("Unable to open database: {e:?}"))?; @@ -447,20 +432,14 @@ pub fn prune_states( // Check that the user has confirmed they want to proceed. if !prune_config.confirm { if db.get_anchor_info().full_state_pruning_enabled() { - info!(log, "States have already been pruned"); + info!("States have already been pruned"); return Ok(()); } - info!(log, "Ready to prune states"); - warn!( - log, - "Pruning states is irreversible"; - ); - warn!( - log, - "Re-run this command with --confirm to commit to state deletion" - ); - info!(log, "Nothing has been pruned on this run"); + info!("Ready to prune states"); + warn!("Pruning states is irreversible"); + warn!("Re-run this command with --confirm to commit to state deletion"); + info!("Nothing has been pruned on this run"); return Err("Error: confirmation flag required".into()); } @@ -471,7 +450,7 @@ pub fn prune_states( db.prune_historic_states(genesis_state_root, &genesis_state) .map_err(|e| format!("Failed to prune due to error: {e:?}"))?; - info!(log, "Historic states pruned successfully"); + info!("Historic states pruned successfully"); Ok(()) } @@ -483,7 +462,6 @@ pub fn run( ) -> Result<(), String> { let client_config = parse_client_config(cli_args, db_manager_config, &env)?; let context = env.core_context(); - let log = context.log().clone(); let format_err = |e| format!("Fatal error: {:?}", e); let get_genesis_state = || { @@ -498,7 +476,6 @@ pub fn run( network_config.genesis_state::( client_config.genesis_state_url.as_deref(), client_config.genesis_state_url_timeout, - &log, ), "get_genesis_state", ) @@ -511,30 +488,29 @@ pub fn run( cli::DatabaseManagerSubcommand::Migrate(migrate_config) => { let migrate_config = parse_migrate_config(migrate_config)?; let genesis_state = get_genesis_state()?; - migrate_db(migrate_config, client_config, genesis_state, &context, log) - .map_err(format_err) + migrate_db(migrate_config, client_config, genesis_state, &context).map_err(format_err) } cli::DatabaseManagerSubcommand::Inspect(inspect_config) => { let inspect_config = parse_inspect_config(inspect_config)?; inspect_db::(inspect_config, client_config) } cli::DatabaseManagerSubcommand::Version(_) => { - display_db_version(client_config, &context, log).map_err(format_err) + display_db_version(client_config, &context).map_err(format_err) } cli::DatabaseManagerSubcommand::PrunePayloads(_) => { - prune_payloads(client_config, &context, log).map_err(format_err) + prune_payloads(client_config, &context).map_err(format_err) } cli::DatabaseManagerSubcommand::PruneBlobs(_) => { - prune_blobs(client_config, &context, log).map_err(format_err) + prune_blobs(client_config, &context).map_err(format_err) } cli::DatabaseManagerSubcommand::PruneStates(prune_states_config) => { let prune_config = parse_prune_states_config(prune_states_config)?; let genesis_state = get_genesis_state()?; - prune_states(client_config, prune_config, genesis_state, &context, log) + prune_states(client_config, prune_config, genesis_state, &context) } cli::DatabaseManagerSubcommand::Compact(compact_config) => { let compact_config = parse_compact_config(compact_config)?; - compact_db::(compact_config, client_config, log).map_err(format_err) + compact_db::(compact_config, client_config).map_err(format_err) } } } diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index 72be77a70b..b7c226f8cd 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lcli" description = "Lighthouse CLI (modeled after zcli)" -version = "6.0.1" +version = "7.0.0-beta.0" authors = ["Paul Hauner "] edition = { workspace = true } @@ -34,10 +34,11 @@ rayon = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } -sloggers = { workspace = true } snap = { workspace = true } state_processing = { workspace = true } store = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } tree_hash = { workspace = true } types = { workspace = true } validator_dir = { workspace = true } diff --git a/lcli/src/block_root.rs b/lcli/src/block_root.rs index a90a4843d8..80087fd6d4 100644 --- a/lcli/src/block_root.rs +++ b/lcli/src/block_root.rs @@ -32,9 +32,9 @@ use clap_utils::{parse_optional, parse_required}; use environment::Environment; use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use eth2_network_config::Eth2NetworkConfig; -use log::info; use std::path::PathBuf; use std::time::{Duration, Instant}; +use tracing::info; use types::{EthSpec, FullPayload, SignedBeaconBlock}; const HTTP_TIMEOUT: Duration = Duration::from_secs(5); @@ -102,7 +102,7 @@ pub fn run( } if let Some(block_root) = block_root { - info!("Block root is {:?}", block_root); + info!(%block_root,"Block root"); } Ok(()) diff --git a/lcli/src/main.rs b/lcli/src/main.rs index f055a23b36..5bfd2233f0 100644 --- a/lcli/src/main.rs +++ b/lcli/src/main.rs @@ -18,6 +18,7 @@ use parse_ssz::run_parse_ssz; use std::path::PathBuf; use std::process; use std::str::FromStr; +use tracing_subscriber::filter::LevelFilter; use types::{EthSpec, EthSpecId}; fn main() { @@ -643,24 +644,31 @@ fn main() { } fn run(env_builder: EnvironmentBuilder, matches: &ArgMatches) -> Result<(), String> { + let (env_builder, _file_logging_layer, _stdout_logging_layer, _sse_logging_layer_opt) = + env_builder + .multi_threaded_tokio_runtime() + .map_err(|e| format!("should start tokio runtime: {:?}", e))? + .init_tracing( + LoggerConfig { + path: None, + debug_level: LevelFilter::TRACE, + logfile_debug_level: LevelFilter::TRACE, + log_format: None, + logfile_format: None, + log_color: true, + logfile_color: false, + disable_log_timestamp: false, + max_log_size: 0, + max_log_number: 0, + compression: false, + is_restricted: true, + sse_logging: false, // No SSE Logging in LCLI + extra_info: false, + }, + "", + ); + let env = env_builder - .multi_threaded_tokio_runtime() - .map_err(|e| format!("should start tokio runtime: {:?}", e))? - .initialize_logger(LoggerConfig { - path: None, - debug_level: String::from("trace"), - logfile_debug_level: String::from("trace"), - log_format: None, - logfile_format: None, - log_color: false, - disable_log_timestamp: false, - max_log_size: 0, - max_log_number: 0, - compression: false, - is_restricted: true, - sse_logging: false, // No SSE Logging in LCLI - }) - .map_err(|e| format!("should start logger: {:?}", e))? .build() .map_err(|e| format!("should build env: {:?}", e))?; diff --git a/lcli/src/parse_ssz.rs b/lcli/src/parse_ssz.rs index dd13f6847b..f1e5c5759a 100644 --- a/lcli/src/parse_ssz.rs +++ b/lcli/src/parse_ssz.rs @@ -1,7 +1,6 @@ use clap::ArgMatches; use clap_utils::parse_required; use eth2_network_config::Eth2NetworkConfig; -use log::info; use serde::Serialize; use snap::raw::Decoder; use ssz::Decode; @@ -9,6 +8,7 @@ use std::fs; use std::fs::File; use std::io::Read; use std::str::FromStr; +use tracing::info; use types::*; enum OutputFormat { @@ -59,7 +59,7 @@ pub fn run_parse_ssz( spec.config_name.as_deref().unwrap_or("unknown"), E::spec_name() ); - info!("Type: {type_str}"); + info!(%type_str, "Type"); // More fork-specific decoders may need to be added in future, but shouldn't be 100% necessary, // as the fork-generic decoder will always be available (requires correct --network flag). diff --git a/lcli/src/skip_slots.rs b/lcli/src/skip_slots.rs index 2ad79051ea..834123e939 100644 --- a/lcli/src/skip_slots.rs +++ b/lcli/src/skip_slots.rs @@ -50,7 +50,6 @@ use clap_utils::{parse_optional, parse_required}; use environment::Environment; use eth2::{types::StateId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use eth2_network_config::Eth2NetworkConfig; -use log::info; use ssz::Encode; use state_processing::state_advance::{complete_state_advance, partial_state_advance}; use state_processing::AllCaches; @@ -58,6 +57,7 @@ use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; use std::time::{Duration, Instant}; +use tracing::info; use types::{BeaconState, EthSpec, Hash256}; const HTTP_TIMEOUT: Duration = Duration::from_secs(10); diff --git a/lcli/src/state_root.rs b/lcli/src/state_root.rs index 17a947b2f0..b2308999d4 100644 --- a/lcli/src/state_root.rs +++ b/lcli/src/state_root.rs @@ -4,9 +4,9 @@ use clap_utils::{parse_optional, parse_required}; use environment::Environment; use eth2::{types::StateId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use eth2_network_config::Eth2NetworkConfig; -use log::info; use std::path::PathBuf; use std::time::{Duration, Instant}; +use tracing::info; use types::{BeaconState, EthSpec}; const HTTP_TIMEOUT: Duration = Duration::from_secs(10); diff --git a/lcli/src/transition_blocks.rs b/lcli/src/transition_blocks.rs index ecfa04fc81..4831f86491 100644 --- a/lcli/src/transition_blocks.rs +++ b/lcli/src/transition_blocks.rs @@ -72,8 +72,6 @@ use eth2::{ BeaconNodeHttpClient, SensitiveUrl, Timeouts, }; use eth2_network_config::Eth2NetworkConfig; -use log::{debug, info}; -use sloggers::{null::NullLoggerBuilder, Build}; use ssz::Encode; use state_processing::state_advance::complete_state_advance; use state_processing::{ @@ -87,6 +85,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::{Duration, Instant}; use store::HotColdDB; +use tracing::{debug, info}; use types::{BeaconState, ChainSpec, EthSpec, Hash256, SignedBeaconBlock}; const HTTP_TIMEOUT: Duration = Duration::from_secs(10); @@ -196,14 +195,8 @@ pub fn run( * Create a `BeaconStore` and `ValidatorPubkeyCache` for block signature verification. */ - let store = HotColdDB::open_ephemeral( - <_>::default(), - spec.clone(), - NullLoggerBuilder - .build() - .map_err(|e| format!("Error on NullLoggerBuilder: {:?}", e))?, - ) - .map_err(|e| format!("Failed to create ephemeral store: {:?}", e))?; + let store = HotColdDB::open_ephemeral(<_>::default(), spec.clone()) + .map_err(|e| format!("Failed to create ephemeral store: {:?}", e))?; let store = Arc::new(store); debug!("Building pubkey cache (might take some time)"); diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index c95735d41c..d941293e91 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lighthouse" -version = "6.0.1" +version = "7.0.0-beta.0" authors = ["Sigma Prime "] edition = { workspace = true } autotests = false @@ -60,9 +60,10 @@ serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } slasher = { workspace = true } -slog = { workspace = true } store = { workspace = true } task_executor = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } unused_port = { workspace = true } validator_client = { workspace = true } diff --git a/lighthouse/environment/Cargo.toml b/lighthouse/environment/Cargo.toml index 02b8e0b655..6d6ffa1725 100644 --- a/lighthouse/environment/Cargo.toml +++ b/lighthouse/environment/Cargo.toml @@ -6,18 +6,19 @@ edition = { workspace = true } [dependencies] async-channel = { workspace = true } +clap = { workspace = true } eth2_config = { workspace = true } eth2_network_config = { workspace = true } futures = { workspace = true } logging = { workspace = true } +logroller = { workspace = true } serde = { workspace = true } -slog = { workspace = true } -slog-async = { workspace = true } -slog-json = "2.3.0" -slog-term = { workspace = true } -sloggers = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } +tracing-appender = { workspace = true } +tracing-log = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } [target.'cfg(not(target_family = "unix"))'.dependencies] diff --git a/lighthouse/environment/src/lib.rs b/lighthouse/environment/src/lib.rs index 89d759d662..005d2734c7 100644 --- a/lighthouse/environment/src/lib.rs +++ b/lighthouse/environment/src/lib.rs @@ -11,31 +11,38 @@ use eth2_config::Eth2Config; use eth2_network_config::Eth2NetworkConfig; use futures::channel::mpsc::{channel, Receiver, Sender}; use futures::{future, StreamExt}; - -use logging::{test_logger, SSELoggingComponents}; +use logging::tracing_logging_layer::LoggingLayer; +use logging::SSELoggingComponents; +use logroller::{Compression, LogRollerBuilder, Rotation, RotationSize}; use serde::{Deserialize, Serialize}; -use slog::{error, info, o, warn, Drain, Duplicate, Level, Logger}; -use sloggers::{file::FileLoggerBuilder, types::Format, types::Severity, Build}; -use std::fs::create_dir_all; -use std::io::{Result as IOResult, Write}; use std::path::PathBuf; use std::sync::Arc; use task_executor::{ShutdownReason, TaskExecutor}; use tokio::runtime::{Builder as RuntimeBuilder, Runtime}; +use tracing::{error, info, warn}; +use tracing_subscriber::filter::LevelFilter; use types::{EthSpec, GnosisEthSpec, MainnetEthSpec, MinimalEthSpec}; #[cfg(target_family = "unix")] use { futures::Future, - std::{pin::Pin, task::Context, task::Poll}, + std::{ + fs::{read_dir, set_permissions, Permissions}, + os::unix::fs::PermissionsExt, + path::Path, + pin::Pin, + task::Context, + task::Poll, + }, tokio::signal::unix::{signal, Signal, SignalKind}, }; #[cfg(not(target_family = "unix"))] use {futures::channel::oneshot, std::cell::RefCell}; -const LOG_CHANNEL_SIZE: usize = 16384; -const SSE_LOG_CHANNEL_SIZE: usize = 2048; +pub mod tracing_common; + +pub const SSE_LOG_CHANNEL_SIZE: usize = 2048; /// The maximum time in seconds the client will wait for all internal tasks to shutdown. const MAXIMUM_SHUTDOWN_TIME: u64 = 15; @@ -47,37 +54,54 @@ const MAXIMUM_SHUTDOWN_TIME: u64 = 15; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LoggerConfig { pub path: Option, - pub debug_level: String, - pub logfile_debug_level: String, + #[serde(skip_serializing, skip_deserializing, default = "default_debug_level")] + pub debug_level: LevelFilter, + #[serde( + skip_serializing, + skip_deserializing, + default = "default_logfile_debug_level" + )] + pub logfile_debug_level: LevelFilter, pub log_format: Option, pub logfile_format: Option, pub log_color: bool, + pub logfile_color: bool, pub disable_log_timestamp: bool, pub max_log_size: u64, pub max_log_number: usize, pub compression: bool, pub is_restricted: bool, pub sse_logging: bool, + pub extra_info: bool, } impl Default for LoggerConfig { fn default() -> Self { LoggerConfig { path: None, - debug_level: String::from("info"), - logfile_debug_level: String::from("debug"), + debug_level: LevelFilter::INFO, + logfile_debug_level: LevelFilter::DEBUG, log_format: None, + log_color: true, logfile_format: None, - log_color: false, + logfile_color: false, disable_log_timestamp: false, max_log_size: 200, max_log_number: 5, compression: false, is_restricted: true, sse_logging: false, + extra_info: false, } } } +fn default_debug_level() -> LevelFilter { + LevelFilter::INFO +} + +fn default_logfile_debug_level() -> LevelFilter { + LevelFilter::DEBUG +} /// An execution context that can be used by a service. /// /// Distinct from an `Environment` because a `Context` is not able to give a mutable reference to a @@ -109,17 +133,11 @@ impl RuntimeContext { pub fn eth2_config(&self) -> &Eth2Config { &self.eth2_config } - - /// Returns a reference to the logger for this service. - pub fn log(&self) -> &slog::Logger { - self.executor.log() - } } /// Builds an `Environment`. pub struct EnvironmentBuilder { runtime: Option>, - log: Option, sse_logging_components: Option, eth_spec_instance: E, eth2_config: Eth2Config, @@ -131,7 +149,6 @@ impl EnvironmentBuilder { pub fn minimal() -> Self { Self { runtime: None, - log: None, sse_logging_components: None, eth_spec_instance: MinimalEthSpec, eth2_config: Eth2Config::minimal(), @@ -145,7 +162,6 @@ impl EnvironmentBuilder { pub fn mainnet() -> Self { Self { runtime: None, - log: None, sse_logging_components: None, eth_spec_instance: MainnetEthSpec, eth2_config: Eth2Config::mainnet(), @@ -159,7 +175,6 @@ impl EnvironmentBuilder { pub fn gnosis() -> Self { Self { runtime: None, - log: None, sse_logging_components: None, eth_spec_instance: GnosisEthSpec, eth2_config: Eth2Config::gnosis(), @@ -182,149 +197,123 @@ impl EnvironmentBuilder { Ok(self) } - /// Sets a logger suitable for test usage. - pub fn test_logger(mut self) -> Result { - self.log = Some(test_logger()); - Ok(self) - } + pub fn init_tracing( + mut self, + config: LoggerConfig, + logfile_prefix: &str, + ) -> ( + Self, + LoggingLayer, + LoggingLayer, + Option, + ) { + let filename_prefix = match logfile_prefix { + "beacon_node" => "beacon", + "validator_client" => "validator", + _ => logfile_prefix, + }; - fn log_nothing(_: &mut dyn Write) -> IOResult<()> { - Ok(()) - } + #[cfg(target_family = "unix")] + let file_mode = if config.is_restricted { 0o600 } else { 0o644 }; - /// Initializes the logger using the specified configuration. - /// The logger is "async" because it has a dedicated thread that accepts logs and then - /// asynchronously flushes them to stdout/files/etc. This means the thread that raised the log - /// does not have to wait for the logs to be flushed. - /// The logger can be duplicated and more detailed logs can be output to `logfile`. - /// Note that background file logging will spawn a new thread. - pub fn initialize_logger(mut self, config: LoggerConfig) -> Result { - // Setting up the initial logger format and build it. - let stdout_drain = if let Some(ref format) = config.log_format { - match format.to_uppercase().as_str() { - "JSON" => { - let stdout_drain = slog_json::Json::default(std::io::stdout()).fuse(); - slog_async::Async::new(stdout_drain) - .chan_size(LOG_CHANNEL_SIZE) - .build() + let file_logging_layer = { + if let Some(path) = config.path { + let mut appender = LogRollerBuilder::new( + path.clone(), + PathBuf::from(format!("{}.log", filename_prefix)), + ) + .rotation(Rotation::SizeBased(RotationSize::MB(config.max_log_size))) + .max_keep_files(config.max_log_number.try_into().unwrap_or_else(|e| { + eprintln!("Failed to convert max_log_number to u64: {}", e); + 10 + })); + + if config.compression { + appender = appender.compression(Compression::Gzip); } - _ => return Err("Logging format provided is not supported".to_string()), - } - } else { - let stdout_decorator_builder = slog_term::TermDecorator::new(); - let stdout_decorator = if config.log_color { - stdout_decorator_builder.force_color() - } else { - stdout_decorator_builder - } - .build(); - let stdout_decorator = - logging::AlignedTermDecorator::new(stdout_decorator, logging::MAX_MESSAGE_WIDTH); - let stdout_drain = slog_term::FullFormat::new(stdout_decorator); - let stdout_drain = if config.disable_log_timestamp { - stdout_drain.use_custom_timestamp(Self::log_nothing) - } else { - stdout_drain - } - .build() - .fuse(); - slog_async::Async::new(stdout_drain) - .chan_size(LOG_CHANNEL_SIZE) - .build() - }; + match appender.build() { + Ok(file_appender) => { + #[cfg(target_family = "unix")] + set_logfile_permissions(&path, filename_prefix, file_mode); - let stdout_drain = match config.debug_level.as_str() { - "info" => stdout_drain.filter_level(Level::Info), - "debug" => stdout_drain.filter_level(Level::Debug), - "trace" => stdout_drain.filter_level(Level::Trace), - "warn" => stdout_drain.filter_level(Level::Warning), - "error" => stdout_drain.filter_level(Level::Error), - "crit" => stdout_drain.filter_level(Level::Critical), - unknown => return Err(format!("Unknown debug-level: {}", unknown)), - }; + let (file_non_blocking_writer, file_guard) = + tracing_appender::non_blocking(file_appender); - let stdout_logger = Logger::root(stdout_drain.fuse(), o!()); - - // Disable file logging if values set to 0. - if config.max_log_size == 0 || config.max_log_number == 0 { - self.log = Some(stdout_logger); - return Ok(self); - } - - // Disable file logging if no path is specified. - let Some(path) = config.path else { - self.log = Some(stdout_logger); - return Ok(self); - }; - - // Ensure directories are created becfore the logfile. - if !path.exists() { - let mut dir = path.clone(); - dir.pop(); - - // Create the necessary directories for the correct service and network. - if !dir.exists() { - let res = create_dir_all(dir); - - // If the directories cannot be created, warn and disable the logger. - match res { - Ok(_) => (), + LoggingLayer::new( + file_non_blocking_writer, + file_guard, + config.disable_log_timestamp, + false, + config.logfile_color, + config.log_format.clone(), + config.logfile_format.clone(), + config.extra_info, + true, + ) + } Err(e) => { - let log = stdout_logger; - warn!( - log, - "Background file logging is disabled"; - "error" => e); - self.log = Some(log); - return Ok(self); + eprintln!("Failed to initialize rolling file appender: {}", e); + let (sink_writer, sink_guard) = + tracing_appender::non_blocking(std::io::sink()); + LoggingLayer::new( + sink_writer, + sink_guard, + config.disable_log_timestamp, + false, + config.logfile_color, + config.log_format.clone(), + config.logfile_format.clone(), + config.extra_info, + true, + ) } } + } else { + eprintln!("No path provided. File logging is disabled."); + let (sink_writer, sink_guard) = tracing_appender::non_blocking(std::io::sink()); + LoggingLayer::new( + sink_writer, + sink_guard, + config.disable_log_timestamp, + false, + true, + config.log_format.clone(), + config.logfile_format.clone(), + config.extra_info, + true, + ) } - } - - let logfile_level = match config.logfile_debug_level.as_str() { - "info" => Severity::Info, - "debug" => Severity::Debug, - "trace" => Severity::Trace, - "warn" => Severity::Warning, - "error" => Severity::Error, - "crit" => Severity::Critical, - unknown => return Err(format!("Unknown loglevel-debug-level: {}", unknown)), }; - let file_logger = FileLoggerBuilder::new(&path) - .level(logfile_level) - .channel_size(LOG_CHANNEL_SIZE) - .format(match config.logfile_format.as_deref() { - Some("JSON") => Format::Json, - _ => Format::default(), - }) - .rotate_size(config.max_log_size) - .rotate_keep(config.max_log_number) - .rotate_compress(config.compression) - .restrict_permissions(config.is_restricted) - .build() - .map_err(|e| format!("Unable to build file logger: {}", e))?; + let (stdout_non_blocking_writer, stdout_guard) = + tracing_appender::non_blocking(std::io::stdout()); - let mut log = Logger::root(Duplicate::new(stdout_logger, file_logger).fuse(), o!()); - - info!( - log, - "Logging to file"; - "path" => format!("{:?}", path) + let stdout_logging_layer = LoggingLayer::new( + stdout_non_blocking_writer, + stdout_guard, + config.disable_log_timestamp, + config.log_color, + true, + config.log_format, + config.logfile_format, + config.extra_info, + false, ); - // If the http API is enabled, we may need to send logs to be consumed by subscribers. - if config.sse_logging { - let sse_logger = SSELoggingComponents::new(SSE_LOG_CHANNEL_SIZE); - self.sse_logging_components = Some(sse_logger.clone()); + let sse_logging_layer_opt = if config.sse_logging { + Some(SSELoggingComponents::new(SSE_LOG_CHANNEL_SIZE)) + } else { + None + }; - log = Logger::root(Duplicate::new(log, sse_logger).fuse(), o!()); - } + self.sse_logging_components = sse_logging_layer_opt.clone(); - self.log = Some(log); - - Ok(self) + ( + self, + file_logging_layer, + stdout_logging_layer, + sse_logging_layer_opt, + ) } /// Adds a network configuration to the environment. @@ -351,7 +340,6 @@ impl EnvironmentBuilder { signal_rx: Some(signal_rx), signal: Some(signal), exit, - log: self.log.ok_or("Cannot build environment without log")?, sse_logging_components: self.sse_logging_components, eth_spec_instance: self.eth_spec_instance, eth2_config: self.eth2_config, @@ -370,7 +358,6 @@ pub struct Environment { signal_tx: Sender, signal: Option>, exit: async_channel::Receiver<()>, - log: Logger, sse_logging_components: Option, eth_spec_instance: E, pub eth2_config: Eth2Config, @@ -386,14 +373,14 @@ impl Environment { &self.runtime } - /// Returns a `Context` where no "service" has been added to the logger output. + /// Returns a `Context` where a "core" service has been added to the logger output. pub fn core_context(&self) -> RuntimeContext { RuntimeContext { executor: TaskExecutor::new( Arc::downgrade(self.runtime()), self.exit.clone(), - self.log.clone(), self.signal_tx.clone(), + "core".to_string(), ), eth_spec_instance: self.eth_spec_instance.clone(), eth2_config: self.eth2_config.clone(), @@ -408,8 +395,8 @@ impl Environment { executor: TaskExecutor::new( Arc::downgrade(self.runtime()), self.exit.clone(), - self.log.new(o!("service" => service_name)), self.signal_tx.clone(), + service_name, ), eth_spec_instance: self.eth_spec_instance.clone(), eth2_config: self.eth2_config.clone(), @@ -441,7 +428,7 @@ impl Environment { let terminate = SignalFuture::new(terminate_stream, "Received SIGTERM"); handles.push(terminate); } - Err(e) => error!(self.log, "Could not register SIGTERM handler"; "error" => e), + Err(e) => error!(error = ?e, "Could not register SIGTERM handler"), }; // setup for handling SIGINT @@ -450,7 +437,7 @@ impl Environment { let interrupt = SignalFuture::new(interrupt_stream, "Received SIGINT"); handles.push(interrupt); } - Err(e) => error!(self.log, "Could not register SIGINT handler"; "error" => e), + Err(e) => error!(error = ?e, "Could not register SIGINT handler"), } // setup for handling a SIGHUP @@ -459,7 +446,7 @@ impl Environment { let hup = SignalFuture::new(hup_stream, "Received SIGHUP"); handles.push(hup); } - Err(e) => error!(self.log, "Could not register SIGHUP handler"; "error" => e), + Err(e) => error!(error = ?e, "Could not register SIGHUP handler"), } future::select(inner_shutdown, future::select_all(handles.into_iter())).await @@ -467,7 +454,7 @@ impl Environment { match self.runtime().block_on(register_handlers) { future::Either::Left((Ok(reason), _)) => { - info!(self.log, "Internal shutdown received"; "reason" => reason.message()); + info!("Internal shutdown received"); Ok(reason) } future::Either::Left((Err(e), _)) => Err(e.into()), @@ -494,14 +481,12 @@ impl Environment { // setup for handling a Ctrl-C let (ctrlc_send, ctrlc_oneshot) = oneshot::channel(); let ctrlc_send_c = RefCell::new(Some(ctrlc_send)); - let log = self.log.clone(); ctrlc::set_handler(move || { if let Some(ctrlc_send) = ctrlc_send_c.try_borrow_mut().unwrap().take() { if let Err(e) = ctrlc_send.send(()) { error!( - log, - "Error sending ctrl-c message"; - "error" => e + error = ?e, + "Error sending ctrl-c message" ); } } @@ -514,7 +499,7 @@ impl Environment { .block_on(future::select(inner_shutdown, ctrlc_oneshot)) { future::Either::Left((Ok(reason), _)) => { - info!(self.log, "Internal shutdown received"; "reason" => reason.message()); + info!(reason = reason.message(), "Internal shutdown received"); Ok(reason) } future::Either::Left((Err(e), _)) => Err(e.into()), @@ -531,9 +516,8 @@ impl Environment { runtime.shutdown_timeout(std::time::Duration::from_secs(MAXIMUM_SHUTDOWN_TIME)) } Err(e) => warn!( - self.log, - "Failed to obtain runtime access to shutdown gracefully"; - "error" => ?e + error = ?e, + "Failed to obtain runtime access to shutdown gracefully" ), } } @@ -579,3 +563,37 @@ impl Future for SignalFuture { } } } + +#[cfg(target_family = "unix")] +fn set_logfile_permissions(log_dir: &Path, filename_prefix: &str, file_mode: u32) { + let newest = read_dir(log_dir) + .ok() + .into_iter() + .flat_map(|entries| entries.filter_map(Result::ok)) + .filter_map(|entry| { + let path = entry.path(); + let fname = path.file_name()?.to_string_lossy(); + if path.is_file() && fname.starts_with(filename_prefix) && fname.ends_with(".log") { + let modified = entry.metadata().ok()?.modified().ok()?; + Some((path, modified)) + } else { + None + } + }) + .max_by_key(|(_path, mtime)| *mtime); + + match newest { + Some((file, _mtime)) => { + if let Err(e) = set_permissions(&file, Permissions::from_mode(file_mode)) { + eprintln!("Failed to set permissions on {}: {}", file.display(), e); + } + } + None => { + eprintln!( + "Couldn't find a newly created logfile in {} matching prefix \"{}\".", + log_dir.display(), + filename_prefix + ); + } + } +} diff --git a/lighthouse/environment/src/tracing_common.rs b/lighthouse/environment/src/tracing_common.rs new file mode 100644 index 0000000000..ad9060a8ff --- /dev/null +++ b/lighthouse/environment/src/tracing_common.rs @@ -0,0 +1,78 @@ +use crate::{EnvironmentBuilder, LoggerConfig}; +use clap::ArgMatches; +use logging::Libp2pDiscv5TracingLayer; +use logging::{tracing_logging_layer::LoggingLayer, SSELoggingComponents}; +use std::process; +use tracing_subscriber::filter::{EnvFilter, FilterFn, LevelFilter}; +use types::EthSpec; + +pub fn construct_logger( + logger_config: LoggerConfig, + matches: &ArgMatches, + environment_builder: EnvironmentBuilder, +) -> ( + EnvironmentBuilder, + EnvFilter, + Libp2pDiscv5TracingLayer, + LoggingLayer, + LoggingLayer, + Option, + LoggerConfig, + FilterFn, +) { + let libp2p_discv5_layer = logging::create_libp2p_discv5_tracing_layer( + logger_config.path.clone(), + logger_config.max_log_size, + logger_config.compression, + logger_config.max_log_number, + ); + + let logfile_prefix = matches.subcommand_name().unwrap_or("lighthouse"); + + let (builder, file_logging_layer, stdout_logging_layer, sse_logging_layer_opt) = + environment_builder.init_tracing(logger_config.clone(), logfile_prefix); + + let filter_layer = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new(logger_config.debug_level.to_string().to_lowercase())) + .unwrap(); + + let dependency_log_filter = + FilterFn::new(filter_dependency_log as fn(&tracing::Metadata<'_>) -> bool); + + ( + builder, + filter_layer, + libp2p_discv5_layer, + file_logging_layer, + stdout_logging_layer, + sse_logging_layer_opt, + logger_config, + dependency_log_filter, + ) +} + +pub fn parse_level(level: &str) -> LevelFilter { + match level.to_lowercase().as_str() { + "error" => LevelFilter::ERROR, + "warn" => LevelFilter::WARN, + "info" => LevelFilter::INFO, + "debug" => LevelFilter::DEBUG, + "trace" => LevelFilter::TRACE, + _ => { + eprintln!("Unsupported log level"); + process::exit(1) + } + } +} + +fn filter_dependency_log(meta: &tracing::Metadata<'_>) -> bool { + if let Some(file) = meta.file() { + let target = meta.target(); + if file.contains("/.cargo/") { + return target.contains("discv5") || target.contains("libp2p"); + } else { + return !file.contains("gossipsub") && !target.contains("hyper"); + } + } + true +} diff --git a/lighthouse/environment/tests/environment_builder.rs b/lighthouse/environment/tests/environment_builder.rs index b0c847612a..a98caf8df5 100644 --- a/lighthouse/environment/tests/environment_builder.rs +++ b/lighthouse/environment/tests/environment_builder.rs @@ -9,8 +9,6 @@ fn builder() -> EnvironmentBuilder { EnvironmentBuilder::mainnet() .multi_threaded_tokio_runtime() .expect("should set runtime") - .test_logger() - .expect("should set logger") } fn eth2_network_config() -> Option { diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index d7a14e3809..8df4831503 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -11,18 +11,23 @@ use clap_utils::{ }; use cli::LighthouseSubcommands; use directory::{parse_path_or_default, DEFAULT_BEACON_NODE_DIR, DEFAULT_VALIDATOR_DIR}; +use environment::tracing_common; use environment::{EnvironmentBuilder, LoggerConfig}; use eth2_network_config::{Eth2NetworkConfig, DEFAULT_HARDCODED_NETWORK, HARDCODED_NET_NAMES}; use ethereum_hashing::have_sha_extensions; use futures::TryFutureExt; use lighthouse_version::VERSION; +use logging::crit; +use logging::MetricsLayer; use malloc_utils::configure_memory_allocator; -use slog::{crit, info}; use std::backtrace::Backtrace; use std::path::PathBuf; use std::process::exit; use std::sync::LazyLock; use task_executor::ShutdownReason; +use tracing::{info, warn}; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use types::{EthSpec, EthSpecId}; use validator_client::ProductionValidatorClient; @@ -120,15 +125,11 @@ fn main() { .display_order(0), ) .arg( - Arg::new("logfile") - .long("logfile") - .value_name("FILE") + Arg::new("logfile-dir") + .long("logfile-dir") + .value_name("DIR") .help( - "File path where the log file will be stored. Once it grows to the \ - value specified in `--logfile-max-size` a new log file is generated where \ - future logs are stored. \ - Once the number of log files exceeds the value specified in \ - `--logfile-max-number` the oldest log file will be overwritten.") + "Directory path where the log file will be stored") .action(ArgAction::Set) .global(true) .display_order(0) @@ -215,13 +216,36 @@ fn main() { .arg( Arg::new("log-color") .long("log-color") - .alias("log-colour") - .help("Force outputting colors when emitting logs to the terminal.") + .alias("log-color") + .help("Enables/Disables colors for logs in terminal. \ + Set it to false to disable colors.") + .num_args(0..=1) + .default_missing_value("true") + .default_value("true") + .value_parser(clap::value_parser!(bool)) + .help_heading(FLAG_HEADER) + .global(true) + .display_order(0) + ) + .arg( + Arg::new("logfile-color") + .long("logfile-color") + .alias("logfile-colour") + .help("Enables colors in logfile.") .action(ArgAction::SetTrue) .help_heading(FLAG_HEADER) .global(true) .display_order(0) ) + .arg( + Arg::new("log-extra-info") + .long("log-extra-info") + .action(ArgAction::SetTrue) + .help_heading(FLAG_HEADER) + .help("If present, show module,file,line in logs") + .global(true) + .display_order(0) + ) .arg( Arg::new("disable-log-timestamp") .long("disable-log-timestamp") @@ -499,10 +523,16 @@ fn run( let log_format = matches.get_one::("log-format"); - let log_color = matches.get_flag("log-color"); + let log_color = matches + .get_one::("log-color") + .copied() + .unwrap_or(true); + + let logfile_color = matches.get_flag("logfile-color"); let disable_log_timestamp = matches.get_flag("disable-log-timestamp"); + let extra_info = matches.get_flag("log-extra-info"); let logfile_debug_level = matches .get_one::("logfile-debug-level") .ok_or("Expected --logfile-debug-level flag")?; @@ -529,15 +559,13 @@ fn run( let logfile_restricted = !matches.get_flag("logfile-no-restricted-perms"); // Construct the path to the log file. - let mut log_path: Option = clap_utils::parse_optional(matches, "logfile")?; + let mut log_path: Option = clap_utils::parse_optional(matches, "logfile-dir")?; if log_path.is_none() { log_path = match matches.subcommand() { Some(("beacon_node", _)) => Some( parse_path_or_default(matches, "datadir")? .join(DEFAULT_BEACON_NODE_DIR) - .join("logs") - .join("beacon") - .with_extension("log"), + .join("logs"), ), Some(("validator_client", vc_matches)) => { let base_path = if vc_matches.contains_id("validators-dir") { @@ -546,12 +574,7 @@ fn run( parse_path_or_default(matches, "datadir")?.join(DEFAULT_VALIDATOR_DIR) }; - Some( - base_path - .join("logs") - .join("validator") - .with_extension("log"), - ) + Some(base_path.join("logs")) } _ => None, }; @@ -567,57 +590,83 @@ fn run( } }; - let logger_config = LoggerConfig { - path: log_path.clone(), - debug_level: String::from(debug_level), - logfile_debug_level: String::from(logfile_debug_level), - log_format: log_format.map(String::from), - logfile_format: logfile_format.map(String::from), - log_color, - disable_log_timestamp, - max_log_size: logfile_max_size * 1_024 * 1_024, - max_log_number: logfile_max_number, - compression: logfile_compress, - is_restricted: logfile_restricted, - sse_logging, + let ( + builder, + filter_layer, + libp2p_discv5_layer, + file_logging_layer, + stdout_logging_layer, + sse_logging_layer_opt, + logger_config, + dependency_log_filter, + ) = tracing_common::construct_logger( + LoggerConfig { + path: log_path.clone(), + debug_level: tracing_common::parse_level(debug_level), + logfile_debug_level: tracing_common::parse_level(logfile_debug_level), + log_format: log_format.map(String::from), + logfile_format: logfile_format.map(String::from), + log_color, + logfile_color, + disable_log_timestamp, + max_log_size: logfile_max_size, + max_log_number: logfile_max_number, + compression: logfile_compress, + is_restricted: logfile_restricted, + sse_logging, + extra_info, + }, + matches, + environment_builder, + ); + + let logging = tracing_subscriber::registry() + .with(dependency_log_filter) + .with(filter_layer) + .with(file_logging_layer.with_filter(logger_config.logfile_debug_level)) + .with(stdout_logging_layer.with_filter(logger_config.debug_level)) + .with(MetricsLayer) + .with(libp2p_discv5_layer); + + let logging_result = if let Some(sse_logging_layer) = sse_logging_layer_opt { + logging.with(sse_logging_layer).try_init() + } else { + logging.try_init() }; - let builder = environment_builder.initialize_logger(logger_config.clone())?; + if let Err(e) = logging_result { + eprintln!("Failed to initialize dependency logging: {e}"); + } let mut environment = builder .multi_threaded_tokio_runtime()? .eth2_network_config(eth2_network_config)? .build()?; - let log = environment.core_context().log().clone(); - // Log panics properly. { - let log = log.clone(); std::panic::set_hook(Box::new(move |info| { crit!( - log, - "Task panic. This is a bug!"; - "location" => info.location().map(ToString::to_string), - "message" => info.payload().downcast_ref::(), - "backtrace" => %Backtrace::capture(), - "advice" => "Please check above for a backtrace and notify the developers", + location = info.location().map(ToString::to_string), + message = info.payload().downcast_ref::(), + backtrace = %Backtrace::capture(), + advice = "Please check above for a backtrace and notify the developers", + "Task panic. This is a bug!" ); })); } // Allow Prometheus to export the time at which the process was started. - metrics::expose_process_start_time(&log); + metrics::expose_process_start_time(); // Allow Prometheus access to the version and commit of the Lighthouse build. metrics::expose_lighthouse_version(); #[cfg(all(feature = "modern", target_arch = "x86_64"))] if !std::is_x86_feature_detected!("adx") { - slog::warn!( - log, - "CPU seems incompatible with optimized Lighthouse build"; - "advice" => "If you get a SIGILL, please try Lighthouse portable build" + tracing::warn!( + advice = "If you get a SIGILL, please try Lighthouse portable build", + "CPU seems incompatible with optimized Lighthouse build" ); } @@ -631,7 +680,7 @@ fn run( ]; for flag in deprecated_flags { if matches.get_one::(flag).is_some() { - slog::warn!(log, "The {} flag is deprecated and does nothing", flag); + warn!("The {} flag is deprecated and does nothing", flag); } } @@ -675,26 +724,21 @@ fn run( match LighthouseSubcommands::from_arg_matches(matches) { Ok(LighthouseSubcommands::DatabaseManager(db_manager_config)) => { - info!(log, "Running database manager for {} network", network_name); + info!("Running database manager for {} network", network_name); database_manager::run(matches, &db_manager_config, environment)?; return Ok(()); } Ok(LighthouseSubcommands::ValidatorClient(validator_client_config)) => { let context = environment.core_context(); - let log = context.log().clone(); let executor = context.executor.clone(); - let config = validator_client::Config::from_cli( - matches, - &validator_client_config, - context.log(), - ) - .map_err(|e| format!("Unable to initialize validator config: {}", e))?; + let config = validator_client::Config::from_cli(matches, &validator_client_config) + .map_err(|e| format!("Unable to initialize validator config: {}", e))?; // Dump configs if `dump-config` or `dump-chain-config` flags are set clap_utils::check_dump_configs::<_, E>(matches, &config, &context.eth2_config.spec)?; let shutdown_flag = matches.get_flag("immediate-shutdown"); if shutdown_flag { - info!(log, "Validator client immediate shutdown triggered."); + info!("Validator client immediate shutdown triggered."); return Ok(()); } @@ -704,7 +748,7 @@ fn run( .and_then(|mut vc| async move { vc.start_service().await }) .await { - crit!(log, "Failed to start validator client"; "reason" => e); + crit!(reason = e, "Failed to start validator client"); // Ignore the error since it always occurs during normal operation when // shutting down. let _ = executor @@ -718,17 +762,12 @@ fn run( Err(_) => (), }; - info!(log, "Lighthouse started"; "version" => VERSION); - info!( - log, - "Configured for network"; - "name" => &network_name - ); + info!(version = VERSION, "Lighthouse started"); + info!(network_name, "Configured network"); match matches.subcommand() { Some(("beacon_node", matches)) => { let context = environment.core_context(); - let log = context.log().clone(); let executor = context.executor.clone(); let mut config = beacon_node::get_config::(matches, &context)?; config.logger_config = logger_config; @@ -737,29 +776,14 @@ fn run( let shutdown_flag = matches.get_flag("immediate-shutdown"); if shutdown_flag { - info!(log, "Beacon node immediate shutdown triggered."); + info!("Beacon node immediate shutdown triggered."); return Ok(()); } - let mut tracing_log_path: Option = - clap_utils::parse_optional(matches, "logfile")?; - - if tracing_log_path.is_none() { - tracing_log_path = Some( - parse_path_or_default(matches, "datadir")? - .join(DEFAULT_BEACON_NODE_DIR) - .join("logs"), - ) - } - - let path = tracing_log_path.clone().unwrap(); - - logging::create_tracing_layer(path); - executor.clone().spawn( async move { if let Err(e) = ProductionBeaconNode::new(context.clone(), config).await { - crit!(log, "Failed to start beacon node"; "reason" => e); + crit!(reason = ?e, "Failed to start beacon node"); // Ignore the error since it always occurs during normal operation when // shutting down. let _ = executor @@ -774,14 +798,14 @@ fn run( // Qt the moment this needs to exist so that we dont trigger a crit. Some(("validator_client", _)) => (), _ => { - crit!(log, "No subcommand supplied. See --help ."); + crit!("No subcommand supplied. See --help ."); return Err("No subcommand supplied.".into()); } }; // Block this thread until we get a ctrl-c or a task sends a shutdown signal. let shutdown_reason = environment.block_until_shutdown_requested()?; - info!(log, "Shutting down.."; "reason" => ?shutdown_reason); + info!(reason = ?shutdown_reason, "Shutting down.."); environment.fire_signal(); diff --git a/lighthouse/src/metrics.rs b/lighthouse/src/metrics.rs index 30e0120582..6b464a18be 100644 --- a/lighthouse/src/metrics.rs +++ b/lighthouse/src/metrics.rs @@ -1,8 +1,8 @@ use lighthouse_version::VERSION; pub use metrics::*; -use slog::{error, Logger}; use std::sync::LazyLock; use std::time::{SystemTime, UNIX_EPOCH}; +use tracing::error; pub static PROCESS_START_TIME_SECONDS: LazyLock> = LazyLock::new(|| { try_create_int_gauge( @@ -19,13 +19,12 @@ pub static LIGHTHOUSE_VERSION: LazyLock> = LazyLock::new(|| ) }); -pub fn expose_process_start_time(log: &Logger) { +pub fn expose_process_start_time() { match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(duration) => set_gauge(&PROCESS_START_TIME_SECONDS, duration.as_secs() as i64), Err(e) => error!( - log, - "Failed to read system time"; - "error" => %e + error = %e, + "Failed to read system time" ), } } diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 03314930b9..96c1b5313a 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -1,7 +1,7 @@ use crate::exec::{CommandLineTestExec, CompletedTest}; use beacon_node::beacon_chain::chain_config::{ DisallowedReOrgOffsets, DEFAULT_RE_ORG_CUTOFF_DENOMINATOR, DEFAULT_RE_ORG_HEAD_THRESHOLD, - DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, + DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION, DEFAULT_SYNC_TOLERANCE_EPOCHS, }; use beacon_node::{ beacon_chain::graffiti_calculator::GraffitiOrigin, @@ -720,6 +720,40 @@ fn builder_user_agent() { ); } +#[test] +fn test_builder_disable_ssz_flag() { + run_payload_builder_flag_test_with_config( + "builder", + "http://meow.cats", + None, + None, + |config| { + assert!( + !config + .execution_layer + .as_ref() + .unwrap() + .disable_builder_ssz_requests, + ); + }, + ); + run_payload_builder_flag_test_with_config( + "builder", + "http://meow.cats", + Some("builder-disable-ssz"), + None, + |config| { + assert!( + config + .execution_layer + .as_ref() + .unwrap() + .disable_builder_ssz_requests, + ); + }, + ); +} + fn run_jwt_optional_flags_test(jwt_flag: &str, jwt_id_flag: &str, jwt_version_flag: &str) { use sensitive_url::SensitiveUrl; @@ -2382,20 +2416,20 @@ fn monitoring_endpoint() { // Tests for Logger flags. #[test] -fn default_log_color_flag() { +fn default_logfile_color_flag() { CommandLineTest::new() .run_with_zero_port() .with_config(|config| { - assert!(!config.logger_config.log_color); + assert!(!config.logger_config.logfile_color); }); } #[test] -fn enabled_log_color_flag() { +fn enabled_logfile_color_flag() { CommandLineTest::new() - .flag("log-color", None) + .flag("logfile-color", None) .run_with_zero_port() .with_config(|config| { - assert!(config.logger_config.log_color); + assert!(config.logger_config.logfile_color); }); } #[test] @@ -2506,7 +2540,6 @@ fn light_client_server_default() { .with_config(|config| { assert!(config.network.enable_light_client_server); assert!(config.chain.enable_light_client_server); - assert!(config.http_api.enable_light_client_server); }); } @@ -2539,12 +2572,35 @@ fn light_client_http_server_disabled() { .flag("disable-light-client-server", None) .run_with_zero_port() .with_config(|config| { - assert!(!config.http_api.enable_light_client_server); assert!(!config.network.enable_light_client_server); assert!(!config.chain.enable_light_client_server); }); } +#[test] +fn sync_tolerance_epochs() { + CommandLineTest::new() + .flag("http", None) + .flag("sync-tolerance-epochs", Some("0")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.chain.sync_tolerance_epochs, 0); + }); +} + +#[test] +fn sync_tolerance_epochs_default() { + CommandLineTest::new() + .flag("http", None) + .run_with_zero_port() + .with_config(|config| { + assert_eq!( + config.chain.sync_tolerance_epochs, + DEFAULT_SYNC_TOLERANCE_EPOCHS + ); + }); +} + #[test] fn gui_flag() { CommandLineTest::new() @@ -2715,3 +2771,29 @@ fn beacon_node_backend_override() { assert_eq!(config.store.backend, BeaconNodeBackend::LevelDb); }); } + +#[test] +fn block_publishing_delay_for_testing() { + CommandLineTest::new() + .flag("delay-block-publishing", Some("2.5")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!( + config.chain.block_publishing_delay, + Some(Duration::from_secs_f64(2.5f64)) + ); + }); +} + +#[test] +fn data_column_publishing_delay_for_testing() { + CommandLineTest::new() + .flag("delay-data-column-publishing", Some("3.5")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!( + config.chain.data_column_publishing_delay, + Some(Duration::from_secs_f64(3.5f64)) + ); + }); +} diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index f28e7d9829..eccd97d486 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -129,6 +129,22 @@ fn use_long_timeouts_flag() { .with_config(|config| assert!(config.use_long_timeouts)); } +#[test] +fn long_timeouts_multiplier_flag_default() { + CommandLineTest::new() + .run() + .with_config(|config| assert_eq!(config.long_timeouts_multiplier, 1)); +} + +#[test] +fn long_timeouts_multiplier_flag() { + CommandLineTest::new() + .flag("use-long-timeouts", None) + .flag("long-timeouts-multiplier", Some("10")) + .run() + .with_config(|config| assert_eq!(config.long_timeouts_multiplier, 10)); +} + #[test] fn beacon_nodes_tls_certs_flag() { let dir = TempDir::new().expect("Unable to create temporary directory"); diff --git a/scripts/local_testnet/network_params.yaml b/scripts/local_testnet/network_params.yaml index b53d88e52c..87ffeb8d22 100644 --- a/scripts/local_testnet/network_params.yaml +++ b/scripts/local_testnet/network_params.yaml @@ -14,4 +14,5 @@ global_log_level: debug snooper_enabled: false additional_services: - dora + - spamoor_blob - prometheus_grafana diff --git a/scripts/local_testnet/network_params_das.yaml b/scripts/local_testnet/network_params_das.yaml index 030aa2b820..80b4bc95c6 100644 --- a/scripts/local_testnet/network_params_das.yaml +++ b/scripts/local_testnet/network_params_das.yaml @@ -4,11 +4,15 @@ participants: cl_extra_params: - --subscribe-all-data-column-subnets - --subscribe-all-subnets + # Note: useful for testing range sync (only produce block if node is in sync to prevent forking) + - --sync-tolerance-epochs=0 - --target-peers=3 count: 2 - cl_type: lighthouse cl_image: lighthouse:local cl_extra_params: + # Note: useful for testing range sync (only produce block if node is in sync to prevent forking) + - --sync-tolerance-epochs=0 - --target-peers=3 count: 2 network_params: diff --git a/scripts/tests/doppelganger_protection.sh b/scripts/tests/doppelganger_protection.sh index 5be5c13dee..80070a0791 100755 --- a/scripts/tests/doppelganger_protection.sh +++ b/scripts/tests/doppelganger_protection.sh @@ -78,7 +78,7 @@ if [[ "$BEHAVIOR" == "failure" ]]; then --files /validator_keys:$vc_1_keys_artifact_id,/testnet:el_cl_genesis_data \ $ENCLAVE_NAME $service_name $LH_IMAGE_NAME -- lighthouse \ vc \ - --debug-level debug \ + --debug-level info \ --testnet-dir=/testnet \ --validators-dir=/validator_keys/keys \ --secrets-dir=/validator_keys/secrets \ diff --git a/slasher/Cargo.toml b/slasher/Cargo.toml index fcecc2fc23..b2f6eca9c3 100644 --- a/slasher/Cargo.toml +++ b/slasher/Cargo.toml @@ -32,16 +32,14 @@ rand = { workspace = true } redb = { version = "2.1.4", optional = true } safe_arith = { workspace = true } serde = { workspace = true } -slog = { workspace = true } ssz_types = { workspace = true } strum = { workspace = true } +tracing = { workspace = true } tree_hash = { workspace = true } tree_hash_derive = { workspace = true } types = { workspace = true } [dev-dependencies] -logging = { workspace = true } maplit = { workspace = true } rayon = { workspace = true } tempfile = { workspace = true } - diff --git a/slasher/service/Cargo.toml b/slasher/service/Cargo.toml index 41e3b5b90a..19398fada8 100644 --- a/slasher/service/Cargo.toml +++ b/slasher/service/Cargo.toml @@ -10,9 +10,9 @@ directory = { workspace = true } lighthouse_network = { workspace = true } network = { workspace = true } slasher = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } state_processing = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } types = { workspace = true } diff --git a/slasher/service/src/service.rs b/slasher/service/src/service.rs index 091a95dc4c..2409a24c78 100644 --- a/slasher/service/src/service.rs +++ b/slasher/service/src/service.rs @@ -8,7 +8,6 @@ use slasher::{ metrics::{self, SLASHER_DATABASE_SIZE, SLASHER_RUN_TIME}, Slasher, }; -use slog::{debug, error, info, trace, warn, Logger}; use slot_clock::SlotClock; use state_processing::{ per_block_processing::errors::{ @@ -21,6 +20,7 @@ use std::sync::Arc; use task_executor::TaskExecutor; use tokio::sync::mpsc::UnboundedSender; use tokio::time::{interval_at, Duration, Instant}; +use tracing::{debug, error, info, info_span, trace, warn, Instrument}; use types::{AttesterSlashing, Epoch, EthSpec, ProposerSlashing}; pub struct SlasherService { @@ -47,9 +47,8 @@ impl SlasherService { .slasher .clone() .ok_or("No slasher is configured")?; - let log = slasher.log().clone(); - info!(log, "Starting slasher"; "broadcast" => slasher.config().broadcast); + info!(broadcast = slasher.config().broadcast, "Starting slasher"); // Buffer just a single message in the channel. If the receiver is still processing, we // don't need to burden them with more work (we can wait). @@ -65,13 +64,17 @@ impl SlasherService { update_period, slot_offset, notif_sender, - log, - ), + ) + .instrument(tracing::info_span!("slasher", service = "slasher")), "slasher_server_notifier", ); executor.spawn_blocking( - || Self::run_processor(beacon_chain, slasher, notif_receiver, network_sender), + || { + let span = info_span!("slasher", service = "slasher"); + let _ = span.enter(); + Self::run_processor(beacon_chain, slasher, notif_receiver, network_sender); + }, "slasher_server_processor", ); @@ -84,14 +87,13 @@ impl SlasherService { update_period: u64, slot_offset: f64, notif_sender: SyncSender, - log: Logger, ) { let slot_offset = Duration::from_secs_f64(slot_offset); let start_instant = if let Some(duration_to_next_slot) = beacon_chain.slot_clock.duration_to_next_slot() { Instant::now() + duration_to_next_slot + slot_offset } else { - error!(log, "Error aligning slasher to slot clock"); + error!("Error aligning slasher to slot clock"); Instant::now() }; let mut interval = interval_at(start_instant, Duration::from_secs(update_period)); @@ -104,7 +106,7 @@ impl SlasherService { break; } } else { - trace!(log, "Slasher has nothing to do: we are pre-genesis"); + trace!("Slasher has nothing to do: we are pre-genesis"); } } } @@ -116,7 +118,6 @@ impl SlasherService { notif_receiver: Receiver, network_sender: UnboundedSender>, ) { - let log = slasher.log(); while let Ok(current_epoch) = notif_receiver.recv() { let t = Instant::now(); @@ -125,10 +126,9 @@ impl SlasherService { Ok(stats) => Some(stats), Err(e) => { error!( - log, - "Error during scheduled slasher processing"; - "epoch" => current_epoch, - "error" => ?e, + epoch = %current_epoch, + error = ?e, + "Error during scheduled slasher processing" ); None } @@ -139,10 +139,9 @@ impl SlasherService { // If the database is full then pruning could help to free it up. if let Err(e) = slasher.prune_database(current_epoch) { error!( - log, - "Error during slasher database pruning"; - "epoch" => current_epoch, - "error" => ?e, + epoch = %current_epoch, + error = ?e, + "Error during slasher database pruning" ); continue; }; @@ -155,12 +154,11 @@ impl SlasherService { if let Some(stats) = stats { debug!( - log, - "Completed slasher update"; - "epoch" => current_epoch, - "time_taken" => format!("{}ms", t.elapsed().as_millis()), - "num_attestations" => stats.attestation_stats.num_processed, - "num_blocks" => stats.block_stats.num_processed, + epoch = %current_epoch, + time_taken = format!("{}ms", t.elapsed().as_millis()), + num_attestations = stats.attestation_stats.num_processed, + num_blocks = stats.block_stats.num_processed, + "Completed slasher update" ); } } @@ -181,7 +179,6 @@ impl SlasherService { slasher: &Slasher, network_sender: &UnboundedSender>, ) { - let log = slasher.log(); let attester_slashings = slasher.get_attester_slashings(); for slashing in attester_slashings { @@ -198,18 +195,16 @@ impl SlasherService { BlockOperationError::Invalid(AttesterSlashingInvalid::NoSlashableIndices), )) => { debug!( - log, - "Skipping attester slashing for slashed validators"; - "slashing" => ?slashing, + ?slashing, + "Skipping attester slashing for slashed validators" ); continue; } Err(e) => { warn!( - log, - "Attester slashing produced is invalid"; - "error" => ?e, - "slashing" => ?slashing, + error = ?e, + ?slashing, + "Attester slashing produced is invalid" ); continue; } @@ -224,9 +219,8 @@ impl SlasherService { Self::publish_attester_slashing(beacon_chain, network_sender, slashing) { debug!( - log, - "Unable to publish attester slashing"; - "error" => e, + error = ?e, + "Unable to publish attester slashing" ); } } @@ -238,7 +232,6 @@ impl SlasherService { slasher: &Slasher, network_sender: &UnboundedSender>, ) { - let log = slasher.log(); let proposer_slashings = slasher.get_proposer_slashings(); for slashing in proposer_slashings { @@ -254,18 +247,16 @@ impl SlasherService { )), )) => { debug!( - log, - "Skipping proposer slashing for slashed validator"; - "validator_index" => index, + validator_index = index, + "Skipping proposer slashing for slashed validator" ); continue; } Err(e) => { error!( - log, - "Proposer slashing produced is invalid"; - "error" => ?e, - "slashing" => ?slashing, + error = ?e, + ?slashing, + "Proposer slashing produced is invalid" ); continue; } @@ -277,9 +268,8 @@ impl SlasherService { Self::publish_proposer_slashing(beacon_chain, network_sender, slashing) { debug!( - log, - "Unable to publish proposer slashing"; - "error" => e, + error = ?e, + "Unable to publish proposer slashing" ); } } diff --git a/slasher/src/database.rs b/slasher/src/database.rs index e2b49dca29..071109e00c 100644 --- a/slasher/src/database.rs +++ b/slasher/src/database.rs @@ -12,12 +12,12 @@ use interface::{Environment, OpenDatabases, RwTransaction}; use lru::LruCache; use parking_lot::Mutex; use serde::de::DeserializeOwned; -use slog::{info, Logger}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use std::borrow::{Borrow, Cow}; use std::marker::PhantomData; use std::sync::Arc; +use tracing::info; use tree_hash::TreeHash; use types::{ AggregateSignature, AttestationData, ChainSpec, Epoch, EthSpec, Hash256, IndexedAttestation, @@ -287,8 +287,8 @@ fn ssz_decode(bytes: Cow<[u8]>) -> Result { } impl SlasherDB { - pub fn open(config: Arc, spec: Arc, log: Logger) -> Result { - info!(log, "Opening slasher database"; "backend" => %config.backend); + pub fn open(config: Arc, spec: Arc) -> Result { + info!(backend = %config.backend, "Opening slasher database"); std::fs::create_dir_all(&config.database_path)?; @@ -665,7 +665,7 @@ impl SlasherDB { target: Epoch, prev_max_target: Option, ) -> Result, Error> { - if prev_max_target.map_or(true, |prev_max| target > prev_max) { + if prev_max_target.is_none_or(|prev_max| target > prev_max) { return Ok(None); } diff --git a/slasher/src/slasher.rs b/slasher/src/slasher.rs index 19f2cd138d..12f35e657e 100644 --- a/slasher/src/slasher.rs +++ b/slasher/src/slasher.rs @@ -9,9 +9,9 @@ use crate::{ IndexedAttestationId, ProposerSlashingStatus, RwTransaction, SimpleBatch, SlasherDB, }; use parking_lot::Mutex; -use slog::{debug, error, info, Logger}; use std::collections::HashSet; use std::sync::Arc; +use tracing::{debug, error, info}; use types::{ AttesterSlashing, ChainSpec, Epoch, EthSpec, IndexedAttestation, ProposerSlashing, SignedBeaconBlockHeader, @@ -25,26 +25,21 @@ pub struct Slasher { attester_slashings: Mutex>>, proposer_slashings: Mutex>, config: Arc, - log: Logger, } impl Slasher { - pub fn open(config: Config, spec: Arc, log: Logger) -> Result { + pub fn open(config: Config, spec: Arc) -> Result { config.validate()?; let config = Arc::new(config); - let db = SlasherDB::open(config.clone(), spec, log.clone())?; - Self::from_config_and_db(config, db, log) + let db = SlasherDB::open(config.clone(), spec)?; + Self::from_config_and_db(config, db) } /// TESTING ONLY. /// /// Initialise a slasher database from an existing `db`. The caller must ensure that the /// database's config matches the one provided. - pub fn from_config_and_db( - config: Arc, - db: SlasherDB, - log: Logger, - ) -> Result { + pub fn from_config_and_db(config: Arc, db: SlasherDB) -> Result { config.validate()?; let attester_slashings = Mutex::new(HashSet::new()); let proposer_slashings = Mutex::new(HashSet::new()); @@ -57,7 +52,6 @@ impl Slasher { attester_slashings, proposer_slashings, config, - log, }) } @@ -80,10 +74,6 @@ impl Slasher { &self.config } - pub fn log(&self) -> &Logger { - &self.log - } - /// Accept an attestation from the network and queue it for processing. pub fn accept_attestation(&self, attestation: IndexedAttestation) { self.attestation_queue.queue(attestation); @@ -126,11 +116,7 @@ impl Slasher { let num_slashings = slashings.len(); if !slashings.is_empty() { - info!( - self.log, - "Found {} new proposer slashings!", - slashings.len(), - ); + info!("Found {} new proposer slashings!", slashings.len()); self.proposer_slashings.lock().extend(slashings); } @@ -156,11 +142,10 @@ impl Slasher { self.attestation_queue.requeue(deferred); debug!( - self.log, - "Pre-processing attestations for slasher"; - "num_valid" => num_valid, - "num_deferred" => num_deferred, - "num_dropped" => num_dropped, + %num_valid, + num_deferred, + num_dropped, + "Pre-processing attestations for slasher" ); metrics::set_gauge(&SLASHER_NUM_ATTESTATIONS_VALID, num_valid as i64); metrics::set_gauge(&SLASHER_NUM_ATTESTATIONS_DEFERRED, num_deferred as i64); @@ -194,12 +179,7 @@ impl Slasher { } } - debug!( - self.log, - "Stored attestations in slasher DB"; - "num_stored" => num_stored, - "num_valid" => num_valid, - ); + debug!(num_stored, ?num_valid, "Stored attestations in slasher DB"); metrics::set_gauge( &SLASHER_NUM_ATTESTATIONS_STORED_PER_BATCH, num_stored as i64, @@ -239,19 +219,14 @@ impl Slasher { ) { Ok(slashings) => { if !slashings.is_empty() { - info!( - self.log, - "Found {} new double-vote slashings!", - slashings.len() - ); + info!("Found {} new double-vote slashings!", slashings.len()); } self.attester_slashings.lock().extend(slashings); } Err(e) => { error!( - self.log, - "Error checking for double votes"; - "error" => format!("{:?}", e) + error = ?e, + "Error checking for double votes" ); return Err(e); } @@ -269,20 +244,12 @@ impl Slasher { ) { Ok(slashings) => { if !slashings.is_empty() { - info!( - self.log, - "Found {} new surround slashings!", - slashings.len() - ); + info!("Found {} new surround slashings!", slashings.len()); } self.attester_slashings.lock().extend(slashings); } Err(e) => { - error!( - self.log, - "Error processing array update"; - "error" => format!("{:?}", e), - ); + error!(error = ?e, "Error processing array update"); return Err(e); } } @@ -315,10 +282,9 @@ impl Slasher { if let Some(slashing) = slashing_status.into_slashing(attestation) { debug!( - self.log, - "Found double-vote slashing"; - "validator_index" => validator_index, - "epoch" => slashing.attestation_1().data().target.epoch, + validator_index, + epoch = %slashing.attestation_1().data().target.epoch, + "Found double-vote slashing" ); slashings.insert(slashing); } diff --git a/slasher/tests/attester_slashings.rs b/slasher/tests/attester_slashings.rs index cc6e57d95d..22c9cfc128 100644 --- a/slasher/tests/attester_slashings.rs +++ b/slasher/tests/attester_slashings.rs @@ -1,6 +1,5 @@ #![cfg(any(feature = "mdbx", feature = "lmdb", feature = "redb"))] -use logging::test_logger; use maplit::hashset; use rayon::prelude::*; use slasher::{ @@ -272,7 +271,7 @@ fn slasher_test( let tempdir = tempdir().unwrap(); let config = Config::new(tempdir.path().into()); let spec = chain_spec(); - let slasher = Slasher::open(config, spec, test_logger()).unwrap(); + let slasher = Slasher::open(config, spec).unwrap(); let current_epoch = Epoch::new(current_epoch); for (i, attestation) in attestations.iter().enumerate() { @@ -302,7 +301,7 @@ fn parallel_slasher_test( let tempdir = tempdir().unwrap(); let config = Config::new(tempdir.path().into()); let spec = chain_spec(); - let slasher = Slasher::open(config, spec, test_logger()).unwrap(); + let slasher = Slasher::open(config, spec).unwrap(); let current_epoch = Epoch::new(current_epoch); attestations diff --git a/slasher/tests/proposer_slashings.rs b/slasher/tests/proposer_slashings.rs index 6d2a1f5176..ef525c6f3f 100644 --- a/slasher/tests/proposer_slashings.rs +++ b/slasher/tests/proposer_slashings.rs @@ -1,6 +1,5 @@ #![cfg(any(feature = "mdbx", feature = "lmdb", feature = "redb"))] -use logging::test_logger; use slasher::{ test_utils::{block as test_block, chain_spec, E}, Config, Slasher, @@ -13,7 +12,7 @@ fn empty_pruning() { let tempdir = tempdir().unwrap(); let config = Config::new(tempdir.path().into()); let spec = chain_spec(); - let slasher = Slasher::::open(config, spec, test_logger()).unwrap(); + let slasher = Slasher::::open(config, spec).unwrap(); slasher.prune_database(Epoch::new(0)).unwrap(); } @@ -27,7 +26,7 @@ fn block_pruning() { config.history_length = 2; let spec = chain_spec(); - let slasher = Slasher::::open(config.clone(), spec, test_logger()).unwrap(); + let slasher = Slasher::::open(config.clone(), spec).unwrap(); let current_epoch = Epoch::from(2 * config.history_length); // Pruning the empty database should be safe. diff --git a/slasher/tests/random.rs b/slasher/tests/random.rs index ff234dff3f..3270700d88 100644 --- a/slasher/tests/random.rs +++ b/slasher/tests/random.rs @@ -1,6 +1,5 @@ #![cfg(any(feature = "mdbx", feature = "lmdb", feature = "redb"))] -use logging::test_logger; use rand::prelude::*; use slasher::{ test_utils::{ @@ -36,9 +35,8 @@ impl Default for TestConfig { fn make_db() -> (TempDir, SlasherDB) { let tempdir = tempdir().unwrap(); let initial_config = Arc::new(Config::new(tempdir.path().into())); - let logger = test_logger(); let spec = chain_spec(); - let db = SlasherDB::open(initial_config.clone(), spec, logger).unwrap(); + let db = SlasherDB::open(initial_config.clone(), spec).unwrap(); (tempdir, db) } @@ -60,7 +58,7 @@ fn random_test(seed: u64, mut db: SlasherDB, test_config: TestConfig) -> Slas let config = Arc::new(config); db.update_config(config.clone()); - let slasher = Slasher::::from_config_and_db(config.clone(), db, test_logger()).unwrap(); + let slasher = Slasher::::from_config_and_db(config.clone(), db).unwrap(); let validators = (0..num_validators as u64).collect::>(); diff --git a/slasher/tests/wrap_around.rs b/slasher/tests/wrap_around.rs index 2ec56bc7d5..e34d0f2233 100644 --- a/slasher/tests/wrap_around.rs +++ b/slasher/tests/wrap_around.rs @@ -1,6 +1,5 @@ #![cfg(any(feature = "mdbx", feature = "lmdb", feature = "redb"))] -use logging::test_logger; use slasher::{ test_utils::{chain_spec, indexed_att}, Config, Slasher, @@ -17,7 +16,7 @@ fn attestation_pruning_empty_wrap_around() { config.chunk_size = 16; config.history_length = 16; - let slasher = Slasher::open(config.clone(), spec, test_logger()).unwrap(); + let slasher = Slasher::open(config.clone(), spec).unwrap(); let v = vec![0]; let history_length = config.history_length as u64; diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index 8a662b72e3..4e744b797a 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -27,10 +27,8 @@ excluded_paths = [ "tests/.*/.*/ssz_static/PowBlock/", # We no longer implement merge logic. "tests/.*/bellatrix/fork_choice/on_merge_block", - # light_client - "tests/.*/.*/light_client/single_merkle_proof", + # Light client sync is not implemented "tests/.*/.*/light_client/sync", - "tests/.*/electra/light_client/update_ranking", # LightClientStore "tests/.*/.*/ssz_static/LightClientStore", # LightClientSnapshot diff --git a/testing/ef_tests/src/cases/fork_choice.rs b/testing/ef_tests/src/cases/fork_choice.rs index a1c74389a7..2f97cdf5b9 100644 --- a/testing/ef_tests/src/cases/fork_choice.rs +++ b/testing/ef_tests/src/cases/fork_choice.rs @@ -371,7 +371,6 @@ impl Tester { } let harness = BeaconChainHarness::>::builder(E::default()) - .logger(logging::test_logger()) .spec(spec.clone()) .keypairs(vec![]) .chain_config(ChainConfig { diff --git a/testing/ef_tests/src/cases/merkle_proof_validity.rs b/testing/ef_tests/src/cases/merkle_proof_validity.rs index 109d2cc796..711974dd43 100644 --- a/testing/ef_tests/src/cases/merkle_proof_validity.rs +++ b/testing/ef_tests/src/cases/merkle_proof_validity.rs @@ -20,6 +20,12 @@ pub struct MerkleProof { pub branch: Vec, } +#[derive(Debug)] +pub enum GenericMerkleProofValidity { + BeaconState(BeaconStateMerkleProofValidity), + BeaconBlockBody(Box>), +} + #[derive(Debug, Clone, Deserialize)] #[serde(bound = "E: EthSpec")] pub struct BeaconStateMerkleProofValidity { @@ -28,6 +34,39 @@ pub struct BeaconStateMerkleProofValidity { pub merkle_proof: MerkleProof, } +impl LoadCase for GenericMerkleProofValidity { + fn load_from_dir(path: &Path, fork_name: ForkName) -> Result { + let path_components = path.iter().collect::>(); + + // The "suite" name is the 2nd last directory in the path. + assert!( + path_components.len() >= 2, + "path should have at least 2 components" + ); + let suite_name = path_components[path_components.len() - 2]; + + if suite_name == "BeaconState" { + BeaconStateMerkleProofValidity::load_from_dir(path, fork_name) + .map(GenericMerkleProofValidity::BeaconState) + } else if suite_name == "BeaconBlockBody" { + BeaconBlockBodyMerkleProofValidity::load_from_dir(path, fork_name) + .map(Box::new) + .map(GenericMerkleProofValidity::BeaconBlockBody) + } else { + panic!("unsupported type for merkle proof test: {:?}", suite_name) + } + } +} + +impl Case for GenericMerkleProofValidity { + fn result(&self, case_index: usize, fork_name: ForkName) -> Result<(), Error> { + match self { + Self::BeaconState(test) => test.result(case_index, fork_name), + Self::BeaconBlockBody(test) => test.result(case_index, fork_name), + } + } +} + impl LoadCase for BeaconStateMerkleProofValidity { fn load_from_dir(path: &Path, fork_name: ForkName) -> Result { let spec = &testing_spec::(fork_name); @@ -72,11 +111,9 @@ impl Case for BeaconStateMerkleProofValidity { } }; - let Ok(proof) = proof else { - return Err(Error::FailedToParseTest( - "Could not retrieve merkle proof".to_string(), - )); - }; + let proof = proof.map_err(|e| { + Error::FailedToParseTest(format!("Could not retrieve merkle proof: {e:?}")) + })?; let proof_len = proof.len(); let branch_len = self.merkle_proof.branch.len(); if proof_len != branch_len { @@ -273,11 +310,11 @@ impl Case for BeaconBlockBodyMerkleProofValidity { fn result(&self, _case_index: usize, _fork_name: ForkName) -> Result<(), Error> { let binding = self.block_body.clone(); let block_body = binding.to_ref(); - let Ok(proof) = block_body.block_body_merkle_proof(self.merkle_proof.leaf_index) else { - return Err(Error::FailedToParseTest( - "Could not retrieve merkle proof".to_string(), - )); - }; + let proof = block_body + .block_body_merkle_proof(self.merkle_proof.leaf_index) + .map_err(|e| { + Error::FailedToParseTest(format!("Could not retrieve merkle proof: {e:?}")) + })?; let proof_len = proof.len(); let branch_len = self.merkle_proof.branch.len(); if proof_len != branch_len { diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index 481c9b2169..a375498239 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -1000,30 +1000,6 @@ impl Handler for KZGRecoverCellsAndKZGProofHandler { } } -#[derive(Derivative)] -#[derivative(Default(bound = ""))] -pub struct BeaconStateMerkleProofValidityHandler(PhantomData); - -impl Handler for BeaconStateMerkleProofValidityHandler { - type Case = cases::BeaconStateMerkleProofValidity; - - fn config_name() -> &'static str { - E::name() - } - - fn runner_name() -> &'static str { - "light_client" - } - - fn handler_name(&self) -> String { - "single_merkle_proof/BeaconState".into() - } - - fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { - fork_name.altair_enabled() - } -} - #[derive(Derivative)] #[derivative(Default(bound = ""))] pub struct KzgInclusionMerkleProofValidityHandler(PhantomData); @@ -1054,10 +1030,10 @@ impl Handler for KzgInclusionMerkleProofValidityHandler(PhantomData); +pub struct MerkleProofValidityHandler(PhantomData); -impl Handler for BeaconBlockBodyMerkleProofValidityHandler { - type Case = cases::BeaconBlockBodyMerkleProofValidity; +impl Handler for MerkleProofValidityHandler { + type Case = cases::GenericMerkleProofValidity; fn config_name() -> &'static str { E::name() @@ -1068,11 +1044,11 @@ impl Handler for BeaconBlockBodyMerkleProofValidityHandle } fn handler_name(&self) -> String { - "single_merkle_proof/BeaconBlockBody".into() + "single_merkle_proof".into() } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { - fork_name.capella_enabled() + fork_name.altair_enabled() } } diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 1f5a7dd997..3948708edf 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -955,13 +955,9 @@ fn kzg_recover_cells_and_proofs() { } #[test] -fn beacon_state_merkle_proof_validity() { - BeaconStateMerkleProofValidityHandler::::default().run(); -} - -#[test] -fn beacon_block_body_merkle_proof_validity() { - BeaconBlockBodyMerkleProofValidityHandler::::default().run(); +fn light_client_merkle_proof_validity() { + MerkleProofValidityHandler::::default().run(); + MerkleProofValidityHandler::::default().run(); } #[test] diff --git a/testing/execution_engine_integration/src/test_rig.rs b/testing/execution_engine_integration/src/test_rig.rs index f664509304..cf31c184fe 100644 --- a/testing/execution_engine_integration/src/test_rig.rs +++ b/testing/execution_engine_integration/src/test_rig.rs @@ -105,7 +105,6 @@ async fn import_and_unlock(http_url: SensitiveUrl, priv_keys: &[&str], password: impl TestRig { pub fn new(generic_engine: Engine) -> Self { - let log = logging::test_logger(); let runtime = Arc::new( tokio::runtime::Builder::new_multi_thread() .enable_all() @@ -114,7 +113,12 @@ impl TestRig { ); let (runtime_shutdown, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let executor = TaskExecutor::new(Arc::downgrade(&runtime), exit, log.clone(), shutdown_tx); + let executor = TaskExecutor::new( + Arc::downgrade(&runtime), + exit, + shutdown_tx, + "test".to_string(), + ); let mut spec = TEST_FORK.make_genesis_spec(MainnetEthSpec::default_spec()); spec.terminal_total_difficulty = Uint256::ZERO; @@ -131,8 +135,7 @@ impl TestRig { default_datadir: execution_engine.datadir(), ..Default::default() }; - let execution_layer = - ExecutionLayer::from_config(config, executor.clone(), log.clone()).unwrap(); + let execution_layer = ExecutionLayer::from_config(config, executor.clone()).unwrap(); ExecutionPair { execution_engine, execution_layer, @@ -150,8 +153,7 @@ impl TestRig { default_datadir: execution_engine.datadir(), ..Default::default() }; - let execution_layer = - ExecutionLayer::from_config(config, executor, log.clone()).unwrap(); + let execution_layer = ExecutionLayer::from_config(config, executor).unwrap(); ExecutionPair { execution_engine, execution_layer, diff --git a/testing/simulator/Cargo.toml b/testing/simulator/Cargo.toml index 77645dba45..12b0afcc75 100644 --- a/testing/simulator/Cargo.toml +++ b/testing/simulator/Cargo.toml @@ -8,14 +8,17 @@ edition = { workspace = true } [dependencies] clap = { workspace = true } env_logger = { workspace = true } +environment = { workspace = true } eth2_network_config = { workspace = true } execution_layer = { workspace = true } futures = { workspace = true } kzg = { workspace = true } +logging = { workspace = true } node_test_rig = { path = "../node_test_rig" } parking_lot = { workspace = true } rayon = { workspace = true } sensitive_url = { path = "../../common/sensitive_url" } serde_json = { workspace = true } tokio = { workspace = true } +tracing-subscriber = { workspace = true } types = { workspace = true } diff --git a/testing/simulator/src/basic_sim.rs b/testing/simulator/src/basic_sim.rs index 82a7028582..fff5c71a87 100644 --- a/testing/simulator/src/basic_sim.rs +++ b/testing/simulator/src/basic_sim.rs @@ -13,6 +13,12 @@ use rayon::prelude::*; use std::cmp::max; use std::sync::Arc; use std::time::Duration; + +use environment::tracing_common; +use logging::MetricsLayer; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + use tokio::time::sleep; use types::{Epoch, EthSpec, MinimalEthSpec}; @@ -82,23 +88,47 @@ pub fn run_basic_sim(matches: &ArgMatches) -> Result<(), String> { }) .collect::>(); - let mut env = EnvironmentBuilder::minimal() - .initialize_logger(LoggerConfig { + let ( + env_builder, + filter_layer, + _libp2p_discv5_layer, + file_logging_layer, + stdout_logging_layer, + _sse_logging_layer_opt, + logger_config, + _dependency_log_filter, + ) = tracing_common::construct_logger( + LoggerConfig { path: None, - debug_level: log_level.clone(), - logfile_debug_level: log_level.clone(), + debug_level: tracing_common::parse_level(&log_level.clone()), + logfile_debug_level: tracing_common::parse_level(&log_level.clone()), log_format: None, logfile_format: None, - log_color: false, + log_color: true, + logfile_color: true, disable_log_timestamp: false, max_log_size: 0, max_log_number: 0, compression: false, is_restricted: true, sse_logging: false, - })? - .multi_threaded_tokio_runtime()? - .build()?; + extra_info: false, + }, + matches, + EnvironmentBuilder::minimal(), + ); + + if let Err(e) = tracing_subscriber::registry() + .with(filter_layer) + .with(file_logging_layer.with_filter(logger_config.logfile_debug_level)) + .with(stdout_logging_layer.with_filter(logger_config.debug_level)) + .with(MetricsLayer) + .try_init() + { + eprintln!("Failed to initialize dependency logging: {e}"); + } + + let mut env = env_builder.multi_threaded_tokio_runtime()?.build()?; let mut spec = (*env.eth2_config.spec).clone(); diff --git a/testing/simulator/src/fallback_sim.rs b/testing/simulator/src/fallback_sim.rs index 7d4bdfa264..98a6a34ffa 100644 --- a/testing/simulator/src/fallback_sim.rs +++ b/testing/simulator/src/fallback_sim.rs @@ -3,7 +3,9 @@ use crate::{checks, LocalNetwork}; use clap::ArgMatches; use crate::retry::with_retry; +use environment::tracing_common; use futures::prelude::*; +use logging::MetricsLayer; use node_test_rig::{ environment::{EnvironmentBuilder, LoggerConfig}, testing_validator_config, ValidatorFiles, @@ -13,8 +15,9 @@ use std::cmp::max; use std::sync::Arc; use std::time::Duration; use tokio::time::sleep; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use types::{Epoch, EthSpec, MinimalEthSpec}; - const END_EPOCH: u64 = 16; const GENESIS_DELAY: u64 = 32; const ALTAIR_FORK_EPOCH: u64 = 0; @@ -89,23 +92,49 @@ pub fn run_fallback_sim(matches: &ArgMatches) -> Result<(), String> { }) .collect::>(); - let mut env = EnvironmentBuilder::minimal() - .initialize_logger(LoggerConfig { + let ( + env_builder, + filter_layer, + libp2p_discv5_layer, + file_logging_layer, + stdout_logging_layer, + _sse_logging_layer_opt, + logger_config, + dependency_log_filter, + ) = tracing_common::construct_logger( + LoggerConfig { path: None, - debug_level: log_level.clone(), - logfile_debug_level: log_level.clone(), + debug_level: tracing_common::parse_level(&log_level.clone()), + logfile_debug_level: tracing_common::parse_level(&log_level.clone()), log_format: None, logfile_format: None, - log_color: false, + log_color: true, + logfile_color: false, disable_log_timestamp: false, max_log_size: 0, max_log_number: 0, compression: false, is_restricted: true, sse_logging: false, - })? - .multi_threaded_tokio_runtime()? - .build()?; + extra_info: false, + }, + matches, + EnvironmentBuilder::minimal(), + ); + + if let Err(e) = tracing_subscriber::registry() + .with(dependency_log_filter) + .with(filter_layer) + .with(file_logging_layer.with_filter(logger_config.logfile_debug_level)) + .with(stdout_logging_layer.with_filter(logger_config.debug_level)) + .with(libp2p_discv5_layer) + .with(MetricsLayer) + .try_init() + { + eprintln!("Failed to initialize dependency logging: {e}"); + } + + let mut env = env_builder.multi_threaded_tokio_runtime()?.build()?; let mut spec = (*env.eth2_config.spec).clone(); diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index a95c15c231..3914d33f93 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -44,7 +44,6 @@ fn default_client_config(network_params: LocalNetworkParams, genesis_time: u64) beacon_config.network.enable_light_client_server = true; beacon_config.network.discv5_config.enable_packet_filter = false; beacon_config.chain.enable_light_client_server = true; - beacon_config.http_api.enable_light_client_server = true; beacon_config.chain.optimistic_finalized_sync = false; beacon_config.trusted_setup = serde_json::from_reader(get_trusted_setup().as_slice()) .expect("Trusted setup bytes should be valid"); diff --git a/testing/test-test_logger/Cargo.toml b/testing/test-test_logger/Cargo.toml deleted file mode 100644 index d2d705f714..0000000000 --- a/testing/test-test_logger/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "test-test_logger" -version = "0.1.0" -edition = { workspace = true } -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -logging = { workspace = true } -slog = { workspace = true } diff --git a/testing/test-test_logger/src/lib.rs b/testing/test-test_logger/src/lib.rs deleted file mode 100644 index a2e2a80943..0000000000 --- a/testing/test-test_logger/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -use slog::{info, Logger}; - -pub struct Config { - log: Logger, -} - -pub fn fn_with_logging(config: &Config) { - info!(&config.log, "hi"); -} - -#[cfg(test)] -mod tests { - use super::*; - use logging::test_logger; - - #[test] - fn test_fn_with_logging() { - let config = Config { log: test_logger() }; - - fn_with_logging(&config); - } -} diff --git a/testing/validator_test_rig/Cargo.toml b/testing/validator_test_rig/Cargo.toml index 76560b8afc..bdbdac95d8 100644 --- a/testing/validator_test_rig/Cargo.toml +++ b/testing/validator_test_rig/Cargo.toml @@ -10,5 +10,5 @@ mockito = { workspace = true } regex = { workspace = true } sensitive_url = { workspace = true } serde_json = { workspace = true } -slog = { workspace = true } +tracing = { workspace = true } types = { workspace = true } diff --git a/testing/validator_test_rig/src/mock_beacon_node.rs b/testing/validator_test_rig/src/mock_beacon_node.rs index f875116155..7a90270913 100644 --- a/testing/validator_test_rig/src/mock_beacon_node.rs +++ b/testing/validator_test_rig/src/mock_beacon_node.rs @@ -1,20 +1,18 @@ use eth2::types::{GenericResponse, SyncingData}; use eth2::{BeaconNodeHttpClient, StatusCode, Timeouts}; -use logging::test_logger; use mockito::{Matcher, Mock, Server, ServerGuard}; use regex::Regex; use sensitive_url::SensitiveUrl; -use slog::{info, Logger}; use std::marker::PhantomData; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::Duration; +use tracing::info; use types::{ChainSpec, ConfigAndPreset, EthSpec, SignedBlindedBeaconBlock}; pub struct MockBeaconNode { server: ServerGuard, pub beacon_api_client: BeaconNodeHttpClient, - log: Logger, _phantom: PhantomData, pub received_blocks: Arc>>>, } @@ -27,11 +25,9 @@ impl MockBeaconNode { SensitiveUrl::from_str(&server.url()).unwrap(), Timeouts::set_all(Duration::from_secs(1)), ); - let log = test_logger(); Self { server, beacon_api_client, - log, _phantom: PhantomData, received_blocks: Arc::new(Mutex::new(Vec::new())), } @@ -69,7 +65,6 @@ impl MockBeaconNode { /// Mocks the `post_beacon_blinded_blocks_v2_ssz` response with an optional `delay`. pub fn mock_post_beacon_blinded_blocks_v2_ssz(&mut self, delay: Duration) -> Mock { let path_pattern = Regex::new(r"^/eth/v2/beacon/blinded_blocks$").unwrap(); - let log = self.log.clone(); let url = self.server.url(); let received_blocks = Arc::clone(&self.received_blocks); @@ -80,7 +75,6 @@ impl MockBeaconNode { .with_status(200) .with_body_from_request(move |request| { info!( - log, "{}", format!( "Received published block request on server {} with delay {} s", diff --git a/testing/web3signer_tests/Cargo.toml b/testing/web3signer_tests/Cargo.toml index f68fa56e16..376aa13406 100644 --- a/testing/web3signer_tests/Cargo.toml +++ b/testing/web3signer_tests/Cargo.toml @@ -14,7 +14,6 @@ eth2_keystore = { workspace = true } eth2_network_config = { workspace = true } futures = { workspace = true } initialized_validators = { workspace = true } -lighthouse_validator_store = { workspace = true } logging = { workspace = true } parking_lot = { workspace = true } reqwest = { workspace = true } diff --git a/testing/web3signer_tests/src/get_web3signer.rs b/testing/web3signer_tests/src/get_web3signer.rs index 800feb204a..8c46a07a7d 100644 --- a/testing/web3signer_tests/src/get_web3signer.rs +++ b/testing/web3signer_tests/src/get_web3signer.rs @@ -1,65 +1,33 @@ //! This build script downloads the latest Web3Signer release and places it in the `OUT_DIR` so it //! can be used for integration testing. -use reqwest::{ - header::{self, HeaderValue}, - Client, -}; -use serde_json::Value; +use reqwest::Client; use std::env; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use zip::ZipArchive; /// Use `None` to download the latest Github release. /// Use `Some("21.8.1")` to download a specific version. const FIXED_VERSION_STRING: Option<&str> = None; -pub async fn download_binary(dest_dir: PathBuf, github_token: &str) { - let version_file = dest_dir.join("version"); - - let client = Client::builder() - // Github gives a 403 without a user agent. - .user_agent("web3signer_tests") - .build() - .unwrap(); - +// This function no longer makes any attempt to avoid downloads, because in practice we use it +// with a fresh temp directory every time we run the tests. We might want to change this in future +// to enable reproducible/offline testing. +pub async fn download_binary(dest_dir: PathBuf) { let version = if let Some(version) = FIXED_VERSION_STRING { version.to_string() } else if let Ok(env_version) = env::var("LIGHTHOUSE_WEB3SIGNER_VERSION") { env_version } else { - // Get the latest release of the web3 signer repo. - let mut token_header_value = HeaderValue::from_str(github_token).unwrap(); - token_header_value.set_sensitive(true); - let latest_response: Value = client - .get("https://api.github.com/repos/ConsenSys/web3signer/releases/latest") - .header(header::AUTHORIZATION, token_header_value) - .send() - .await - .unwrap() - .error_for_status() - .unwrap() - .json() - .await - .unwrap(); - latest_response - .get("tag_name") - .unwrap() - .as_str() - .unwrap() - .to_string() + // The Consenys artifact server resolves `latest` to the latest release. We previously hit + // the Github API to establish the version, but that is no longer necessary. + "latest".to_string() }; + eprintln!("Downloading web3signer version: {version}"); - if version_file.exists() && fs::read(&version_file).unwrap() == version.as_bytes() { - // The latest version is already downloaded, do nothing. - return; - } else { - // Ignore the result since we don't care if the version file already exists. - let _ = fs::remove_file(&version_file); - } - - // Download the latest release zip. + // Download the release zip. + let client = Client::builder().build().unwrap(); let zip_url = format!("https://artifacts.consensys.net/public/web3signer/raw/names/web3signer.zip/versions/{}/web3signer-{}.zip", version, version); let zip_response = client .get(zip_url) @@ -73,8 +41,9 @@ pub async fn download_binary(dest_dir: PathBuf, github_token: &str) { .unwrap(); // Write the zip to a file. - let zip_path = dest_dir.join(format!("{}.zip", version)); + let zip_path = dest_dir.join(format!("web3signer-{version}.zip")); fs::write(&zip_path, zip_response).unwrap(); + // Unzip the zip. let mut zip_file = fs::File::open(&zip_path).unwrap(); ZipArchive::new(&mut zip_file) @@ -88,15 +57,33 @@ pub async fn download_binary(dest_dir: PathBuf, github_token: &str) { if web3signer_dir.exists() { fs::remove_dir_all(&web3signer_dir).unwrap(); } - fs::rename( - dest_dir.join(format!("web3signer-{}", version)), - web3signer_dir, - ) - .unwrap(); - // Delete zip and unzipped dir. + let versioned_web3signer_dir = find_versioned_web3signer_dir(&dest_dir); + eprintln!( + "Renaming versioned web3signer dir at: {}", + versioned_web3signer_dir.display() + ); + + fs::rename(versioned_web3signer_dir, web3signer_dir).unwrap(); + + // Delete zip. fs::remove_file(&zip_path).unwrap(); - - // Update the version file to avoid duplicate downloads. - fs::write(&version_file, version).unwrap(); +} + +fn find_versioned_web3signer_dir(dest_dir: &Path) -> PathBuf { + for entry in fs::read_dir(dest_dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + + if path + .file_name() + .and_then(|n| n.to_str()) + .map(|s| s.starts_with("web3signer-")) + .unwrap_or(false) + && entry.file_type().unwrap().is_dir() + { + return path; + } + } + panic!("no directory named web3signer-* found after ZIP extraction") } diff --git a/testing/web3signer_tests/src/lib.rs b/testing/web3signer_tests/src/lib.rs index 05512e9608..1eb14cf1d5 100644 --- a/testing/web3signer_tests/src/lib.rs +++ b/testing/web3signer_tests/src/lib.rs @@ -25,8 +25,6 @@ mod tests { use initialized_validators::{ load_pem_certificate, load_pkcs12_identity, InitializedValidators, }; - use lighthouse_validator_store::LighthouseValidatorStore; - use logging::test_logger; use parking_lot::Mutex; use reqwest::Client; use serde::Serialize; @@ -46,7 +44,7 @@ mod tests { use tokio::time::sleep; use types::{attestation::AttestationBase, *}; use url::Url; - use validator_store::{Error as ValidatorStoreError, SignedBlock, ValidatorStore}; + use validator_store::{Error as ValidatorStoreError, ValidatorStore}; /// If the we are unable to reach the Web3Signer HTTP API within this time out then we will /// assume it failed to start. @@ -75,7 +73,6 @@ mod tests { impl SignedObject for Signature {} impl SignedObject for Attestation {} impl SignedObject for SignedBeaconBlock {} - impl SignedObject for SignedBlock {} impl SignedObject for SignedAggregateAndProof {} impl SignedObject for SelectionProof {} impl SignedObject for SyncSelectionProof {} @@ -180,14 +177,7 @@ mod tests { pub async fn new(network: &str, listen_address: &str, listen_port: u16) -> Self { GET_WEB3SIGNER_BIN .get_or_init(|| async { - // Read a Github API token from the environment. This is intended to prevent rate-limits on CI. - // We use a name that is unlikely to accidentally collide with anything the user has configured. - let github_token = env::var("LIGHTHOUSE_GITHUB_TOKEN"); - download_binary( - TEMP_DIR.lock().path().to_path_buf(), - github_token.as_deref().unwrap_or(""), - ) - .await; + download_binary(TEMP_DIR.lock().path().to_path_buf()).await; }) .await; @@ -311,7 +301,7 @@ mod tests { /// A testing rig which holds a `ValidatorStore`. struct ValidatorStoreRig { - validator_store: Arc>, + validator_store: Arc>, _validator_dir: TempDir, runtime: Arc, _runtime_shutdown: async_channel::Sender<()>, @@ -325,7 +315,6 @@ mod tests { using_web3signer: bool, spec: Arc, ) -> Self { - let log = test_logger(); let validator_dir = TempDir::new().unwrap(); let config = initialized_validators::Config::default(); @@ -334,7 +323,6 @@ mod tests { validator_definitions, validator_dir.path().into(), config.clone(), - log.clone(), ) .await .unwrap(); @@ -349,8 +337,12 @@ mod tests { ); let (runtime_shutdown, exit) = async_channel::bounded(1); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); - let executor = - TaskExecutor::new(Arc::downgrade(&runtime), exit, log.clone(), shutdown_tx); + let executor = TaskExecutor::new( + Arc::downgrade(&runtime), + exit, + shutdown_tx, + "test".to_string(), + ); let slashing_db_path = validator_dir.path().join(SLASHING_PROTECTION_FILENAME); let slashing_protection = SlashingDatabase::open_or_create(&slashing_db_path).unwrap(); @@ -360,12 +352,12 @@ mod tests { let slot_clock = TestingSlotClock::new(Slot::new(0), Duration::from_secs(0), Duration::from_secs(1)); - let config = lighthouse_validator_store::Config { + let config = validator_store::Config { enable_web3signer_slashing_protection: slashing_protection_config.local, ..Default::default() }; - let validator_store = LighthouseValidatorStore::new( + let validator_store = ValidatorStore::<_, E>::new( initialized_validators, slashing_protection, Hash256::repeat_byte(42), @@ -374,7 +366,6 @@ mod tests { slot_clock, &config, executor, - log.clone(), ); Self { @@ -490,7 +481,7 @@ mod tests { generate_sig: F, ) -> Self where - F: Fn(PublicKeyBytes, Arc>) -> R, + F: Fn(PublicKeyBytes, Arc>) -> R, R: Future, // We use the `SignedObject` trait to white-list objects for comparison. This avoids // accidentally comparing something meaningless like a `()`. @@ -525,8 +516,8 @@ mod tests { web3signer_should_sign: bool, ) -> Self where - F: Fn(PublicKeyBytes, Arc>) -> R, - R: Future>, + F: Fn(PublicKeyBytes, Arc>) -> R, + R: Future>, { for validator_rig in &self.validator_rigs { let result = @@ -600,10 +591,10 @@ mod tests { .assert_signatures_match("beacon_block_base", |pubkey, validator_store| { let spec = spec.clone(); async move { - let block = BeaconBlock::::Base(BeaconBlockBase::empty(&spec)); + let block = BeaconBlock::Base(BeaconBlockBase::empty(&spec)); let block_slot = block.slot(); validator_store - .sign_block(pubkey, block.into(), block_slot) + .sign_block(pubkey, block, block_slot) .await .unwrap() } @@ -670,14 +661,10 @@ mod tests { .assert_signatures_match("beacon_block_altair", |pubkey, validator_store| { let spec = spec.clone(); async move { - let mut altair_block = BeaconBlockAltair::::empty(&spec); + let mut altair_block = BeaconBlockAltair::empty(&spec); altair_block.slot = altair_fork_slot; validator_store - .sign_block( - pubkey, - BeaconBlock::Altair(altair_block).into(), - altair_fork_slot, - ) + .sign_block(pubkey, BeaconBlock::Altair(altair_block), altair_fork_slot) .await .unwrap() } @@ -757,12 +744,12 @@ mod tests { .assert_signatures_match("beacon_block_bellatrix", |pubkey, validator_store| { let spec = spec.clone(); async move { - let mut bellatrix_block = BeaconBlockBellatrix::::empty(&spec); + let mut bellatrix_block = BeaconBlockBellatrix::empty(&spec); bellatrix_block.slot = bellatrix_fork_slot; validator_store .sign_block( pubkey, - BeaconBlock::Bellatrix(bellatrix_block).into(), + BeaconBlock::Bellatrix(bellatrix_block), bellatrix_fork_slot, ) .await @@ -818,7 +805,7 @@ mod tests { }; let first_block = || { - let mut bellatrix_block = BeaconBlockBellatrix::::empty(&spec); + let mut bellatrix_block = BeaconBlockBellatrix::empty(&spec); bellatrix_block.slot = bellatrix_fork_slot; BeaconBlock::Bellatrix(bellatrix_block) }; @@ -884,7 +871,7 @@ mod tests { let block = first_block(); let slot = block.slot(); validator_store - .sign_block(pubkey, block.into(), slot) + .sign_block(pubkey, block, slot) .await .unwrap() }) @@ -895,7 +882,7 @@ mod tests { let block = double_vote_block(); let slot = block.slot(); validator_store - .sign_block(pubkey, block.into(), slot) + .sign_block(pubkey, block, slot) .await .map(|_| ()) }, diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 9b4887b478..85517682bb 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -22,7 +22,6 @@ fdlimit = "0.3.0" graffiti_file = { workspace = true } hyper = { workspace = true } initialized_validators = { workspace = true } -lighthouse_validator_store = { workspace = true } metrics = { workspace = true } monitoring_api = { workspace = true } parking_lot = { workspace = true } @@ -30,9 +29,9 @@ reqwest = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } slashing_protection = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } types = { workspace = true } validator_http_api = { workspace = true } validator_http_metrics = { workspace = true } diff --git a/validator_client/beacon_node_fallback/Cargo.toml b/validator_client/beacon_node_fallback/Cargo.toml index ccf2d650a6..4297bae15f 100644 --- a/validator_client/beacon_node_fallback/Cargo.toml +++ b/validator_client/beacon_node_fallback/Cargo.toml @@ -10,13 +10,13 @@ path = "src/lib.rs" [dependencies] clap = { workspace = true } +environment = { workspace = true } eth2 = { workspace = true } futures = { workspace = true } itertools = { workspace = true } serde = { workspace = true } slot_clock = { workspace = true } strum = { workspace = true } -task_executor = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } types = { workspace = true } diff --git a/validator_client/beacon_node_fallback/src/lib.rs b/validator_client/beacon_node_fallback/src/lib.rs index 9cdac02389..befc18c563 100644 --- a/validator_client/beacon_node_fallback/src/lib.rs +++ b/validator_client/beacon_node_fallback/src/lib.rs @@ -8,6 +8,7 @@ use beacon_node_health::{ IsOptimistic, SyncDistanceTier, }; use clap::ValueEnum; +use environment::RuntimeContext; use eth2::BeaconNodeHttpClient; use futures::future; use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; @@ -16,11 +17,11 @@ use std::cmp::Ordering; use std::fmt; use std::fmt::Debug; use std::future::Future; +use std::marker::PhantomData; use std::sync::Arc; use std::time::{Duration, Instant}; use std::vec::Vec; use strum::EnumVariantNames; -use task_executor::TaskExecutor; use tokio::{sync::RwLock, time::sleep}; use tracing::{debug, error, warn}; use types::{ChainSpec, Config as ConfigSpec, EthSpec, Slot}; @@ -60,16 +61,17 @@ pub struct LatencyMeasurement { /// /// See `SLOT_LOOKAHEAD` for information about when this should run. pub fn start_fallback_updater_service( - executor: TaskExecutor, - beacon_nodes: Arc>, + context: RuntimeContext, + beacon_nodes: Arc>, ) -> Result<(), &'static str> { + let executor = context.executor; if beacon_nodes.slot_clock.is_none() { return Err("Cannot start fallback updater without slot clock"); } let future = async move { loop { - beacon_nodes.update_all_candidates::().await; + beacon_nodes.update_all_candidates().await; let sleep_time = beacon_nodes .slot_clock @@ -184,27 +186,29 @@ impl Serialize for CandidateInfo { /// Represents a `BeaconNodeHttpClient` inside a `BeaconNodeFallback` that may or may not be used /// for a query. #[derive(Clone, Debug)] -pub struct CandidateBeaconNode { +pub struct CandidateBeaconNode { pub index: usize, pub beacon_node: BeaconNodeHttpClient, pub health: Arc>>, + _phantom: PhantomData, } -impl PartialEq for CandidateBeaconNode { +impl PartialEq for CandidateBeaconNode { fn eq(&self, other: &Self) -> bool { self.index == other.index && self.beacon_node == other.beacon_node } } -impl Eq for CandidateBeaconNode {} +impl Eq for CandidateBeaconNode {} -impl CandidateBeaconNode { +impl CandidateBeaconNode { /// Instantiate a new node. pub fn new(beacon_node: BeaconNodeHttpClient, index: usize) -> Self { Self { index, beacon_node, health: Arc::new(RwLock::new(Err(CandidateError::Uninitialized))), + _phantom: PhantomData, } } @@ -213,13 +217,13 @@ impl CandidateBeaconNode { *self.health.read().await } - pub async fn refresh_health( + pub async fn refresh_health( &self, distance_tiers: &BeaconNodeSyncDistanceTiers, slot_clock: Option<&T>, spec: &ChainSpec, ) -> Result<(), CandidateError> { - if let Err(e) = self.is_compatible::(spec).await { + if let Err(e) = self.is_compatible(spec).await { *self.health.write().await = Err(e); return Err(e); } @@ -283,7 +287,7 @@ impl CandidateBeaconNode { } /// Checks if the node has the correct specification. - async fn is_compatible(&self, spec: &ChainSpec) -> Result<(), CandidateError> { + async fn is_compatible(&self, spec: &ChainSpec) -> Result<(), CandidateError> { let config = self .beacon_node .get_config_spec::() @@ -353,10 +357,10 @@ impl CandidateBeaconNode { ); } else if beacon_node_spec.fulu_fork_epoch != spec.fulu_fork_epoch { warn!( - endpoint = %self.beacon_node, - endpoint_fulu_fork_epoc = ?beacon_node_spec.fulu_fork_epoch, - hint = UPDATE_REQUIRED_LOG_HINT, - "Beacon node has mismatched Fulu fork epoch" + endpoint = %self.beacon_node, + endpoint_fulu_fork_epoch = ?beacon_node_spec.fulu_fork_epoch, + hint = UPDATE_REQUIRED_LOG_HINT, + "Beacon node has mismatched Fulu fork epoch" ); } @@ -368,17 +372,17 @@ impl CandidateBeaconNode { /// behaviour, where the failure of one candidate results in the next candidate receiving an /// identical query. #[derive(Clone, Debug)] -pub struct BeaconNodeFallback { - pub candidates: Arc>>, +pub struct BeaconNodeFallback { + pub candidates: Arc>>>, distance_tiers: BeaconNodeSyncDistanceTiers, slot_clock: Option, broadcast_topics: Vec, spec: Arc, } -impl BeaconNodeFallback { +impl BeaconNodeFallback { pub fn new( - candidates: Vec, + candidates: Vec>, config: Config, broadcast_topics: Vec, spec: Arc, @@ -460,7 +464,7 @@ impl BeaconNodeFallback { /// It is possible for a node to return an unsynced status while continuing to serve /// low quality responses. To route around this it's best to poll all connected beacon nodes. /// A previous implementation of this function polled only the unavailable BNs. - pub async fn update_all_candidates(&self) { + pub async fn update_all_candidates(&self) { // Clone the vec, so we release the read lock immediately. // `candidate.health` is behind an Arc, so this would still allow us to mutate the values. let candidates = self.candidates.read().await.clone(); @@ -468,7 +472,7 @@ impl BeaconNodeFallback { let mut nodes = Vec::with_capacity(candidates.len()); for candidate in candidates.iter() { - futures.push(candidate.refresh_health::( + futures.push(candidate.refresh_health( &self.distance_tiers, self.slot_clock.as_ref(), &self.spec, @@ -671,7 +675,7 @@ impl BeaconNodeFallback { } /// Helper functions to allow sorting candidate nodes by health. -async fn sort_nodes_by_health(nodes: &mut Vec) { +async fn sort_nodes_by_health(nodes: &mut Vec>) { // Fetch all health values. let health_results: Vec> = future::join_all(nodes.iter().map(|node| node.health())).await; @@ -689,7 +693,7 @@ async fn sort_nodes_by_health(nodes: &mut Vec) { }); // Reorder candidates based on the sorted indices. - let sorted_nodes: Vec = indices_with_health + let sorted_nodes: Vec> = indices_with_health .into_iter() .map(|(index, _)| nodes[index].clone()) .collect(); @@ -748,7 +752,7 @@ mod tests { let optimistic_status = IsOptimistic::No; let execution_status = ExecutionEngineHealth::Healthy; - fn new_candidate(index: usize) -> CandidateBeaconNode { + fn new_candidate(index: usize) -> CandidateBeaconNode { let beacon_node = BeaconNodeHttpClient::new( SensitiveUrl::parse(&format!("http://example_{index}.com")).unwrap(), Timeouts::set_all(Duration::from_secs(index as u64)), @@ -855,21 +859,21 @@ mod tests { async fn new_mock_beacon_node( index: usize, spec: &ChainSpec, - ) -> (MockBeaconNode, CandidateBeaconNode) { - let mut mock_beacon_node = MockBeaconNode::new().await; + ) -> (MockBeaconNode, CandidateBeaconNode) { + let mut mock_beacon_node = MockBeaconNode::::new().await; mock_beacon_node.mock_config_spec(spec); let beacon_node = - CandidateBeaconNode::new(mock_beacon_node.beacon_api_client.clone(), index); + CandidateBeaconNode::::new(mock_beacon_node.beacon_api_client.clone(), index); (mock_beacon_node, beacon_node) } fn create_beacon_node_fallback( - candidates: Vec, + candidates: Vec>, topics: Vec, spec: Arc, - ) -> BeaconNodeFallback { + ) -> BeaconNodeFallback { let mut beacon_node_fallback = BeaconNodeFallback::new(candidates, Config::default(), topics, spec); @@ -925,7 +929,7 @@ mod tests { sync_distance: Slot::new(0), }); - beacon_node_fallback.update_all_candidates::().await; + beacon_node_fallback.update_all_candidates().await; let candidates = beacon_node_fallback.candidates.read().await; assert_eq!( diff --git a/validator_client/doppelganger_service/Cargo.toml b/validator_client/doppelganger_service/Cargo.toml index a37dbbf303..803dd94322 100644 --- a/validator_client/doppelganger_service/Cargo.toml +++ b/validator_client/doppelganger_service/Cargo.toml @@ -8,13 +8,13 @@ authors = ["Sigma Prime "] beacon_node_fallback = { workspace = true } environment = { workspace = true } eth2 = { workspace = true } +logging = { workspace = true } parking_lot = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } task_executor = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } types = { workspace = true } -validator_store = { workspace = true } [dev-dependencies] futures = { workspace = true } diff --git a/validator_client/doppelganger_service/src/lib.rs b/validator_client/doppelganger_service/src/lib.rs index 49e70a335a..cb81b3ffc2 100644 --- a/validator_client/doppelganger_service/src/lib.rs +++ b/validator_client/doppelganger_service/src/lib.rs @@ -32,16 +32,78 @@ use beacon_node_fallback::BeaconNodeFallback; use environment::RuntimeContext; use eth2::types::LivenessResponseData; +use logging::crit; use parking_lot::RwLock; -use slog::{crit, error, info, Logger}; use slot_clock::SlotClock; use std::collections::{HashMap, HashSet}; use std::future::Future; use std::sync::Arc; use task_executor::ShutdownReason; use tokio::time::sleep; +use tracing::{error, info}; use types::{Epoch, EthSpec, PublicKeyBytes, Slot}; -use validator_store::{DoppelgangerStatus, ValidatorStore}; + +/// A wrapper around `PublicKeyBytes` which encodes information about the status of a validator +/// pubkey with regards to doppelganger protection. +#[derive(Debug, PartialEq)] +pub enum DoppelgangerStatus { + /// Doppelganger protection has approved this for signing. + /// + /// This is because the service has waited some period of time to + /// detect other instances of this key on the network. + SigningEnabled(PublicKeyBytes), + /// Doppelganger protection is still waiting to detect other instances. + /// + /// Do not use this pubkey for signing slashable messages!! + /// + /// However, it can safely be used for other non-slashable operations (e.g., collecting duties + /// or subscribing to subnets). + SigningDisabled(PublicKeyBytes), + /// This pubkey is unknown to the doppelganger service. + /// + /// This represents a serious internal error in the program. This validator will be permanently + /// disabled! + UnknownToDoppelganger(PublicKeyBytes), +} + +impl DoppelgangerStatus { + /// Only return a pubkey if it is explicitly safe for doppelganger protection. + /// + /// If `Some(pubkey)` is returned, doppelganger has declared it safe for signing. + /// + /// ## Note + /// + /// "Safe" is only best-effort by doppelganger. There is no guarantee that a doppelganger + /// doesn't exist. + pub fn only_safe(self) -> Option { + match self { + DoppelgangerStatus::SigningEnabled(pubkey) => Some(pubkey), + DoppelgangerStatus::SigningDisabled(_) => None, + DoppelgangerStatus::UnknownToDoppelganger(_) => None, + } + } + + /// Returns a key regardless of whether or not doppelganger has approved it. Such a key might be + /// used for signing non-slashable messages, duties collection or other activities. + /// + /// If the validator is unknown to doppelganger then `None` will be returned. + pub fn ignored(self) -> Option { + match self { + DoppelgangerStatus::SigningEnabled(pubkey) => Some(pubkey), + DoppelgangerStatus::SigningDisabled(pubkey) => Some(pubkey), + DoppelgangerStatus::UnknownToDoppelganger(_) => None, + } + } + + /// Only return a pubkey if it will not be used for signing due to doppelganger detection. + pub fn only_unsafe(self) -> Option { + match self { + DoppelgangerStatus::SigningEnabled(_) => None, + DoppelgangerStatus::SigningDisabled(pubkey) => Some(pubkey), + DoppelgangerStatus::UnknownToDoppelganger(pubkey) => Some(pubkey), + } + } +} struct LivenessResponses { current_epoch_responses: Vec, @@ -52,6 +114,13 @@ struct LivenessResponses { /// validators on the network. pub const DEFAULT_REMAINING_DETECTION_EPOCHS: u64 = 1; +/// This crate cannot depend on ValidatorStore as validator_store depends on this crate and +/// initialises the doppelganger protection. For this reason, we abstract the validator store +/// functions this service needs through the following trait +pub trait DoppelgangerValidatorStore { + fn get_validator_index(&self, pubkey: &PublicKeyBytes) -> Option; +} + /// Store the per-validator status of doppelganger checking. #[derive(Debug, PartialEq)] pub struct DoppelgangerState { @@ -94,9 +163,8 @@ impl DoppelgangerState { /// If the BN fails to respond to either of these requests, simply return an empty response. /// This behaviour is to help prevent spurious failures on the BN from needlessly preventing /// doppelganger progression. -async fn beacon_node_liveness( - beacon_nodes: Arc>, - log: Logger, +async fn beacon_node_liveness( + beacon_nodes: Arc>, current_epoch: Epoch, validator_indices: Vec, ) -> LivenessResponses { @@ -135,10 +203,9 @@ async fn beacon_node_liveness( .await .unwrap_or_else(|e| { crit!( - log, - "Failed previous epoch liveness query"; - "error" => %e, - "previous_epoch" => %previous_epoch, + error = %e, + previous_epoch = %previous_epoch, + "Failed previous epoch liveness query" ); // Return an empty vec. In effect, this means to keep trying to make doppelganger // progress even if some of the calls are failing. @@ -171,10 +238,9 @@ async fn beacon_node_liveness( .await .unwrap_or_else(|e| { crit!( - log, - "Failed current epoch liveness query"; - "error" => %e, - "current_epoch" => %current_epoch, + error = %e, + current_epoch = %current_epoch, + "Failed current epoch liveness query" ); // Return an empty vec. In effect, this means to keep trying to make doppelganger // progress even if some of the calls are failing. @@ -189,11 +255,10 @@ async fn beacon_node_liveness( || current_epoch_responses.len() != previous_epoch_responses.len() { error!( - log, - "Liveness query omitted validators"; - "previous_epoch_response" => previous_epoch_responses.len(), - "current_epoch_response" => current_epoch_responses.len(), - "requested" => validator_indices.len(), + previous_epoch_response = previous_epoch_responses.len(), + current_epoch_response = current_epoch_responses.len(), + requested = validator_indices.len(), + "Liveness query omitted validators" ) } @@ -203,66 +268,49 @@ async fn beacon_node_liveness( } } +#[derive(Default)] pub struct DoppelgangerService { doppelganger_states: RwLock>, - log: Logger, } impl DoppelgangerService { - pub fn new(log: Logger) -> Self { - Self { - doppelganger_states: <_>::default(), - log, - } - } - /// Starts a reoccurring future which will try to keep the doppelganger service updated each /// slot. pub fn start_update_service( service: Arc, context: RuntimeContext, validator_store: Arc, - beacon_nodes: Arc>, + beacon_nodes: Arc>, slot_clock: T, ) -> Result<(), String> where E: EthSpec, T: 'static + SlotClock, - V: ValidatorStore + Send + Sync + 'static, + V: DoppelgangerValidatorStore + Send + Sync + 'static, { // Define the `get_index` function as one that uses the validator store. - let get_index = move |pubkey| validator_store.validator_index(&pubkey); + let get_index = move |pubkey| validator_store.get_validator_index(&pubkey); // Define the `get_liveness` function as one that queries the beacon node API. - let log = service.log.clone(); let get_liveness = move |current_epoch, validator_indices| { - beacon_node_liveness::( - beacon_nodes.clone(), - log.clone(), - current_epoch, - validator_indices, - ) + beacon_node_liveness(beacon_nodes.clone(), current_epoch, validator_indices) }; let mut shutdown_sender = context.executor.shutdown_sender(); - let log = service.log.clone(); + let mut shutdown_func = move || { if let Err(e) = shutdown_sender.try_send(ShutdownReason::Failure("Doppelganger detected.")) { crit!( - log, - "Failed to send shutdown signal"; - "msg" => "terminate this process immediately", - "error" => ?e + msg = "terminate this process immediately", + error = ?e, + "Failed to send shutdown signal" ); } }; - info!( - service.log, - "Doppelganger detection service started"; - ); + info!("Doppelganger detection service started"); context.executor.spawn( async move { @@ -292,9 +340,8 @@ impl DoppelgangerService { .await { error!( - service.log, - "Error during doppelganger detection"; - "error" => ?e + error = ?e, + "Error during doppelganger detection" ); } } @@ -319,10 +366,9 @@ impl DoppelgangerService { }) .unwrap_or_else(|| { crit!( - self.log, - "Validator unknown to doppelganger service"; - "msg" => "preventing validator from performing duties", - "pubkey" => ?validator + msg = "preventing validator from performing duties", + pubkey = ?validator, + "Validator unknown to doppelganger service" ); DoppelgangerStatus::UnknownToDoppelganger(validator) }) @@ -332,18 +378,17 @@ impl DoppelgangerService { /// /// Validators added during the genesis epoch will not have doppelganger protection applied to /// them. - pub fn register_new_validator( + pub fn register_new_validator( &self, validator: PublicKeyBytes, slot_clock: &T, - slots_per_epoch: u64, ) -> Result<(), String> { let current_epoch = slot_clock // If registering before genesis, use the genesis slot. .now_or_genesis() .ok_or_else(|| "Unable to read slot clock when registering validator".to_string())? - .epoch(slots_per_epoch); - let genesis_epoch = slot_clock.genesis_slot().epoch(slots_per_epoch); + .epoch(E::slots_per_epoch()); + let genesis_epoch = slot_clock.genesis_slot().epoch(E::slots_per_epoch()); let remaining_epochs = if current_epoch <= genesis_epoch { // Disable doppelganger protection when the validator was initialized before genesis. @@ -485,11 +530,7 @@ impl DoppelgangerService { // Resolve the index from the server response back to a public key. let Some(pubkey) = indices_map.get(&response.index) else { - crit!( - self.log, - "Inconsistent indices map"; - "validator_index" => response.index, - ); + crit!(validator_index = response.index, "Inconsistent indices map"); // Skip this result if an inconsistency is detected. continue; }; @@ -499,9 +540,8 @@ impl DoppelgangerService { state.next_check_epoch } else { crit!( - self.log, - "Inconsistent doppelganger state"; - "validator_pubkey" => ?pubkey, + validator_pubkey = ?pubkey, + "Inconsistent doppelganger state" ); // Skip this result if an inconsistency is detected. continue; @@ -515,15 +555,14 @@ impl DoppelgangerService { let violators_exist = !violators.is_empty(); if violators_exist { crit!( - self.log, - "Doppelganger(s) detected"; - "msg" => "A doppelganger occurs when two different validator clients run the \ - same public key. This validator client detected another instance of a local \ - validator on the network and is shutting down to prevent potential slashable \ - offences. Ensure that you are not running a duplicate or overlapping \ - validator client", - "doppelganger_indices" => ?violators - ) + msg = "A doppelganger occurs when two different validator clients run the \ + same public key. This validator client detected another instance of a local \ + validator on the network and is shutting down to prevent potential slashable \ + offences. Ensure that you are not running a duplicate or overlapping \ + validator client", + doppelganger_indices = ?violators, + "Doppelganger(s) detected" + ); } // The concept of "epoch satisfaction" is that for some epoch `e` we are *satisfied* that @@ -598,19 +637,17 @@ impl DoppelgangerService { doppelganger_state.complete_detection_in_epoch(previous_epoch); info!( - self.log, - "Found no doppelganger"; - "further_checks_remaining" => doppelganger_state.remaining_epochs, - "epoch" => response.epoch, - "validator_index" => response.index + further_checks_remaining = doppelganger_state.remaining_epochs, + epoch = %response.epoch, + validator_index = response.index, + "Found no doppelganger" ); if doppelganger_state.remaining_epochs == 0 { info!( - self.log, - "Doppelganger detection complete"; - "msg" => "starting validator", - "validator_index" => response.index + msg = "starting validator", + validator_index = response.index, + "Doppelganger detection complete" ); } } @@ -629,7 +666,6 @@ impl DoppelgangerService { mod test { use super::*; use futures::executor::block_on; - use logging::test_logger; use slot_clock::TestingSlotClock; use std::future; use std::time::Duration; @@ -637,7 +673,6 @@ mod test { test_utils::{SeedableRng, TestRandom, XorShiftRng}, MainnetEthSpec, }; - use validator_store::DoppelgangerStatus; const DEFAULT_VALIDATORS: usize = 8; @@ -674,13 +709,12 @@ mod test { fn build(self) -> TestScenario { let mut rng = XorShiftRng::from_seed([42; 16]); let slot_clock = TestingSlotClock::new(Slot::new(0), GENESIS_TIME, SLOT_DURATION); - let log = test_logger(); TestScenario { validators: (0..self.validator_count) .map(|_| PublicKeyBytes::random_for_test(&mut rng)) .collect(), - doppelganger: DoppelgangerService::new(log), + doppelganger: DoppelgangerService::default(), slot_clock, } } @@ -739,7 +773,7 @@ mod test { .expect("index should exist"); self.doppelganger - .register_new_validator(pubkey, &self.slot_clock, E::slots_per_epoch()) + .register_new_validator::(pubkey, &self.slot_clock) .unwrap(); self.doppelganger .doppelganger_states diff --git a/validator_client/graffiti_file/Cargo.toml b/validator_client/graffiti_file/Cargo.toml index 8868f5aec8..b3bbeb1fd7 100644 --- a/validator_client/graffiti_file/Cargo.toml +++ b/validator_client/graffiti_file/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" [dependencies] bls = { workspace = true } serde = { workspace = true } -slog = { workspace = true } +tracing = { workspace = true } types = { workspace = true } [dev-dependencies] diff --git a/validator_client/graffiti_file/src/lib.rs b/validator_client/graffiti_file/src/lib.rs index 7cab504bf8..86f582aa38 100644 --- a/validator_client/graffiti_file/src/lib.rs +++ b/validator_client/graffiti_file/src/lib.rs @@ -1,11 +1,11 @@ +use bls::PublicKeyBytes; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs::File; use std::io::{prelude::*, BufReader}; use std::path::PathBuf; use std::str::FromStr; - -use bls::PublicKeyBytes; +use tracing::warn; use types::{graffiti::GraffitiString, Graffiti}; #[derive(Debug)] @@ -111,10 +111,14 @@ pub fn determine_graffiti( validator_definition_graffiti: Option, graffiti_flag: Option, ) -> Option { - // TODO when merging make sure logging on failure is back: - // warn!(log, "Failed to read graffiti file"; "error" => ?e); graffiti_file - .and_then(|mut g| g.load_graffiti(validator_pubkey).unwrap_or(None)) + .and_then(|mut g| match g.load_graffiti(validator_pubkey) { + Ok(g) => g, + Err(e) => { + warn!(error = ?e, "Failed to read graffiti file"); + None + } + }) .or(validator_definition_graffiti) .or(graffiti_flag) } diff --git a/validator_client/http_api/Cargo.toml b/validator_client/http_api/Cargo.toml index db93c9d4dc..482212d890 100644 --- a/validator_client/http_api/Cargo.toml +++ b/validator_client/http_api/Cargo.toml @@ -16,35 +16,35 @@ deposit_contract = { workspace = true } directory = { workspace = true } dirs = { workspace = true } doppelganger_service = { workspace = true } -eth2 = { workspace = true } -eth2_keystore = { workspace = true } +eth2 = { workspace = true } +eth2_keystore = { workspace = true } ethereum_serde_utils = { workspace = true } filesystem = { workspace = true } graffiti_file = { workspace = true } health_metrics = { workspace = true } initialized_validators = { workspace = true } -lighthouse_validator_store = { workspace = true } lighthouse_version = { workspace = true } logging = { workspace = true } parking_lot = { workspace = true } rand = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } signing_method = { workspace = true } slashing_protection = { workspace = true } -slog = { workspace = true } -slot_clock = { workspace = true } -sysinfo = { workspace = true } -system_health = { workspace = true } -task_executor = { workspace = true } -tempfile = { workspace = true } -tokio = { workspace = true } -tokio-stream = { workspace = true } -types = { workspace = true } -url = { workspace = true } -validator_dir = { workspace = true } -validator_services = { workspace = true } -validator_store = { workspace = true } +slot_clock = { workspace = true } +sysinfo = { workspace = true } +system_health = { workspace = true } +task_executor = { workspace = true } +tempfile = { workspace = true } +tokio = { workspace = true } +tokio-stream = { workspace = true } +tracing = { workspace = true } +types = { workspace = true } +url = { workspace = true } +validator_dir = { workspace = true } +validator_services = { workspace = true } +validator_store = { workspace = true } warp = { workspace = true } warp_utils = { workspace = true } zeroize = { workspace = true } diff --git a/validator_client/http_api/src/create_signed_voluntary_exit.rs b/validator_client/http_api/src/create_signed_voluntary_exit.rs index 0c714715a7..7a9dc798d6 100644 --- a/validator_client/http_api/src/create_signed_voluntary_exit.rs +++ b/validator_client/http_api/src/create_signed_voluntary_exit.rs @@ -1,18 +1,16 @@ use bls::{PublicKey, PublicKeyBytes}; use eth2::types::GenericResponse; -use lighthouse_validator_store::LighthouseValidatorStore; -use slog::{info, Logger}; use slot_clock::SlotClock; use std::sync::Arc; +use tracing::info; use types::{Epoch, EthSpec, SignedVoluntaryExit, VoluntaryExit}; use validator_store::ValidatorStore; pub async fn create_signed_voluntary_exit( pubkey: PublicKey, maybe_epoch: Option, - validator_store: Arc>, + validator_store: Arc>, slot_clock: T, - log: Logger, ) -> Result, warp::Rejection> { let epoch = match maybe_epoch { Some(epoch) => epoch, @@ -46,10 +44,9 @@ pub async fn create_signed_voluntary_exit pubkey_bytes.as_hex_string(), - "epoch" => epoch + validator = pubkey_bytes.as_hex_string(), + %epoch, + "Signing voluntary exit" ); let signed_voluntary_exit = validator_store diff --git a/validator_client/http_api/src/create_validator.rs b/validator_client/http_api/src/create_validator.rs index 278274198d..f90a1057a4 100644 --- a/validator_client/http_api/src/create_validator.rs +++ b/validator_client/http_api/src/create_validator.rs @@ -5,11 +5,12 @@ use account_utils::{ random_mnemonic, random_password, }; use eth2::lighthouse_vc::types::{self as api_types}; -use lighthouse_validator_store::LighthouseValidatorStore; use slot_clock::SlotClock; use std::path::{Path, PathBuf}; -use types::{ChainSpec, EthSpec}; +use types::ChainSpec; +use types::EthSpec; use validator_dir::{keystore_password_path, Builder as ValidatorDirBuilder}; +use validator_store::ValidatorStore; use zeroize::Zeroizing; /// Create some validator EIP-2335 keystores and store them on disk. Then, enroll the validators in @@ -29,7 +30,7 @@ pub async fn create_validators_mnemonic, T: 'static + SlotClock, validator_requests: &[api_types::ValidatorRequest], validator_dir: P, secrets_dir: Option, - validator_store: &LighthouseValidatorStore, + validator_store: &ValidatorStore, spec: &ChainSpec, ) -> Result<(Vec, Mnemonic), warp::Rejection> { let mnemonic = mnemonic_opt.unwrap_or_else(random_mnemonic); @@ -177,7 +178,7 @@ pub async fn create_validators_mnemonic, T: 'static + SlotClock, pub async fn create_validators_web3signer( validators: Vec, - validator_store: &LighthouseValidatorStore, + validator_store: &ValidatorStore, ) -> Result<(), warp::Rejection> { for validator in validators { validator_store diff --git a/validator_client/http_api/src/graffiti.rs b/validator_client/http_api/src/graffiti.rs index 4372b14b04..86238a697c 100644 --- a/validator_client/http_api/src/graffiti.rs +++ b/validator_client/http_api/src/graffiti.rs @@ -1,12 +1,12 @@ use bls::PublicKey; -use lighthouse_validator_store::LighthouseValidatorStore; use slot_clock::SlotClock; use std::sync::Arc; use types::{graffiti::GraffitiString, EthSpec, Graffiti}; +use validator_store::ValidatorStore; pub fn get_graffiti( validator_pubkey: PublicKey, - validator_store: Arc>, + validator_store: Arc>, graffiti_flag: Option, ) -> Result { let initialized_validators_rw_lock = validator_store.initialized_validators(); @@ -29,7 +29,7 @@ pub fn get_graffiti( pub fn set_graffiti( validator_pubkey: PublicKey, graffiti: GraffitiString, - validator_store: Arc>, + validator_store: Arc>, ) -> Result<(), warp::Rejection> { let initialized_validators_rw_lock = validator_store.initialized_validators(); let mut initialized_validators = initialized_validators_rw_lock.write(); @@ -55,7 +55,7 @@ pub fn set_graffiti( pub fn delete_graffiti( validator_pubkey: PublicKey, - validator_store: Arc>, + validator_store: Arc>, ) -> Result<(), warp::Rejection> { let initialized_validators_rw_lock = validator_store.initialized_validators(); let mut initialized_validators = initialized_validators_rw_lock.write(); diff --git a/validator_client/http_api/src/keystores.rs b/validator_client/http_api/src/keystores.rs index 9b57bbd557..c2bcfe5ab4 100644 --- a/validator_client/http_api/src/keystores.rs +++ b/validator_client/http_api/src/keystores.rs @@ -10,22 +10,22 @@ use eth2::lighthouse_vc::{ }; use eth2_keystore::Keystore; use initialized_validators::{Error, InitializedValidators}; -use lighthouse_validator_store::LighthouseValidatorStore; use signing_method::SigningMethod; -use slog::{info, warn, Logger}; use slot_clock::SlotClock; use std::path::PathBuf; use std::sync::Arc; use task_executor::TaskExecutor; use tokio::runtime::Handle; +use tracing::{info, warn}; use types::{EthSpec, PublicKeyBytes}; use validator_dir::{keystore_password_path, Builder as ValidatorDirBuilder}; +use validator_store::ValidatorStore; use warp::Rejection; use warp_utils::reject::{custom_bad_request, custom_server_error}; use zeroize::Zeroizing; pub fn list( - validator_store: Arc>, + validator_store: Arc>, ) -> ListKeystoresResponse { let initialized_validators_rwlock = validator_store.initialized_validators(); let initialized_validators = initialized_validators_rwlock.read(); @@ -62,9 +62,8 @@ pub fn import( request: ImportKeystoresRequest, validator_dir: PathBuf, secrets_dir: Option, - validator_store: Arc>, + validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { // Check request validity. This is the only cases in which we should return a 4xx code. if request.keystores.len() != request.passwords.len() { @@ -88,18 +87,14 @@ pub fn import( .iter() .any(|data| data.pubkey == pubkey_bytes) { - warn!( - log, - "Slashing protection data not provided"; - "public_key" => ?public_key, - ); + warn!(?public_key, "Slashing protection data not provided"); } } } validator_store.import_slashing_protection(slashing_protection) } else { - warn!(log, "No slashing protection data provided with keystores"); + warn!("No slashing protection data provided with keystores"); Ok(()) }; @@ -122,7 +117,7 @@ pub fn import( ) } else if let Some(handle) = task_executor.handle() { // Import the keystore. - match import_single_keystore::<_, E>( + match import_single_keystore( keystore, password, validator_dir.clone(), @@ -133,10 +128,9 @@ pub fn import( Ok(status) => Status::ok(status), Err(e) => { warn!( - log, - "Error importing keystore, skipped"; - "pubkey" => pubkey_str, - "error" => ?e, + pubkey = pubkey_str, + error = ?e, + "Error importing keystore, skipped" ); Status::error(ImportKeystoreStatus::Error, e) } @@ -157,9 +151,8 @@ pub fn import( if successful_import > 0 { info!( - log, - "Imported keystores via standard HTTP API"; - "count" => successful_import, + count = successful_import, + "Imported keystores via standard HTTP API" ); } @@ -171,7 +164,7 @@ fn import_single_keystore( password: Zeroizing, validator_dir_path: PathBuf, secrets_dir: Option, - validator_store: &LighthouseValidatorStore, + validator_store: &ValidatorStore, handle: Handle, ) -> Result { // Check if the validator key already exists, erroring if it is a remote signer validator. @@ -241,11 +234,10 @@ fn import_single_keystore( pub fn delete( request: DeleteKeystoresRequest, - validator_store: Arc>, + validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { - let export_response = export(request, validator_store, task_executor, log.clone())?; + let export_response = export(request, validator_store, task_executor)?; // Check the status is Deleted to confirm deletion is successful, then only display the log let successful_deletion = export_response @@ -256,9 +248,8 @@ pub fn delete( if successful_deletion > 0 { info!( - log, - "Deleted keystore via standard HTTP API"; - "count" => successful_deletion, + count = successful_deletion, + "Deleted keystore via standard HTTP API" ); } @@ -274,9 +265,8 @@ pub fn delete( pub fn export( request: DeleteKeystoresRequest, - validator_store: Arc>, + validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { // Remove from initialized validators. let initialized_validators_rwlock = validator_store.initialized_validators(); @@ -294,10 +284,9 @@ pub fn export( Ok(status) => status, Err(error) => { warn!( - log, - "Error deleting keystore"; - "pubkey" => ?pubkey_bytes, - "error" => ?error, + pubkey = ?pubkey_bytes, + ?error, + "Error deleting keystore" ); SingleExportKeystoresResponse { status: Status::error(DeleteKeystoreStatus::Error, error), diff --git a/validator_client/http_api/src/lib.rs b/validator_client/http_api/src/lib.rs index 7f59938526..5bb4747bfe 100644 --- a/validator_client/http_api/src/lib.rs +++ b/validator_client/http_api/src/lib.rs @@ -13,7 +13,6 @@ use graffiti::{delete_graffiti, get_graffiti, set_graffiti}; use create_signed_voluntary_exit::create_signed_voluntary_exit; use graffiti_file::{determine_graffiti, GraffitiFile}; -use lighthouse_validator_store::LighthouseValidatorStore; use validator_store::ValidatorStore; use account_utils::{ @@ -35,13 +34,14 @@ use eth2::lighthouse_vc::{ }; use health_metrics::observe::Observe; use lighthouse_version::version_with_platform; +use logging::crit; use logging::SSELoggingComponents; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; -use slog::{crit, info, warn, Logger}; use slot_clock::SlotClock; use std::collections::HashMap; use std::future::Future; +use std::marker::PhantomData; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; @@ -49,6 +49,7 @@ use sysinfo::{System, SystemExt}; use system_health::observe_system_health_vc; use task_executor::TaskExecutor; use tokio_stream::{wrappers::BroadcastStream, StreamExt}; +use tracing::{info, warn}; use types::{ChainSpec, ConfigAndPreset, EthSpec}; use validator_dir::Builder as ValidatorDirBuilder; use validator_services::block_service::BlockService; @@ -76,20 +77,20 @@ impl From for Error { /// A wrapper around all the items required to spawn the HTTP server. /// /// The server will gracefully handle the case where any fields are `None`. -pub struct Context { +pub struct Context { pub task_executor: TaskExecutor, pub api_secret: ApiSecret, - pub block_service: Option, T>>, - pub validator_store: Option>>, + pub block_service: Option>, + pub validator_store: Option>>, pub validator_dir: Option, pub secrets_dir: Option, pub graffiti_file: Option, pub graffiti_flag: Option, pub spec: Arc, pub config: Config, - pub log: Logger, pub sse_logging_components: Option, pub slot_clock: T, + pub _phantom: PhantomData, } /// Configuration for the HTTP server. @@ -147,7 +148,6 @@ pub fn serve( let config = &ctx.config; let allow_keystore_export = config.allow_keystore_export; let store_passwords_in_secrets_dir = config.store_passwords_in_secrets_dir; - let log = ctx.log.clone(); // Configure CORS. let cors_builder = { @@ -164,7 +164,7 @@ pub fn serve( // Sanity check. if !config.enabled { - crit!(log, "Cannot start disabled metrics HTTP server"); + crit!("Cannot start disabled metrics HTTP server"); return Err(Error::Other( "A disabled metrics server should not be started".to_string(), )); @@ -178,9 +178,8 @@ pub fn serve( Ok(abs_path) => api_token_path = abs_path, Err(e) => { warn!( - log, - "Error canonicalizing token path"; - "error" => ?e, + error = ?e, + "Error canonicalizing token path" ); } }; @@ -238,9 +237,6 @@ pub fn serve( let inner_graffiti_flag = ctx.graffiti_flag; let graffiti_flag_filter = warp::any().map(move || inner_graffiti_flag); - let inner_ctx = ctx.clone(); - let log_filter = warp::any().map(move || inner_ctx.log.clone()); - let inner_slot_clock = ctx.slot_clock.clone(); let slot_clock_filter = warp::any().map(move || inner_slot_clock.clone()); @@ -324,7 +320,7 @@ pub fn serve( .and(warp::path("validators")) .and(warp::path::end()) .and(validator_store_filter.clone()) - .then(|validator_store: Arc>| { + .then(|validator_store: Arc>| { blocking_json_task(move || { let validators = validator_store .initialized_validators() @@ -349,7 +345,7 @@ pub fn serve( .and(warp::path::end()) .and(validator_store_filter.clone()) .then( - |validator_pubkey: PublicKey, validator_store: Arc>| { + |validator_pubkey: PublicKey, validator_store: Arc>| { blocking_json_task(move || { let validator = validator_store .initialized_validators() @@ -398,12 +394,10 @@ pub fn serve( .and(validator_store_filter.clone()) .and(graffiti_file_filter.clone()) .and(graffiti_flag_filter) - .and(log_filter.clone()) .then( - |validator_store: Arc>, + |validator_store: Arc>, graffiti_file: Option, - graffiti_flag: Option, - _log| { + graffiti_flag: Option| { blocking_json_task(move || { let mut result = HashMap::new(); for (key, graffiti_definition) in validator_store @@ -430,35 +424,33 @@ pub fn serve( .and(warp::path("fallback_health")) .and(warp::path::end()) .and(block_service_filter.clone()) - .then( - |block_filter: BlockService, T>| async move { - let mut result: HashMap> = HashMap::new(); + .then(|block_filter: BlockService| async move { + let mut result: HashMap> = HashMap::new(); - let mut beacon_nodes = Vec::new(); - for node in &*block_filter.beacon_nodes.candidates.read().await { - beacon_nodes.push(CandidateInfo { + let mut beacon_nodes = Vec::new(); + for node in &*block_filter.beacon_nodes.candidates.read().await { + beacon_nodes.push(CandidateInfo { + index: node.index, + endpoint: node.beacon_node.to_string(), + health: *node.health.read().await, + }); + } + result.insert("beacon_nodes".to_string(), beacon_nodes); + + if let Some(proposer_nodes_list) = &block_filter.proposer_nodes { + let mut proposer_nodes = Vec::new(); + for node in &*proposer_nodes_list.candidates.read().await { + proposer_nodes.push(CandidateInfo { index: node.index, endpoint: node.beacon_node.to_string(), health: *node.health.read().await, }); } - result.insert("beacon_nodes".to_string(), beacon_nodes); + result.insert("proposer_nodes".to_string(), proposer_nodes); + } - if let Some(proposer_nodes_list) = &block_filter.proposer_nodes { - let mut proposer_nodes = Vec::new(); - for node in &*proposer_nodes_list.candidates.read().await { - proposer_nodes.push(CandidateInfo { - index: node.index, - endpoint: node.beacon_node.to_string(), - health: *node.health.read().await, - }); - } - result.insert("proposer_nodes".to_string(), proposer_nodes); - } - - blocking_json_task(move || Ok(api_types::GenericResponse::from(result))).await - }, - ); + blocking_json_task(move || Ok(api_types::GenericResponse::from(result))).await + }); // POST lighthouse/validators/ let post_validators = warp::path("lighthouse") @@ -474,14 +466,14 @@ pub fn serve( move |body: Vec, validator_dir: PathBuf, secrets_dir: PathBuf, - validator_store: Arc>, + validator_store: Arc>, spec: Arc, task_executor: TaskExecutor| { blocking_json_task(move || { let secrets_dir = store_passwords_in_secrets_dir.then_some(secrets_dir); if let Some(handle) = task_executor.handle() { let (validators, mnemonic) = - handle.block_on(create_validators_mnemonic::<_, _, E>( + handle.block_on(create_validators_mnemonic( None, None, &body, @@ -519,7 +511,7 @@ pub fn serve( move |body: api_types::CreateValidatorsMnemonicRequest, validator_dir: PathBuf, secrets_dir: PathBuf, - validator_store: Arc>, + validator_store: Arc>, spec: Arc, task_executor: TaskExecutor| { blocking_json_task(move || { @@ -533,7 +525,7 @@ pub fn serve( )) })?; let (validators, _mnemonic) = - handle.block_on(create_validators_mnemonic::<_, _, E>( + handle.block_on(create_validators_mnemonic( Some(mnemonic), Some(body.key_derivation_path_offset), &body.validators, @@ -566,7 +558,7 @@ pub fn serve( move |body: api_types::KeystoreValidatorsPostRequest, validator_dir: PathBuf, secrets_dir: PathBuf, - validator_store: Arc>, + validator_store: Arc>, task_executor: TaskExecutor| { blocking_json_task(move || { // Check to ensure the password is correct. @@ -652,7 +644,7 @@ pub fn serve( .and(task_executor_filter.clone()) .then( |body: Vec, - validator_store: Arc>, + validator_store: Arc>, task_executor: TaskExecutor| { blocking_json_task(move || { if let Some(handle) = task_executor.handle() { @@ -680,7 +672,7 @@ pub fn serve( ), }) .collect(); - handle.block_on(create_validators_web3signer::<_, E>( + handle.block_on(create_validators_web3signer( web3signers, &validator_store, ))?; @@ -706,7 +698,7 @@ pub fn serve( .then( |validator_pubkey: PublicKey, body: api_types::ValidatorPatchRequest, - validator_store: Arc>, + validator_store: Arc>, graffiti_file: Option, task_executor: TaskExecutor| { blocking_json_task(move || { @@ -767,7 +759,7 @@ pub fn serve( // Disabling an already disabled validator *with no other changes* is a // no-op. (Some(false), None) - if body.enabled.map_or(true, |enabled| !enabled) + if body.enabled.is_none_or(|enabled| !enabled) && body.gas_limit.is_none() && body.builder_boost_factor.is_none() && body.builder_proposals.is_none() @@ -834,11 +826,10 @@ pub fn serve( .and(warp::body::json()) .and(validator_store_filter.clone()) .and(task_executor_filter.clone()) - .and(log_filter.clone()) - .then(move |request, validator_store, task_executor, log| { + .then(move |request, validator_store, task_executor| { blocking_json_task(move || { if allow_keystore_export { - keystores::export(request, validator_store, task_executor, log) + keystores::export(request, validator_store, task_executor) } else { Err(warp_utils::reject::custom_bad_request( "keystore export is disabled".to_string(), @@ -860,7 +851,7 @@ pub fn serve( .and(warp::path::end()) .and(validator_store_filter.clone()) .then( - |validator_pubkey: PublicKey, validator_store: Arc>| { + |validator_pubkey: PublicKey, validator_store: Arc>| { blocking_json_task(move || { if validator_store .initialized_validators() @@ -901,7 +892,7 @@ pub fn serve( .then( |validator_pubkey: PublicKey, request: api_types::UpdateFeeRecipientRequest, - validator_store: Arc>| { + validator_store: Arc>| { blocking_json_task(move || { if validator_store .initialized_validators() @@ -937,7 +928,7 @@ pub fn serve( .and(warp::path::end()) .and(validator_store_filter.clone()) .then( - |validator_pubkey: PublicKey, validator_store: Arc>| { + |validator_pubkey: PublicKey, validator_store: Arc>| { blocking_json_task(move || { if validator_store .initialized_validators() @@ -973,7 +964,7 @@ pub fn serve( .and(warp::path::end()) .and(validator_store_filter.clone()) .then( - |validator_pubkey: PublicKey, validator_store: Arc>| { + |validator_pubkey: PublicKey, validator_store: Arc>| { blocking_json_task(move || { if validator_store .initialized_validators() @@ -1006,7 +997,7 @@ pub fn serve( .then( |validator_pubkey: PublicKey, request: api_types::UpdateGasLimitRequest, - validator_store: Arc>| { + validator_store: Arc>| { blocking_json_task(move || { if validator_store .initialized_validators() @@ -1042,7 +1033,7 @@ pub fn serve( .and(warp::path::end()) .and(validator_store_filter.clone()) .then( - |validator_pubkey: PublicKey, validator_store: Arc>| { + |validator_pubkey: PublicKey, validator_store: Arc>| { blocking_json_task(move || { if validator_store .initialized_validators() @@ -1079,24 +1070,21 @@ pub fn serve( .and(warp::path::end()) .and(validator_store_filter.clone()) .and(slot_clock_filter) - .and(log_filter.clone()) .and(task_executor_filter.clone()) .then( |pubkey: PublicKey, query: api_types::VoluntaryExitQuery, - validator_store: Arc>, + validator_store: Arc>, slot_clock: T, - log, task_executor: TaskExecutor| { blocking_json_task(move || { if let Some(handle) = task_executor.handle() { let signed_voluntary_exit = - handle.block_on(create_signed_voluntary_exit::( + handle.block_on(create_signed_voluntary_exit( pubkey, query.epoch, validator_store, slot_clock, - log, ))?; Ok(signed_voluntary_exit) } else { @@ -1118,7 +1106,7 @@ pub fn serve( .and(graffiti_flag_filter) .then( |pubkey: PublicKey, - validator_store: Arc>, + validator_store: Arc>, graffiti_flag: Option| { blocking_json_task(move || { let graffiti = get_graffiti(pubkey.clone(), validator_store, graffiti_flag)?; @@ -1142,7 +1130,7 @@ pub fn serve( .then( |pubkey: PublicKey, query: SetGraffitiRequest, - validator_store: Arc>, + validator_store: Arc>, graffiti_file: Option| { blocking_json_task(move || { if graffiti_file.is_some() { @@ -1167,7 +1155,7 @@ pub fn serve( .and(graffiti_file_filter.clone()) .then( |pubkey: PublicKey, - validator_store: Arc>, + validator_store: Arc>, graffiti_file: Option| { blocking_json_task(move || { if graffiti_file.is_some() { @@ -1184,7 +1172,7 @@ pub fn serve( // GET /eth/v1/keystores let get_std_keystores = std_keystores.and(validator_store_filter.clone()).then( - |validator_store: Arc>| { + |validator_store: Arc>| { blocking_json_task(move || Ok(keystores::list(validator_store))) }, ); @@ -1196,18 +1184,16 @@ pub fn serve( .and(secrets_dir_filter) .and(validator_store_filter.clone()) .and(task_executor_filter.clone()) - .and(log_filter.clone()) .then( - move |request, validator_dir, secrets_dir, validator_store, task_executor, log| { + move |request, validator_dir, secrets_dir, validator_store, task_executor| { let secrets_dir = store_passwords_in_secrets_dir.then_some(secrets_dir); blocking_json_task(move || { - keystores::import::<_, E>( + keystores::import( request, validator_dir, secrets_dir, validator_store, task_executor, - log, ) }) }, @@ -1218,16 +1204,13 @@ pub fn serve( .and(warp::body::json()) .and(validator_store_filter.clone()) .and(task_executor_filter.clone()) - .and(log_filter.clone()) - .then(|request, validator_store, task_executor, log| { - blocking_json_task(move || { - keystores::delete(request, validator_store, task_executor, log) - }) + .then(|request, validator_store, task_executor| { + blocking_json_task(move || keystores::delete(request, validator_store, task_executor)) }); // GET /eth/v1/remotekeys let get_std_remotekeys = std_remotekeys.and(validator_store_filter.clone()).then( - |validator_store: Arc>| { + |validator_store: Arc>| { blocking_json_task(move || Ok(remotekeys::list(validator_store))) }, ); @@ -1237,11 +1220,8 @@ pub fn serve( .and(warp::body::json()) .and(validator_store_filter.clone()) .and(task_executor_filter.clone()) - .and(log_filter.clone()) - .then(|request, validator_store, task_executor, log| { - blocking_json_task(move || { - remotekeys::import::<_, E>(request, validator_store, task_executor, log) - }) + .then(|request, validator_store, task_executor| { + blocking_json_task(move || remotekeys::import(request, validator_store, task_executor)) }); // DELETE /eth/v1/remotekeys @@ -1249,11 +1229,8 @@ pub fn serve( .and(warp::body::json()) .and(validator_store_filter) .and(task_executor_filter) - .and(log_filter.clone()) - .then(|request, validator_store, task_executor, log| { - blocking_json_task(move || { - remotekeys::delete(request, validator_store, task_executor, log) - }) + .then(|request, validator_store, task_executor| { + blocking_json_task(move || remotekeys::delete(request, validator_store, task_executor)) }); // Subscribe to get VC logs via Server side events @@ -1271,7 +1248,9 @@ pub fn serve( match msg { Ok(data) => { // Serialize to json - match data.to_json_string() { + match serde_json::to_string(&data) + .map_err(|e| format!("{:?}", e)) + { // Send the json as a Server Sent Event Ok(json) => Event::default().json_data(json).map_err(|e| { warp_utils::reject::server_sent_event_error(format!( @@ -1364,10 +1343,9 @@ pub fn serve( )?; info!( - log, - "HTTP API started"; - "listen_address" => listening_socket.to_string(), - "api_token_file" => ?api_token_path, + listen_address = listening_socket.to_string(), + ?api_token_path, + "HTTP API started" ); Ok((listening_socket, server)) diff --git a/validator_client/http_api/src/remotekeys.rs b/validator_client/http_api/src/remotekeys.rs index 802871ea5a..49d666f303 100644 --- a/validator_client/http_api/src/remotekeys.rs +++ b/validator_client/http_api/src/remotekeys.rs @@ -8,19 +8,19 @@ use eth2::lighthouse_vc::std_types::{ ListRemotekeysResponse, SingleListRemotekeysResponse, Status, }; use initialized_validators::{Error, InitializedValidators}; -use lighthouse_validator_store::LighthouseValidatorStore; -use slog::{info, warn, Logger}; use slot_clock::SlotClock; use std::sync::Arc; use task_executor::TaskExecutor; use tokio::runtime::Handle; +use tracing::{info, warn}; use types::{EthSpec, PublicKeyBytes}; use url::Url; +use validator_store::ValidatorStore; use warp::Rejection; use warp_utils::reject::custom_server_error; pub fn list( - validator_store: Arc>, + validator_store: Arc>, ) -> ListRemotekeysResponse { let initialized_validators_rwlock = validator_store.initialized_validators(); let initialized_validators = initialized_validators_rwlock.read(); @@ -50,14 +50,12 @@ pub fn list( pub fn import( request: ImportRemotekeysRequest, - validator_store: Arc>, + validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { info!( - log, - "Importing remotekeys via standard HTTP API"; - "count" => request.remote_keys.len(), + count = request.remote_keys.len(), + "Importing remotekeys via standard HTTP API" ); // Import each remotekey. Some remotekeys may fail to be imported, so we record a status for each. let mut statuses = Vec::with_capacity(request.remote_keys.len()); @@ -65,19 +63,14 @@ pub fn import( for remotekey in request.remote_keys { let status = if let Some(handle) = task_executor.handle() { // Import the keystore. - match import_single_remotekey::<_, E>( - remotekey.pubkey, - remotekey.url, - &validator_store, - handle, - ) { + match import_single_remotekey(remotekey.pubkey, remotekey.url, &validator_store, handle) + { Ok(status) => Status::ok(status), Err(e) => { warn!( - log, - "Error importing keystore, skipped"; - "pubkey" => remotekey.pubkey.to_string(), - "error" => ?e, + pubkey = remotekey.pubkey.to_string(), + error = ?e, + "Error importing keystore, skipped" ); Status::error(ImportRemotekeyStatus::Error, e) } @@ -96,7 +89,7 @@ pub fn import( fn import_single_remotekey( pubkey: PublicKeyBytes, url: String, - validator_store: &LighthouseValidatorStore, + validator_store: &ValidatorStore, handle: Handle, ) -> Result { if let Err(url_err) = Url::parse(&url) { @@ -150,14 +143,12 @@ fn import_single_remotekey( pub fn delete( request: DeleteRemotekeysRequest, - validator_store: Arc>, + validator_store: Arc>, task_executor: TaskExecutor, - log: Logger, ) -> Result { info!( - log, - "Deleting remotekeys via standard HTTP API"; - "count" => request.pubkeys.len(), + count = request.pubkeys.len(), + "Deleting remotekeys via standard HTTP API" ); // Remove from initialized validators. let initialized_validators_rwlock = validator_store.initialized_validators(); @@ -175,10 +166,9 @@ pub fn delete( Ok(status) => Status::ok(status), Err(error) => { warn!( - log, - "Error deleting keystore"; - "pubkey" => ?pubkey_bytes, - "error" => ?error, + pubkey = ?pubkey_bytes, + ?error, + "Error deleting keystore" ); Status::error(DeleteRemotekeyStatus::Error, error) } diff --git a/validator_client/http_api/src/test_utils.rs b/validator_client/http_api/src/test_utils.rs index e08c8f79af..4a5d3b6cc7 100644 --- a/validator_client/http_api/src/test_utils.rs +++ b/validator_client/http_api/src/test_utils.rs @@ -14,20 +14,19 @@ use eth2::{ use eth2_keystore::KeystoreBuilder; use initialized_validators::key_cache::{KeyCache, CACHE_FILENAME}; use initialized_validators::{InitializedValidators, OnDecryptFailure}; -use lighthouse_validator_store::{Config as ValidatorStoreConfig, LighthouseValidatorStore}; -use logging::test_logger; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME}; use slot_clock::{SlotClock, TestingSlotClock}; use std::future::Future; +use std::marker::PhantomData; use std::net::{IpAddr, Ipv4Addr}; use std::sync::Arc; use std::time::Duration; use task_executor::test_utils::TestRuntime; use tempfile::{tempdir, TempDir}; use tokio::sync::oneshot; -use validator_services::block_service::BlockService; +use validator_store::{Config as ValidatorStoreConfig, ValidatorStore}; use zeroize::Zeroizing; pub const PASSWORD_BYTES: &[u8] = &[42, 50, 37]; @@ -55,7 +54,7 @@ pub struct Web3SignerValidatorScenario { pub struct ApiTester { pub client: ValidatorClientHttpClient, pub initialized_validators: Arc>, - pub validator_store: Arc>, + pub validator_store: Arc>, pub url: SensitiveUrl, pub api_token: String, pub test_runtime: TestRuntime, @@ -70,8 +69,6 @@ impl ApiTester { } pub async fn new_with_http_config(http_config: HttpConfig) -> Self { - let log = test_logger(); - let validator_dir = tempdir().unwrap(); let secrets_dir = tempdir().unwrap(); let token_path = tempdir().unwrap().path().join(PK_FILENAME); @@ -82,7 +79,6 @@ impl ApiTester { validator_defs, validator_dir.path().into(), Default::default(), - log.clone(), ) .await .unwrap(); @@ -105,16 +101,15 @@ impl ApiTester { let test_runtime = TestRuntime::default(); - let validator_store = Arc::new(LighthouseValidatorStore::new( + let validator_store = Arc::new(ValidatorStore::<_, E>::new( initialized_validators, slashing_protection, Hash256::repeat_byte(42), spec.clone(), - Some(Arc::new(DoppelgangerService::new(log.clone()))), + Some(Arc::new(DoppelgangerService::default())), slot_clock.clone(), &config, test_runtime.task_executor.clone(), - log.clone(), )); validator_store @@ -126,7 +121,7 @@ impl ApiTester { let context = Arc::new(Context { task_executor: test_runtime.task_executor.clone(), api_secret, - block_service: None::, _>>, + block_service: None, validator_dir: Some(validator_dir.path().into()), secrets_dir: Some(secrets_dir.path().into()), validator_store: Some(validator_store.clone()), @@ -134,9 +129,9 @@ impl ApiTester { graffiti_flag: Some(Graffiti::default()), spec, config: http_config, - log, sse_logging_components: None, slot_clock, + _phantom: PhantomData, }); let ctx = context; let (shutdown_tx, shutdown_rx) = oneshot::channel(); @@ -144,7 +139,7 @@ impl ApiTester { // It's not really interesting why this triggered, just that it happened. let _ = shutdown_rx.await; }; - let (listening_socket, server) = super::serve::<_, E>(ctx, server_shutdown).unwrap(); + let (listening_socket, server) = super::serve(ctx, server_shutdown).unwrap(); tokio::spawn(server); diff --git a/validator_client/http_api/src/tests.rs b/validator_client/http_api/src/tests.rs index 62ae832794..5468718fb5 100644 --- a/validator_client/http_api/src/tests.rs +++ b/validator_client/http_api/src/tests.rs @@ -18,13 +18,12 @@ use eth2::{ Error as ApiError, }; use eth2_keystore::KeystoreBuilder; -use lighthouse_validator_store::{Config as ValidatorStoreConfig, LighthouseValidatorStore}; -use logging::test_logger; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME}; use slot_clock::{SlotClock, TestingSlotClock}; use std::future::Future; +use std::marker::PhantomData; use std::net::{IpAddr, Ipv4Addr}; use std::str::FromStr; use std::sync::Arc; @@ -32,7 +31,7 @@ use std::time::Duration; use task_executor::test_utils::TestRuntime; use tempfile::{tempdir, TempDir}; use types::graffiti::GraffitiString; -use validator_store::ValidatorStore; +use validator_store::{Config as ValidatorStoreConfig, ValidatorStore}; use zeroize::Zeroizing; const PASSWORD_BYTES: &[u8] = &[42, 50, 37]; @@ -43,7 +42,7 @@ type E = MainnetEthSpec; struct ApiTester { client: ValidatorClientHttpClient, initialized_validators: Arc>, - validator_store: Arc>, + validator_store: Arc>, url: SensitiveUrl, slot_clock: TestingSlotClock, _validator_dir: TempDir, @@ -61,8 +60,6 @@ impl ApiTester { } pub async fn new_with_config(config: ValidatorStoreConfig) -> Self { - let log = test_logger(); - let validator_dir = tempdir().unwrap(); let secrets_dir = tempdir().unwrap(); let token_path = tempdir().unwrap().path().join("api-token.txt"); @@ -73,7 +70,6 @@ impl ApiTester { validator_defs, validator_dir.path().into(), InitializedValidatorsConfig::default(), - log.clone(), ) .await .unwrap(); @@ -95,16 +91,15 @@ impl ApiTester { let test_runtime = TestRuntime::default(); - let validator_store = Arc::new(LighthouseValidatorStore::new( + let validator_store = Arc::new(ValidatorStore::<_, E>::new( initialized_validators, slashing_protection, Hash256::repeat_byte(42), spec.clone(), - Some(Arc::new(DoppelgangerService::new(log.clone()))), + Some(Arc::new(DoppelgangerService::default())), slot_clock.clone(), &config, test_runtime.task_executor.clone(), - log.clone(), )); validator_store @@ -113,7 +108,7 @@ impl ApiTester { let initialized_validators = validator_store.initialized_validators(); - let context = Arc::new(Context::<_, E> { + let context = Arc::new(Context { task_executor: test_runtime.task_executor.clone(), api_secret, block_service: None, @@ -133,12 +128,12 @@ impl ApiTester { http_token_path: token_path, }, sse_logging_components: None, - log, slot_clock: slot_clock.clone(), + _phantom: PhantomData, }); let ctx = context.clone(); let (listening_socket, server) = - super::serve::<_, E>(ctx, test_runtime.task_executor.exit()).unwrap(); + super::serve(ctx, test_runtime.task_executor.exit()).unwrap(); tokio::spawn(server); @@ -707,7 +702,7 @@ impl ApiTester { assert_eq!( self.validator_store - .determine_builder_boost_factor(&validator.voting_pubkey), + .determine_validator_builder_boost_factor(&validator.voting_pubkey), builder_boost_factor ); @@ -717,7 +712,7 @@ impl ApiTester { pub fn assert_default_builder_boost_factor(self, builder_boost_factor: Option) -> Self { assert_eq!( self.validator_store - .determine_builder_boost_factor(&PublicKeyBytes::empty()), + .determine_default_builder_boost_factor(), builder_boost_factor ); @@ -1164,7 +1159,7 @@ async fn validator_derived_builder_boost_factor_with_process_defaults() { }) .await .assert_default_builder_boost_factor(Some(80)) - .assert_validator_derived_builder_boost_factor(0, Some(80)) + .assert_validator_derived_builder_boost_factor(0, None) .await .set_builder_proposals(0, false) .await diff --git a/validator_client/http_api/src/tests/keystores.rs b/validator_client/http_api/src/tests/keystores.rs index 5a4cfc677f..6559a2bb9e 100644 --- a/validator_client/http_api/src/tests/keystores.rs +++ b/validator_client/http_api/src/tests/keystores.rs @@ -8,13 +8,12 @@ use eth2::lighthouse_vc::{ types::Web3SignerValidatorRequest, }; use itertools::Itertools; -use lighthouse_validator_store::DEFAULT_GAS_LIMIT; use rand::{rngs::SmallRng, Rng, SeedableRng}; use slashing_protection::interchange::{Interchange, InterchangeMetadata}; use std::{collections::HashMap, path::Path}; use tokio::runtime::Handle; use types::{attestation::AttestationBase, Address}; -use validator_store::ValidatorStore; +use validator_store::DEFAULT_GAS_LIMIT; use zeroize::Zeroizing; fn new_keystore(password: Zeroizing) -> Keystore { diff --git a/validator_client/http_metrics/Cargo.toml b/validator_client/http_metrics/Cargo.toml index bc1860c6ff..f2684da4b1 100644 --- a/validator_client/http_metrics/Cargo.toml +++ b/validator_client/http_metrics/Cargo.toml @@ -6,16 +6,17 @@ authors = ["Sigma Prime "] [dependencies] health_metrics = { workspace = true } -lighthouse_validator_store = { workspace = true } lighthouse_version = { workspace = true } +logging = { workspace = true } malloc_utils = { workspace = true } metrics = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -slog = { workspace = true } slot_clock = { workspace = true } +tracing = { workspace = true } types = { workspace = true } validator_metrics = { workspace = true } validator_services = { workspace = true } +validator_store = { workspace = true } warp = { workspace = true } warp_utils = { workspace = true } diff --git a/validator_client/http_metrics/src/lib.rs b/validator_client/http_metrics/src/lib.rs index 0fb12d2a5f..6bf18e7b93 100644 --- a/validator_client/http_metrics/src/lib.rs +++ b/validator_client/http_metrics/src/lib.rs @@ -2,19 +2,20 @@ //! //! For other endpoints, see the `http_api` crate. -use lighthouse_validator_store::LighthouseValidatorStore; use lighthouse_version::version_with_platform; +use logging::crit; use malloc_utils::scrape_allocator_metrics; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; -use slog::{crit, info, Logger}; use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::future::Future; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; +use tracing::info; use types::EthSpec; use validator_services::duties_service::DutiesService; +use validator_store::ValidatorStore; use warp::{http::Response, Filter}; #[derive(Debug)] @@ -35,22 +36,19 @@ impl From for Error { } } -type ValidatorStore = LighthouseValidatorStore; - /// Contains objects which have shared access from inside/outside of the metrics server. -pub struct Shared { - pub validator_store: Option>>, - pub duties_service: Option, SystemTimeSlotClock>>>, +pub struct Shared { + pub validator_store: Option>>, + pub duties_service: Option>>, pub genesis_time: Option, } /// A wrapper around all the items required to spawn the HTTP server. /// /// The server will gracefully handle the case where any fields are `None`. -pub struct Context { +pub struct Context { pub config: Config, pub shared: RwLock>, - pub log: Logger, } /// Configuration for the HTTP server. @@ -95,7 +93,6 @@ pub fn serve( shutdown: impl Future + Send + Sync + 'static, ) -> Result<(SocketAddr, impl Future), Error> { let config = &ctx.config; - let log = ctx.log.clone(); // Configure CORS. let cors_builder = { @@ -112,7 +109,7 @@ pub fn serve( // Sanity check. if !config.enabled { - crit!(log, "Cannot start disabled metrics HTTP server"); + crit!("Cannot start disabled metrics HTTP server"); return Err(Error::Other( "A disabled metrics server should not be started".to_string(), )); @@ -124,7 +121,7 @@ pub fn serve( .map(move || inner_ctx.clone()) .and_then(|ctx: Arc>| async move { Ok::<_, warp::Rejection>( - gather_prometheus_metrics::(&ctx) + gather_prometheus_metrics(&ctx) .map(|body| { Response::builder() .status(200) @@ -153,9 +150,8 @@ pub fn serve( )?; info!( - log, - "Metrics HTTP server started"; - "listen_address" => listening_socket.to_string(), + listen_address = listening_socket.to_string(), + "Metrics HTTP server started" ); Ok((listening_socket, server)) diff --git a/validator_client/initialized_validators/Cargo.toml b/validator_client/initialized_validators/Cargo.toml index 05e85261f9..8b2ae62aea 100644 --- a/validator_client/initialized_validators/Cargo.toml +++ b/validator_client/initialized_validators/Cargo.toml @@ -18,8 +18,8 @@ reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } signing_method = { workspace = true } -slog = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } types = { workspace = true } url = { workspace = true } validator_dir = { workspace = true } diff --git a/validator_client/initialized_validators/src/lib.rs b/validator_client/initialized_validators/src/lib.rs index bd64091dae..cbc1287a85 100644 --- a/validator_client/initialized_validators/src/lib.rs +++ b/validator_client/initialized_validators/src/lib.rs @@ -22,13 +22,13 @@ use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; use reqwest::{Certificate, Client, Error as ReqwestError, Identity}; use serde::{Deserialize, Serialize}; use signing_method::SigningMethod; -use slog::{debug, error, info, warn, Logger}; use std::collections::{HashMap, HashSet}; use std::fs::{self, File}; use std::io::{self, Read}; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; +use tracing::{debug, error, info, warn}; use types::graffiti::GraffitiString; use types::{Address, Graffiti, Keypair, PublicKey, PublicKeyBytes}; use url::{ParseError, Url}; @@ -503,8 +503,6 @@ pub struct InitializedValidators { validators: HashMap, /// The clients used for communications with a remote signer. web3_signer_client_map: Option>, - /// For logging via `slog`. - log: Logger, config: Config, } @@ -514,7 +512,6 @@ impl InitializedValidators { definitions: ValidatorDefinitions, validators_dir: PathBuf, config: Config, - log: Logger, ) -> Result { let mut this = Self { validators_dir, @@ -522,7 +519,6 @@ impl InitializedValidators { validators: HashMap::default(), web3_signer_client_map: None, config, - log, }; this.update_validators().await?; Ok(this) @@ -1151,10 +1147,9 @@ impl InitializedValidators { for uuid in cache.uuids() { if !definitions_map.contains_key(uuid) { debug!( - self.log, - "Resetting the key cache"; - "keystore_uuid" => %uuid, - "reason" => "impossible to decrypt due to missing keystore", + keystore_uuid = %uuid, + reason = "impossible to decrypt due to missing keystore", + "Resetting the key cache" ); return Ok(KeyCache::new()); } @@ -1281,30 +1276,27 @@ impl InitializedValidators { self.validators .insert(init.voting_public_key().compress(), init); info!( - self.log, - "Enabled validator"; - "signing_method" => "local_keystore", - "voting_pubkey" => format!("{:?}", def.voting_public_key), + signing_method = "local_keystore", + voting_pubkey = format!("{:?}", def.voting_public_key), + "Enabled validator" ); if let Some(lockfile_path) = existing_lockfile_path { warn!( - self.log, - "Ignored stale lockfile"; - "path" => lockfile_path.display(), - "cause" => "Ungraceful shutdown (harmless) OR \ + path = ?lockfile_path.display(), + cause = "Ungraceful shutdown (harmless) OR \ non-Lighthouse client using this keystore \ - (risky)" + (risky)", + "Ignored stale lockfile" ); } } Err(e) => { error!( - self.log, - "Failed to initialize validator"; - "error" => format!("{:?}", e), - "signing_method" => "local_keystore", - "validator" => format!("{:?}", def.voting_public_key) + error = format!("{:?}", e), + signing_method = "local_keystore", + validator = format!("{:?}", def.voting_public_key), + "Failed to initialize validator" ); // Exit on an invalid validator. @@ -1327,19 +1319,17 @@ impl InitializedValidators { .insert(init.voting_public_key().compress(), init); info!( - self.log, - "Enabled validator"; - "signing_method" => "remote_signer", - "voting_pubkey" => format!("{:?}", def.voting_public_key), + signing_method = "remote_signer", + voting_pubkey = format!("{:?}", def.voting_public_key), + "Enabled validator" ); } Err(e) => { error!( - self.log, - "Failed to initialize validator"; - "error" => format!("{:?}", e), - "signing_method" => "remote_signer", - "validator" => format!("{:?}", def.voting_public_key) + error = format!("{:?}", e), + signing_method = "remote_signer", + validator = format!("{:?}", def.voting_public_key), + "Failed to initialize validator" ); // Exit on an invalid validator. @@ -1364,9 +1354,8 @@ impl InitializedValidators { } info!( - self.log, - "Disabled validator"; - "voting_pubkey" => format!("{:?}", def.voting_public_key) + voting_pubkey = format!("{:?}", def.voting_public_key), + "Disabled validator" ); } } @@ -1378,23 +1367,18 @@ impl InitializedValidators { } let validators_dir = self.validators_dir.clone(); - let log = self.log.clone(); if has_local_definitions && key_cache.is_modified() { tokio::task::spawn_blocking(move || { match key_cache.save(validators_dir) { - Err(e) => warn!( - log, - "Error during saving of key_cache"; - "err" => format!("{:?}", e) - ), - Ok(true) => info!(log, "Modified key_cache saved successfully"), + Err(e) => warn!(err = format!("{:?}", e), "Error during saving of key_cache"), + Ok(true) => info!("Modified key_cache saved successfully"), _ => {} }; }) .await .map_err(Error::TokioJoin)?; } else { - debug!(log, "Key cache not modified"); + debug!("Key cache not modified"); } // Update the enabled and total validator counts diff --git a/validator_client/lighthouse_validator_store/Cargo.toml b/validator_client/lighthouse_validator_store/Cargo.toml deleted file mode 100644 index 5d38d6dd11..0000000000 --- a/validator_client/lighthouse_validator_store/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "lighthouse_validator_store" -version = "0.1.0" -edition = { workspace = true } -authors = ["Sigma Prime "] - -[dependencies] -account_utils = { workspace = true } -beacon_node_fallback = { workspace = true } -doppelganger_service = { workspace = true } -either = { workspace = true } -environment = { workspace = true } -eth2 = { workspace = true } -initialized_validators = { workspace = true } -parking_lot = { workspace = true } -serde = { workspace = true } -signing_method = { workspace = true } -slashing_protection = { workspace = true } -slog = { workspace = true } -slot_clock = { workspace = true } -task_executor = { workspace = true } -tokio = { workspace = true } -types = { workspace = true } -validator_metrics = { workspace = true } -validator_store = { workspace = true } - -[dev-dependencies] -futures = { workspace = true } -logging = { workspace = true } diff --git a/validator_client/lighthouse_validator_store/src/lib.rs b/validator_client/lighthouse_validator_store/src/lib.rs deleted file mode 100644 index 0306015523..0000000000 --- a/validator_client/lighthouse_validator_store/src/lib.rs +++ /dev/null @@ -1,1116 +0,0 @@ -use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition}; -use doppelganger_service::DoppelgangerService; -use initialized_validators::InitializedValidators; -use parking_lot::{Mutex, RwLock}; -use serde::{Deserialize, Serialize}; -use signing_method::Error as SigningError; -use signing_method::{SignableMessage, SigningContext, SigningMethod}; -use slashing_protection::{ - interchange::Interchange, InterchangeError, NotSafe, Safe, SlashingDatabase, -}; -use slog::{crit, error, info, warn, Logger}; -use slot_clock::SlotClock; -use std::marker::PhantomData; -use std::path::Path; -use std::sync::Arc; -use task_executor::TaskExecutor; -use types::{ - graffiti::GraffitiString, AbstractExecPayload, Address, AggregateAndProof, Attestation, - BeaconBlock, BlindedPayload, ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, Fork, - Graffiti, Hash256, PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, - SignedBeaconBlock, SignedContributionAndProof, SignedRoot, SignedValidatorRegistrationData, - SignedVoluntaryExit, Slot, SyncAggregatorSelectionData, SyncCommitteeContribution, - SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, - VoluntaryExit, -}; -use validator_store::{ - DoppelgangerStatus, Error as ValidatorStoreError, ProposalData, SignedBlock, UnsignedBlock, - ValidatorStore, -}; - -pub type Error = ValidatorStoreError; - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct Config { - /// Fallback fee recipient address. - pub fee_recipient: Option
, - /// Fallback gas limit. - pub gas_limit: Option, - /// Enable use of the blinded block endpoints during proposals. - pub builder_proposals: bool, - /// Enable slashing protection even while using web3signer keys. - pub enable_web3signer_slashing_protection: bool, - /// If true, Lighthouse will prefer builder proposals, if available. - pub prefer_builder_proposals: bool, - /// Specifies the boost factor, a percentage multiplier to apply to the builder's payload value. - pub builder_boost_factor: Option, -} - -/// Number of epochs of slashing protection history to keep. -/// -/// This acts as a maximum safe-guard against clock drift. -const SLASHING_PROTECTION_HISTORY_EPOCHS: u64 = 512; - -/// Currently used as the default gas limit in execution clients. -/// -/// https://github.com/ethereum/builder-specs/issues/17 -pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000; - -pub struct LighthouseValidatorStore { - validators: Arc>, - slashing_protection: SlashingDatabase, - slashing_protection_last_prune: Arc>, - genesis_validators_root: Hash256, - spec: Arc, - log: Logger, - doppelganger_service: Option>, - slot_clock: T, - fee_recipient_process: Option
, - gas_limit: Option, - builder_proposals: bool, - enable_web3signer_slashing_protection: bool, - prefer_builder_proposals: bool, - builder_boost_factor: Option, - task_executor: TaskExecutor, - _phantom: PhantomData, -} - -impl LighthouseValidatorStore { - // All arguments are different types. Making the fields `pub` is undesired. A builder seems - // unnecessary. - #[allow(clippy::too_many_arguments)] - pub fn new( - validators: InitializedValidators, - slashing_protection: SlashingDatabase, - genesis_validators_root: Hash256, - spec: Arc, - doppelganger_service: Option>, - slot_clock: T, - config: &Config, - task_executor: TaskExecutor, - log: Logger, - ) -> Self { - Self { - validators: Arc::new(RwLock::new(validators)), - slashing_protection, - slashing_protection_last_prune: Arc::new(Mutex::new(Epoch::new(0))), - genesis_validators_root, - spec, - log, - doppelganger_service, - slot_clock, - fee_recipient_process: config.fee_recipient, - gas_limit: config.gas_limit, - builder_proposals: config.builder_proposals, - enable_web3signer_slashing_protection: config.enable_web3signer_slashing_protection, - prefer_builder_proposals: config.prefer_builder_proposals, - builder_boost_factor: config.builder_boost_factor, - task_executor, - _phantom: PhantomData, - } - } - - /// Register all local validators in doppelganger protection to try and prevent instances of - /// duplicate validators operating on the network at the same time. - /// - /// This function has no effect if doppelganger protection is disabled. - pub fn register_all_in_doppelganger_protection_if_enabled(&self) -> Result<(), String> { - if let Some(doppelganger_service) = &self.doppelganger_service { - for pubkey in self.validators.read().iter_voting_pubkeys() { - doppelganger_service.register_new_validator( - *pubkey, - &self.slot_clock, - E::slots_per_epoch(), - )? - } - } - - Ok(()) - } - - /// Returns `true` if doppelganger protection is enabled, or else `false`. - pub fn doppelganger_protection_enabled(&self) -> bool { - self.doppelganger_service.is_some() - } - - pub fn initialized_validators(&self) -> Arc> { - self.validators.clone() - } - - /// Indicates if the `voting_public_key` exists in self and is enabled. - pub fn has_validator(&self, voting_public_key: &PublicKeyBytes) -> bool { - self.validators - .read() - .validator(voting_public_key) - .is_some() - } - - /// Insert a new validator to `self`, where the validator is represented by an EIP-2335 - /// keystore on the filesystem. - #[allow(clippy::too_many_arguments)] - pub async fn add_validator_keystore>( - &self, - voting_keystore_path: P, - password_storage: PasswordStorage, - enable: bool, - graffiti: Option, - suggested_fee_recipient: Option
, - gas_limit: Option, - builder_proposals: Option, - builder_boost_factor: Option, - prefer_builder_proposals: Option, - ) -> Result { - let mut validator_def = ValidatorDefinition::new_keystore_with_password( - voting_keystore_path, - password_storage, - graffiti.map(Into::into), - suggested_fee_recipient, - gas_limit, - builder_proposals, - builder_boost_factor, - prefer_builder_proposals, - ) - .map_err(|e| format!("failed to create validator definitions: {:?}", e))?; - - validator_def.enabled = enable; - - self.add_validator(validator_def).await - } - - /// Insert a new validator to `self`. - /// - /// This function includes: - /// - /// - Adding the validator definition to the YAML file, saving it to the filesystem. - /// - Enabling the validator with the slashing protection database. - /// - If `enable == true`, starting to perform duties for the validator. - // FIXME: ignore this clippy lint until the validator store is refactored to use async locks - #[allow(clippy::await_holding_lock)] - pub async fn add_validator( - &self, - validator_def: ValidatorDefinition, - ) -> Result { - let validator_pubkey = validator_def.voting_public_key.compress(); - - self.slashing_protection - .register_validator(validator_pubkey) - .map_err(|e| format!("failed to register validator: {:?}", e))?; - - if let Some(doppelganger_service) = &self.doppelganger_service { - doppelganger_service.register_new_validator( - validator_pubkey, - &self.slot_clock, - E::slots_per_epoch(), - )?; - } - - self.validators - .write() - .add_definition_replace_disabled(validator_def.clone()) - .await - .map_err(|e| format!("Unable to add definition: {:?}", e))?; - - Ok(validator_def) - } - - /// Returns doppelganger statuses for all enabled validators. - #[allow(clippy::needless_collect)] // Collect is required to avoid holding a lock. - pub fn doppelganger_statuses(&self) -> Vec { - // Collect all the pubkeys first to avoid interleaving locks on `self.validators` and - // `self.doppelganger_service`. - let pubkeys = self - .validators - .read() - .iter_voting_pubkeys() - .cloned() - .collect::>(); - - pubkeys - .into_iter() - .map(|pubkey| { - self.doppelganger_service - .as_ref() - .map(|doppelganger_service| doppelganger_service.validator_status(pubkey)) - // Allow signing on all pubkeys if doppelganger protection is disabled. - .unwrap_or_else(|| DoppelgangerStatus::SigningEnabled(pubkey)) - }) - .collect() - } - - fn fork(&self, epoch: Epoch) -> Fork { - self.spec.fork_at_epoch(epoch) - } - - /// Returns a `SigningMethod` for `validator_pubkey` *only if* that validator is considered safe - /// by doppelganger protection. - fn doppelganger_checked_signing_method( - &self, - validator_pubkey: PublicKeyBytes, - ) -> Result, Error> { - if self.doppelganger_protection_allows_signing(validator_pubkey) { - self.validators - .read() - .signing_method(&validator_pubkey) - .ok_or(Error::UnknownPubkey(validator_pubkey)) - } else { - Err(Error::DoppelgangerProtected(validator_pubkey)) - } - } - - /// Returns a `SigningMethod` for `validator_pubkey` regardless of that validators doppelganger - /// protection status. - /// - /// ## Warning - /// - /// This method should only be used for signing non-slashable messages. - fn doppelganger_bypassed_signing_method( - &self, - validator_pubkey: PublicKeyBytes, - ) -> Result, Error> { - self.validators - .read() - .signing_method(&validator_pubkey) - .ok_or(Error::UnknownPubkey(validator_pubkey)) - } - - fn signing_context(&self, domain: Domain, signing_epoch: Epoch) -> SigningContext { - if domain == Domain::VoluntaryExit { - if self.spec.fork_name_at_epoch(signing_epoch).deneb_enabled() { - // EIP-7044 - SigningContext { - domain, - epoch: signing_epoch, - fork: Fork { - previous_version: self.spec.capella_fork_version, - current_version: self.spec.capella_fork_version, - epoch: signing_epoch, - }, - genesis_validators_root: self.genesis_validators_root, - } - } else { - SigningContext { - domain, - epoch: signing_epoch, - fork: self.fork(signing_epoch), - genesis_validators_root: self.genesis_validators_root, - } - } - } else { - SigningContext { - domain, - epoch: signing_epoch, - fork: self.fork(signing_epoch), - genesis_validators_root: self.genesis_validators_root, - } - } - } - - pub fn get_fee_recipient_defaulting(&self, fee_recipient: Option
) -> Option
{ - // If there's nothing in the file, try the process-level default value. - fee_recipient.or(self.fee_recipient_process) - } - - /// Returns the suggested_fee_recipient from `validator_definitions.yml` if any. - /// This has been pulled into a private function so the read lock is dropped easily - fn suggested_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option
{ - self.validators - .read() - .suggested_fee_recipient(validator_pubkey) - } - - /// Returns the gas limit for the given public key. The priority order for fetching - /// the gas limit is: - /// - /// 1. validator_definitions.yml - /// 2. process level gas limit - /// 3. `DEFAULT_GAS_LIMIT` - pub fn get_gas_limit(&self, validator_pubkey: &PublicKeyBytes) -> u64 { - self.get_gas_limit_defaulting(self.validators.read().gas_limit(validator_pubkey)) - } - - fn get_gas_limit_defaulting(&self, gas_limit: Option) -> u64 { - // If there is a `gas_limit` in the validator definitions yaml - // file, use that value. - gas_limit - // If there's nothing in the file, try the process-level default value. - .or(self.gas_limit) - // If there's no process-level default, use the `DEFAULT_GAS_LIMIT`. - .unwrap_or(DEFAULT_GAS_LIMIT) - } - - /// Returns a `bool` for the given public key that denotes whether this validator should use the - /// builder API. The priority order for fetching this value is: - /// - /// 1. validator_definitions.yml - /// 2. process level flag - pub fn get_builder_proposals(&self, validator_pubkey: &PublicKeyBytes) -> bool { - // If there is a `suggested_fee_recipient` in the validator definitions yaml - // file, use that value. - self.get_builder_proposals_defaulting( - self.validators.read().builder_proposals(validator_pubkey), - ) - } - - /// Returns a `u64` for the given public key that denotes the builder boost factor. The priority order for fetching this value is: - /// - /// 1. validator_definitions.yml - /// 2. process level flag - pub fn get_builder_boost_factor(&self, validator_pubkey: &PublicKeyBytes) -> Option { - self.validators - .read() - .builder_boost_factor(validator_pubkey) - .or(self.builder_boost_factor) - } - - /// Returns a `bool` for the given public key that denotes whether this validator should prefer a - /// builder payload. The priority order for fetching this value is: - /// - /// 1. validator_definitions.yml - /// 2. process level flag - pub fn get_prefer_builder_proposals(&self, validator_pubkey: &PublicKeyBytes) -> bool { - self.validators - .read() - .prefer_builder_proposals(validator_pubkey) - .unwrap_or(self.prefer_builder_proposals) - } - - fn get_builder_proposals_defaulting(&self, builder_proposals: Option) -> bool { - builder_proposals - // If there's nothing in the file, try the process-level default value. - .unwrap_or(self.builder_proposals) - } - - pub fn import_slashing_protection( - &self, - interchange: Interchange, - ) -> Result<(), InterchangeError> { - self.slashing_protection - .import_interchange_info(interchange, self.genesis_validators_root)?; - Ok(()) - } - - /// Export slashing protection data while also disabling the given keys in the database. - /// - /// If any key is unknown to the slashing protection database it will be silently omitted - /// from the result. It is the caller's responsibility to check whether all keys provided - /// had data returned for them. - pub fn export_slashing_protection_for_keys( - &self, - pubkeys: &[PublicKeyBytes], - ) -> Result { - self.slashing_protection.with_transaction(|txn| { - let known_pubkeys = pubkeys - .iter() - .filter_map(|pubkey| { - let validator_id = self - .slashing_protection - .get_validator_id_ignoring_status(txn, pubkey) - .ok()?; - - Some( - self.slashing_protection - .update_validator_status(txn, validator_id, false) - .map(|()| *pubkey), - ) - }) - .collect::, _>>()?; - self.slashing_protection.export_interchange_info_in_txn( - self.genesis_validators_root, - Some(&known_pubkeys), - txn, - ) - }) - } - - async fn sign_abstract_block>( - &self, - validator_pubkey: PublicKeyBytes, - block: BeaconBlock, - current_slot: Slot, - ) -> Result, Error> { - // Make sure the block slot is not higher than the current slot to avoid potential attacks. - if block.slot() > current_slot { - warn!( - self.log, - "Not signing block with slot greater than current slot"; - "block_slot" => block.slot().as_u64(), - "current_slot" => current_slot.as_u64() - ); - return Err(Error::GreaterThanCurrentSlot { - slot: block.slot(), - current_slot, - }); - } - - let signing_epoch = block.epoch(); - let signing_context = self.signing_context(Domain::BeaconProposer, signing_epoch); - let domain_hash = signing_context.domain_hash(&self.spec); - - let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; - - // Check for slashing conditions. - let slashing_status = if signing_method - .requires_local_slashing_protection(self.enable_web3signer_slashing_protection) - { - self.slashing_protection.check_and_insert_block_proposal( - &validator_pubkey, - &block.block_header(), - domain_hash, - ) - } else { - Ok(Safe::Valid) - }; - - match slashing_status { - // We can safely sign this block without slashing. - Ok(Safe::Valid) => { - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_BLOCKS_TOTAL, - &[validator_metrics::SUCCESS], - ); - - let signature = signing_method - .get_signature( - SignableMessage::BeaconBlock(&block), - signing_context, - &self.spec, - &self.task_executor, - ) - .await?; - Ok(SignedBeaconBlock::from_block(block, signature)) - } - Ok(Safe::SameData) => { - warn!( - self.log, - "Skipping signing of previously signed block"; - ); - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_BLOCKS_TOTAL, - &[validator_metrics::SAME_DATA], - ); - Err(Error::SameData) - } - Err(NotSafe::UnregisteredValidator(pk)) => { - warn!( - self.log, - "Not signing block for unregistered validator"; - "msg" => "Carefully consider running with --init-slashing-protection (see --help)", - "public_key" => format!("{:?}", pk) - ); - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_BLOCKS_TOTAL, - &[validator_metrics::UNREGISTERED], - ); - Err(Error::Slashable(NotSafe::UnregisteredValidator(pk))) - } - Err(e) => { - crit!( - self.log, - "Not signing slashable block"; - "error" => format!("{:?}", e) - ); - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_BLOCKS_TOTAL, - &[validator_metrics::SLASHABLE], - ); - Err(Error::Slashable(e)) - } - } - } -} - -impl ValidatorStore for LighthouseValidatorStore { - type Error = SigningError; - type E = E; - - /// Attempts to resolve the pubkey to a validator index. - /// - /// It may return `None` if the `pubkey` is: - /// - /// - Unknown. - /// - Known, but with an unknown index. - fn validator_index(&self, pubkey: &PublicKeyBytes) -> Option { - self.validators.read().get_index(pubkey) - } - - /// Returns all voting pubkeys for all enabled validators. - /// - /// The `filter_func` allows for filtering pubkeys based upon their `DoppelgangerStatus`. There - /// are two primary functions used here: - /// - /// - `DoppelgangerStatus::only_safe`: only returns pubkeys which have passed doppelganger - /// protection and are safe-enough to sign messages. - /// - `DoppelgangerStatus::ignored`: returns all the pubkeys from `only_safe` *plus* those still - /// undergoing protection. This is useful for collecting duties or other non-signing tasks. - #[allow(clippy::needless_collect)] // Collect is required to avoid holding a lock. - fn voting_pubkeys(&self, filter_func: F) -> I - where - I: FromIterator, - F: Fn(DoppelgangerStatus) -> Option, - { - // Collect all the pubkeys first to avoid interleaving locks on `self.validators` and - // `self.doppelganger_service()`. - let pubkeys = self - .validators - .read() - .iter_voting_pubkeys() - .cloned() - .collect::>(); - - pubkeys - .into_iter() - .map(|pubkey| { - self.doppelganger_service - .as_ref() - .map(|doppelganger_service| doppelganger_service.validator_status(pubkey)) - // Allow signing on all pubkeys if doppelganger protection is disabled. - .unwrap_or_else(|| DoppelgangerStatus::SigningEnabled(pubkey)) - }) - .filter_map(filter_func) - .collect() - } - - /// Check if the `validator_pubkey` is permitted by the doppleganger protection to sign - /// messages. - fn doppelganger_protection_allows_signing(&self, validator_pubkey: PublicKeyBytes) -> bool { - self.doppelganger_service - .as_ref() - // If there's no doppelganger service then we assume it is purposefully disabled and - // declare that all keys are safe with regard to it. - .map_or(true, |doppelganger_service| { - doppelganger_service - .validator_status(validator_pubkey) - .only_safe() - .is_some() - }) - } - - fn num_voting_validators(&self) -> usize { - self.validators.read().num_enabled() - } - - fn graffiti(&self, validator_pubkey: &PublicKeyBytes) -> Option { - self.validators.read().graffiti(validator_pubkey) - } - - /// Returns the fee recipient for the given public key. The priority order for fetching - /// the fee recipient is: - /// 1. validator_definitions.yml - /// 2. process level fee recipient - fn get_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option
{ - // If there is a `suggested_fee_recipient` in the validator definitions yaml - // file, use that value. - self.get_fee_recipient_defaulting(self.suggested_fee_recipient(validator_pubkey)) - } - - /// Translate the per validator `builder_proposals`, `builder_boost_factor` and - /// `prefer_builder_proposals` to a boost factor, if available. - /// - If `prefer_builder_proposals` is true, set boost factor to `u64::MAX` to indicate a - /// preference for builder payloads. - /// - If `builder_boost_factor` is a value other than None, return its value as the boost factor. - /// - If `builder_proposals` is set to false, set boost factor to 0 to indicate a preference for - /// local payloads. - /// - Else return `None` to indicate no preference between builder and local payloads. - fn determine_builder_boost_factor(&self, validator_pubkey: &PublicKeyBytes) -> Option { - let validator_prefer_builder_proposals = self - .validators - .read() - .prefer_builder_proposals(validator_pubkey); - - if matches!(validator_prefer_builder_proposals, Some(true)) { - return Some(u64::MAX); - } - - let factor = self - .validators - .read() - .builder_boost_factor(validator_pubkey) - .or_else(|| { - if matches!( - self.validators.read().builder_proposals(validator_pubkey), - Some(false) - ) { - return Some(0); - } - None - }); - - factor.or_else(|| { - if self.prefer_builder_proposals { - return Some(u64::MAX); - } - self.builder_boost_factor.or({ - if !self.builder_proposals { - Some(0) - } else { - None - } - }) - }) - } - - async fn randao_reveal( - &self, - validator_pubkey: PublicKeyBytes, - signing_epoch: Epoch, - ) -> Result { - let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; - let signing_context = self.signing_context(Domain::Randao, signing_epoch); - - let signature = signing_method - .get_signature::>( - SignableMessage::RandaoReveal(signing_epoch), - signing_context, - &self.spec, - &self.task_executor, - ) - .await?; - - Ok(signature) - } - - fn set_validator_index(&self, validator_pubkey: &PublicKeyBytes, index: u64) { - self.initialized_validators() - .write() - .set_index(validator_pubkey, index); - } - - async fn sign_block( - &self, - validator_pubkey: PublicKeyBytes, - block: UnsignedBlock, - current_slot: Slot, - ) -> Result, Error> { - match block { - UnsignedBlock::Full(block) => self - .sign_abstract_block(validator_pubkey, block, current_slot) - .await - .map(SignedBlock::Full), - UnsignedBlock::Blinded(block) => self - .sign_abstract_block(validator_pubkey, block, current_slot) - .await - .map(SignedBlock::Blinded), - } - } - - async fn sign_attestation( - &self, - validator_pubkey: PublicKeyBytes, - validator_committee_position: usize, - attestation: &mut Attestation, - current_epoch: Epoch, - ) -> Result<(), Error> { - // Make sure the target epoch is not higher than the current epoch to avoid potential attacks. - if attestation.data().target.epoch > current_epoch { - return Err(Error::GreaterThanCurrentEpoch { - epoch: attestation.data().target.epoch, - current_epoch, - }); - } - - // Get the signing method and check doppelganger protection. - let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; - - // Checking for slashing conditions. - let signing_epoch = attestation.data().target.epoch; - let signing_context = self.signing_context(Domain::BeaconAttester, signing_epoch); - let domain_hash = signing_context.domain_hash(&self.spec); - let slashing_status = if signing_method - .requires_local_slashing_protection(self.enable_web3signer_slashing_protection) - { - self.slashing_protection.check_and_insert_attestation( - &validator_pubkey, - attestation.data(), - domain_hash, - ) - } else { - Ok(Safe::Valid) - }; - - match slashing_status { - // We can safely sign this attestation. - Ok(Safe::Valid) => { - let signature = signing_method - .get_signature::>( - SignableMessage::AttestationData(attestation.data()), - signing_context, - &self.spec, - &self.task_executor, - ) - .await?; - attestation - .add_signature(&signature, validator_committee_position) - .map_err(Error::UnableToSignAttestation)?; - - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, - &[validator_metrics::SUCCESS], - ); - - Ok(()) - } - Ok(Safe::SameData) => { - warn!( - self.log, - "Skipping signing of previously signed attestation" - ); - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, - &[validator_metrics::SAME_DATA], - ); - Err(Error::SameData) - } - Err(NotSafe::UnregisteredValidator(pk)) => { - warn!( - self.log, - "Not signing attestation for unregistered validator"; - "msg" => "Carefully consider running with --init-slashing-protection (see --help)", - "public_key" => format!("{:?}", pk) - ); - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, - &[validator_metrics::UNREGISTERED], - ); - Err(Error::Slashable(NotSafe::UnregisteredValidator(pk))) - } - Err(e) => { - crit!( - self.log, - "Not signing slashable attestation"; - "attestation" => format!("{:?}", attestation.data()), - "error" => format!("{:?}", e) - ); - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, - &[validator_metrics::SLASHABLE], - ); - Err(Error::Slashable(e)) - } - } - } - - async fn sign_voluntary_exit( - &self, - validator_pubkey: PublicKeyBytes, - voluntary_exit: VoluntaryExit, - ) -> Result { - let signing_epoch = voluntary_exit.epoch; - let signing_context = self.signing_context(Domain::VoluntaryExit, signing_epoch); - let signing_method = self.doppelganger_bypassed_signing_method(validator_pubkey)?; - - let signature = signing_method - .get_signature::>( - SignableMessage::VoluntaryExit(&voluntary_exit), - signing_context, - &self.spec, - &self.task_executor, - ) - .await?; - - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_VOLUNTARY_EXITS_TOTAL, - &[validator_metrics::SUCCESS], - ); - - Ok(SignedVoluntaryExit { - message: voluntary_exit, - signature, - }) - } - - async fn sign_validator_registration_data( - &self, - validator_registration_data: ValidatorRegistrationData, - ) -> Result { - let domain_hash = self.spec.get_builder_domain(); - let signing_root = validator_registration_data.signing_root(domain_hash); - - let signing_method = - self.doppelganger_bypassed_signing_method(validator_registration_data.pubkey)?; - let signature = signing_method - .get_signature_from_root::>( - SignableMessage::ValidatorRegistration(&validator_registration_data), - signing_root, - &self.task_executor, - None, - ) - .await?; - - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_VALIDATOR_REGISTRATIONS_TOTAL, - &[validator_metrics::SUCCESS], - ); - - Ok(SignedValidatorRegistrationData { - message: validator_registration_data, - signature, - }) - } - - /// Signs an `AggregateAndProof` for a given validator. - /// - /// The resulting `SignedAggregateAndProof` is sent on the aggregation channel and cannot be - /// modified by actors other than the signing validator. - async fn produce_signed_aggregate_and_proof( - &self, - validator_pubkey: PublicKeyBytes, - aggregator_index: u64, - aggregate: Attestation, - selection_proof: SelectionProof, - ) -> Result, Error> { - let signing_epoch = aggregate.data().target.epoch; - let signing_context = self.signing_context(Domain::AggregateAndProof, signing_epoch); - - let message = - AggregateAndProof::from_attestation(aggregator_index, aggregate, selection_proof); - - let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; - let signature = signing_method - .get_signature::>( - SignableMessage::SignedAggregateAndProof(message.to_ref()), - signing_context, - &self.spec, - &self.task_executor, - ) - .await?; - - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_AGGREGATES_TOTAL, - &[validator_metrics::SUCCESS], - ); - - Ok(SignedAggregateAndProof::from_aggregate_and_proof( - message, signature, - )) - } - - /// Produces a `SelectionProof` for the `slot`, signed by with corresponding secret key to - /// `validator_pubkey`. - async fn produce_selection_proof( - &self, - validator_pubkey: PublicKeyBytes, - slot: Slot, - ) -> Result { - let signing_epoch = slot.epoch(E::slots_per_epoch()); - let signing_context = self.signing_context(Domain::SelectionProof, signing_epoch); - - // Bypass the `with_validator_signing_method` function. - // - // This is because we don't care about doppelganger protection when it comes to selection - // proofs. They are not slashable and we need them to subscribe to subnets on the BN. - // - // As long as we disallow `SignedAggregateAndProof` then these selection proofs will never - // be published on the network. - let signing_method = self.doppelganger_bypassed_signing_method(validator_pubkey)?; - - let signature = signing_method - .get_signature::>( - SignableMessage::SelectionProof(slot), - signing_context, - &self.spec, - &self.task_executor, - ) - .await - .map_err(Error::SpecificError)?; - - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_SELECTION_PROOFS_TOTAL, - &[validator_metrics::SUCCESS], - ); - - Ok(signature.into()) - } - - /// Produce a `SyncSelectionProof` for `slot` signed by the secret key of `validator_pubkey`. - async fn produce_sync_selection_proof( - &self, - validator_pubkey: &PublicKeyBytes, - slot: Slot, - subnet_id: SyncSubnetId, - ) -> Result { - let signing_epoch = slot.epoch(E::slots_per_epoch()); - let signing_context = - self.signing_context(Domain::SyncCommitteeSelectionProof, signing_epoch); - - // Bypass `with_validator_signing_method`: sync committee messages are not slashable. - let signing_method = self.doppelganger_bypassed_signing_method(*validator_pubkey)?; - - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_SYNC_SELECTION_PROOFS_TOTAL, - &[validator_metrics::SUCCESS], - ); - - let message = SyncAggregatorSelectionData { - slot, - subcommittee_index: subnet_id.into(), - }; - - let signature = signing_method - .get_signature::>( - SignableMessage::SyncSelectionProof(&message), - signing_context, - &self.spec, - &self.task_executor, - ) - .await - .map_err(Error::SpecificError)?; - - Ok(signature.into()) - } - - async fn produce_sync_committee_signature( - &self, - slot: Slot, - beacon_block_root: Hash256, - validator_index: u64, - validator_pubkey: &PublicKeyBytes, - ) -> Result { - let signing_epoch = slot.epoch(E::slots_per_epoch()); - let signing_context = self.signing_context(Domain::SyncCommittee, signing_epoch); - - // Bypass `with_validator_signing_method`: sync committee messages are not slashable. - let signing_method = self.doppelganger_bypassed_signing_method(*validator_pubkey)?; - - let signature = signing_method - .get_signature::>( - SignableMessage::SyncCommitteeSignature { - beacon_block_root, - slot, - }, - signing_context, - &self.spec, - &self.task_executor, - ) - .await - .map_err(Error::SpecificError)?; - - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_SYNC_COMMITTEE_MESSAGES_TOTAL, - &[validator_metrics::SUCCESS], - ); - - Ok(SyncCommitteeMessage { - slot, - beacon_block_root, - validator_index, - signature, - }) - } - - async fn produce_signed_contribution_and_proof( - &self, - aggregator_index: u64, - aggregator_pubkey: PublicKeyBytes, - contribution: SyncCommitteeContribution, - selection_proof: SyncSelectionProof, - ) -> Result, Error> { - let signing_epoch = contribution.slot.epoch(E::slots_per_epoch()); - let signing_context = self.signing_context(Domain::ContributionAndProof, signing_epoch); - - // Bypass `with_validator_signing_method`: sync committee messages are not slashable. - let signing_method = self.doppelganger_bypassed_signing_method(aggregator_pubkey)?; - - let message = ContributionAndProof { - aggregator_index, - contribution, - selection_proof: selection_proof.into(), - }; - - let signature = signing_method - .get_signature::>( - SignableMessage::SignedContributionAndProof(&message), - signing_context, - &self.spec, - &self.task_executor, - ) - .await - .map_err(Error::SpecificError)?; - - validator_metrics::inc_counter_vec( - &validator_metrics::SIGNED_SYNC_COMMITTEE_CONTRIBUTIONS_TOTAL, - &[validator_metrics::SUCCESS], - ); - - Ok(SignedContributionAndProof { message, signature }) - } - - /// Prune the slashing protection database so that it remains performant. - /// - /// This function will only do actual pruning periodically, so it should usually be - /// cheap to call. The `first_run` flag can be used to print a more verbose message when pruning - /// runs. - fn prune_slashing_protection_db(&self, current_epoch: Epoch, first_run: bool) { - // Attempt to prune every SLASHING_PROTECTION_HISTORY_EPOCHs, with a tolerance for - // missing the epoch that aligns exactly. - let mut last_prune = self.slashing_protection_last_prune.lock(); - if current_epoch / SLASHING_PROTECTION_HISTORY_EPOCHS - <= *last_prune / SLASHING_PROTECTION_HISTORY_EPOCHS - { - return; - } - - if first_run { - info!( - self.log, - "Pruning slashing protection DB"; - "epoch" => current_epoch, - "msg" => "pruning may take several minutes the first time it runs" - ); - } else { - info!(self.log, "Pruning slashing protection DB"; "epoch" => current_epoch); - } - - let _timer = - validator_metrics::start_timer(&validator_metrics::SLASHING_PROTECTION_PRUNE_TIMES); - - let new_min_target_epoch = current_epoch.saturating_sub(SLASHING_PROTECTION_HISTORY_EPOCHS); - let new_min_slot = new_min_target_epoch.start_slot(E::slots_per_epoch()); - - let all_pubkeys: Vec<_> = self.voting_pubkeys(DoppelgangerStatus::ignored); - - if let Err(e) = self - .slashing_protection - .prune_all_signed_attestations(all_pubkeys.iter(), new_min_target_epoch) - { - error!( - self.log, - "Error during pruning of signed attestations"; - "error" => ?e, - ); - return; - } - - if let Err(e) = self - .slashing_protection - .prune_all_signed_blocks(all_pubkeys.iter(), new_min_slot) - { - error!( - self.log, - "Error during pruning of signed blocks"; - "error" => ?e, - ); - return; - } - - *last_prune = current_epoch; - - info!(self.log, "Completed pruning of slashing protection DB"); - } - - /// Returns `ProposalData` for the provided `pubkey` if it exists in `InitializedValidators`. - /// `ProposalData` fields include defaulting logic described in `get_fee_recipient_defaulting`, - /// `get_gas_limit_defaulting`, and `get_builder_proposals_defaulting`. - fn proposal_data(&self, pubkey: &PublicKeyBytes) -> Option { - self.validators - .read() - .validator(pubkey) - .map(|validator| ProposalData { - validator_index: validator.get_index(), - fee_recipient: self - .get_fee_recipient_defaulting(validator.get_suggested_fee_recipient()), - gas_limit: self.get_gas_limit_defaulting(validator.get_gas_limit()), - builder_proposals: self - .get_builder_proposals_defaulting(validator.get_builder_proposals()), - }) - } -} diff --git a/validator_client/signing_method/src/lib.rs b/validator_client/signing_method/src/lib.rs index 316c1d2205..f3b62c9500 100644 --- a/validator_client/signing_method/src/lib.rs +++ b/validator_client/signing_method/src/lib.rs @@ -12,7 +12,7 @@ use std::sync::Arc; use task_executor::TaskExecutor; use types::*; use url::Url; -use web3signer::{ForkInfo, MessageType, SigningRequest, SigningResponse}; +use web3signer::{ForkInfo, SigningRequest, SigningResponse}; pub use web3signer::Web3SignerObject; @@ -152,13 +152,8 @@ impl SigningMethod { genesis_validators_root, }); - self.get_signature_from_root::( - signable_message, - signing_root, - executor, - fork_info, - ) - .await + self.get_signature_from_root(signable_message, signing_root, executor, fork_info) + .await } pub async fn get_signature_from_root>( @@ -232,7 +227,11 @@ impl SigningMethod { // Determine the Web3Signer message type. let message_type = object.message_type(); - if matches!(message_type, MessageType::ValidatorRegistration) && fork_info.is_some() + + if matches!( + object, + Web3SignerObject::Deposit { .. } | Web3SignerObject::ValidatorRegistration(_) + ) && fork_info.is_some() { return Err(Error::GenesisForkVersionRequired); } diff --git a/validator_client/slashing_protection/Cargo.toml b/validator_client/slashing_protection/Cargo.toml index 1a098742d8..88e6dd794d 100644 --- a/validator_client/slashing_protection/Cargo.toml +++ b/validator_client/slashing_protection/Cargo.toml @@ -19,6 +19,7 @@ rusqlite = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tempfile = { workspace = true } +tracing = { workspace = true } types = { workspace = true } [dev-dependencies] diff --git a/validator_client/slashing_protection/src/lib.rs b/validator_client/slashing_protection/src/lib.rs index 825a34cabc..51dd3e3164 100644 --- a/validator_client/slashing_protection/src/lib.rs +++ b/validator_client/slashing_protection/src/lib.rs @@ -27,7 +27,7 @@ pub const SLASHING_PROTECTION_FILENAME: &str = "slashing_protection.sqlite"; /// The attestation or block is not safe to sign. /// /// This could be because it's slashable, or because an error occurred. -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug)] pub enum NotSafe { UnregisteredValidator(PublicKeyBytes), DisabledValidator(PublicKeyBytes), diff --git a/validator_client/slashing_protection/src/signed_attestation.rs b/validator_client/slashing_protection/src/signed_attestation.rs index 332f80c704..779b5f770a 100644 --- a/validator_client/slashing_protection/src/signed_attestation.rs +++ b/validator_client/slashing_protection/src/signed_attestation.rs @@ -10,7 +10,7 @@ pub struct SignedAttestation { } /// Reasons why an attestation may be slashable (or invalid). -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug)] pub enum InvalidAttestation { /// The attestation has the same target epoch as an attestation from the DB (enclosed). DoubleVote(SignedAttestation), diff --git a/validator_client/slashing_protection/src/signed_block.rs b/validator_client/slashing_protection/src/signed_block.rs index d46872529e..92ec2dcbe8 100644 --- a/validator_client/slashing_protection/src/signed_block.rs +++ b/validator_client/slashing_protection/src/signed_block.rs @@ -9,7 +9,7 @@ pub struct SignedBlock { } /// Reasons why a block may be slashable. -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug)] pub enum InvalidBlock { DoubleBlockProposal(SignedBlock), SlotViolatesLowerBound { block_slot: Slot, bound_slot: Slot }, diff --git a/validator_client/slashing_protection/src/slashing_database.rs b/validator_client/slashing_protection/src/slashing_database.rs index 71611339f9..f4c844d314 100644 --- a/validator_client/slashing_protection/src/slashing_database.rs +++ b/validator_client/slashing_protection/src/slashing_database.rs @@ -1113,7 +1113,7 @@ fn max_or(opt_x: Option, y: T) -> T { /// /// If prev is `None` and `new` is `Some` then `true` is returned. fn monotonic(new: Option, prev: Option) -> bool { - new.is_some_and(|new_val| prev.map_or(true, |prev_val| new_val >= prev_val)) + new.is_some_and(|new_val| prev.is_none_or(|prev_val| new_val >= prev_val)) } /// The result of importing a single entry from an interchange file. diff --git a/validator_client/src/check_synced.rs b/validator_client/src/check_synced.rs new file mode 100644 index 0000000000..5f3e0fe036 --- /dev/null +++ b/validator_client/src/check_synced.rs @@ -0,0 +1,25 @@ +use crate::beacon_node_fallback::CandidateError; +use eth2::{types::Slot, BeaconNodeHttpClient}; +use tracing::warn; + +pub async fn check_node_health( + beacon_node: &BeaconNodeHttpClient, +) -> Result<(Slot, bool, bool), CandidateError> { + let resp = match beacon_node.get_node_syncing().await { + Ok(resp) => resp, + Err(e) => { + warn!( + error = %e, + "Unable connect to beacon node" + ); + + return Err(CandidateError::Offline); + } + }; + + Ok(( + resp.data.head_slot, + resp.data.is_optimistic, + resp.data.el_offline, + )) +} diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index dfcd2064e5..18bd736957 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -97,6 +97,15 @@ pub struct ValidatorClient { )] pub disable_auto_discover: bool, + #[clap( + long, + help = "Disable the performance of attestation duties (and sync committee duties). This \ + flag should only be used in emergencies to prioritise block proposal duties.", + display_order = 0, + help_heading = FLAG_HEADER + )] + pub disable_attesting: bool, + #[clap( long, help = "If present, the validator client will use longer timeouts for requests \ @@ -107,6 +116,20 @@ pub struct ValidatorClient { )] pub use_long_timeouts: bool, + #[clap( + long, + requires = "use_long_timeouts", + default_value_t = 1, + help = "If present, the validator client will use a multiplier for the timeout \ + when making requests to the beacon node. This only takes effect when \ + the `--use-long-timeouts` flag is present. The timeouts will be the slot \ + duration multiplied by this value. This flag is generally not recommended, \ + longer timeouts can cause missed duties when fallbacks are used.", + display_order = 0, + help_heading = FLAG_HEADER, + )] + pub long_timeouts_multiplier: u32, + #[clap( long, value_name = "CERTIFICATE-FILES", diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 20fa3ffe5a..cfc88969c9 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -10,17 +10,17 @@ use directory::{ use eth2::types::Graffiti; use graffiti_file::GraffitiFile; use initialized_validators::Config as InitializedValidatorsConfig; -use lighthouse_validator_store::Config as ValidatorStoreConfig; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{info, warn, Logger}; use std::fs; use std::net::IpAddr; use std::path::PathBuf; use std::time::Duration; +use tracing::{info, warn}; use types::GRAFFITI_BYTES_LEN; use validator_http_api::{self, PK_FILENAME}; use validator_http_metrics; +use validator_store::Config as ValidatorStoreConfig; pub const DEFAULT_BEACON_NODE: &str = "http://localhost:5052/"; @@ -49,6 +49,8 @@ pub struct Config { pub init_slashing_protection: bool, /// If true, use longer timeouts for requests made to the beacon node. pub use_long_timeouts: bool, + /// Multiplier to use for long timeouts. + pub long_timeouts_multiplier: u32, /// Graffiti to be inserted everytime we create a block. pub graffiti: Option, /// Graffiti file to load per validator graffitis. @@ -85,6 +87,7 @@ pub struct Config { /// Configuration for the initialized validators #[serde(flatten)] pub initialized_validators: InitializedValidatorsConfig, + pub disable_attesting: bool, } impl Default for Config { @@ -111,6 +114,7 @@ impl Default for Config { disable_auto_discover: false, init_slashing_protection: false, use_long_timeouts: false, + long_timeouts_multiplier: 1, graffiti: None, graffiti_file: None, http_api: <_>::default(), @@ -126,6 +130,7 @@ impl Default for Config { validator_registration_batch_size: 500, distributed: false, initialized_validators: <_>::default(), + disable_attesting: false, } } } @@ -136,7 +141,6 @@ impl Config { pub fn from_cli( cli_args: &ArgMatches, validator_client_config: &ValidatorClient, - log: &Logger, ) -> Result { let mut config = Config::default(); @@ -194,6 +198,7 @@ impl Config { config.disable_auto_discover = validator_client_config.disable_auto_discover; config.init_slashing_protection = validator_client_config.init_slashing_protection; config.use_long_timeouts = validator_client_config.use_long_timeouts; + config.long_timeouts_multiplier = validator_client_config.long_timeouts_multiplier; if let Some(graffiti_file_path) = validator_client_config.graffiti_file.as_ref() { let mut graffiti_file = GraffitiFile::new(graffiti_file_path.into()); @@ -201,7 +206,10 @@ impl Config { .read_graffiti_file() .map_err(|e| format!("Error reading graffiti file: {:?}", e))?; config.graffiti_file = Some(graffiti_file); - info!(log, "Successfully loaded graffiti file"; "path" => graffiti_file_path.to_str()); + info!( + path = graffiti_file_path.to_str(), + "Successfully loaded graffiti file" + ); } if let Some(input_graffiti) = validator_client_config.graffiti.as_ref() { @@ -369,16 +377,17 @@ impl Config { config.validator_store.enable_web3signer_slashing_protection = if validator_client_config.disable_slashing_protection_web3signer { warn!( - log, - "Slashing protection for remote keys disabled"; - "info" => "ensure slashing protection on web3signer is enabled or you WILL \ - get slashed" + info = "ensure slashing protection on web3signer is enabled or you WILL \ + get slashed", + "Slashing protection for remote keys disabled" ); false } else { true }; + config.disable_attesting = validator_client_config.disable_attesting; + Ok(config) } } diff --git a/validator_client/src/latency.rs b/validator_client/src/latency.rs index e2a80876ec..edd8daa731 100644 --- a/validator_client/src/latency.rs +++ b/validator_client/src/latency.rs @@ -1,9 +1,9 @@ use beacon_node_fallback::BeaconNodeFallback; use environment::RuntimeContext; -use slog::debug; use slot_clock::SlotClock; use std::sync::Arc; use tokio::time::sleep; +use tracing::debug; use types::EthSpec; /// The latency service will run 11/12ths of the way through the slot. @@ -15,10 +15,8 @@ pub const SLOT_DELAY_DENOMINATOR: u32 = 12; pub fn start_latency_service( context: RuntimeContext, slot_clock: T, - beacon_nodes: Arc>, + beacon_nodes: Arc>, ) { - let log = context.log().clone(); - let future = async move { loop { let sleep_time = slot_clock @@ -39,10 +37,9 @@ pub fn start_latency_service( for (i, measurement) in beacon_nodes.measure_latency().await.iter().enumerate() { if let Some(latency) = measurement.latency { debug!( - log, - "Measured BN latency"; - "node" => &measurement.beacon_node_id, - "latency" => latency.as_millis(), + node = &measurement.beacon_node_id, + latency = latency.as_millis(), + "Measured BN latency" ); validator_metrics::observe_timer_vec( &validator_metrics::VC_BEACON_NODE_LATENCY, diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 3bad63a50b..7171dea57b 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -20,15 +20,14 @@ use doppelganger_service::DoppelgangerService; use environment::RuntimeContext; use eth2::{reqwest::ClientBuilder, BeaconNodeHttpClient, StatusCode, Timeouts}; use initialized_validators::Error::UnableToOpenVotingKeystore; -use lighthouse_validator_store::LighthouseValidatorStore; use notifier::spawn_notifier; use parking_lot::RwLock; use reqwest::Certificate; -use slog::{debug, error, info, warn, Logger}; use slot_clock::SlotClock; use slot_clock::SystemTimeSlotClock; use std::fs::File; use std::io::Read; +use std::marker::PhantomData; use std::net::SocketAddr; use std::path::Path; use std::sync::Arc; @@ -37,16 +36,18 @@ use tokio::{ sync::mpsc, time::{sleep, Duration}, }; +use tracing::{debug, error, info, warn}; use types::{EthSpec, Hash256}; use validator_http_api::ApiSecret; use validator_services::{ attestation_service::{AttestationService, AttestationServiceBuilder}, block_service::{BlockService, BlockServiceBuilder}, - duties_service::{self, DutiesService, DutiesServiceBuilder}, + duties_service::{self, DutiesService}, preparation_service::{PreparationService, PreparationServiceBuilder}, + sync::SyncDutiesMap, sync_committee_service::SyncCommitteeService, }; -use validator_store::ValidatorStore as ValidatorStoreTrait; +use validator_store::ValidatorStore; /// The interval between attempts to contact the beacon node during startup. const RETRY_DELAY: Duration = Duration::from_secs(2); @@ -71,22 +72,20 @@ const HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT: u32 = 4; const DOPPELGANGER_SERVICE_NAME: &str = "doppelganger"; -type ValidatorStore = LighthouseValidatorStore; - #[derive(Clone)] pub struct ProductionValidatorClient { context: RuntimeContext, - duties_service: Arc, SystemTimeSlotClock>>, - block_service: BlockService, SystemTimeSlotClock>, - attestation_service: AttestationService, SystemTimeSlotClock>, - sync_committee_service: SyncCommitteeService, SystemTimeSlotClock>, + duties_service: Arc>, + block_service: BlockService, + attestation_service: AttestationService, + sync_committee_service: SyncCommitteeService, doppelganger_service: Option>, - preparation_service: PreparationService, SystemTimeSlotClock>, - validator_store: Arc>, + preparation_service: PreparationService, + validator_store: Arc>, slot_clock: SystemTimeSlotClock, http_api_listen_addr: Option, config: Config, - beacon_nodes: Arc>, + beacon_nodes: Arc>, genesis_time: u64, } @@ -98,7 +97,7 @@ impl ProductionValidatorClient { cli_args: &ArgMatches, validator_client_config: &ValidatorClient, ) -> Result { - let config = Config::from_cli(cli_args, validator_client_config, context.log()) + let config = Config::from_cli(cli_args, validator_client_config) .map_err(|e| format!("Unable to initialize config: {}", e))?; Self::new(context, config).await } @@ -106,8 +105,6 @@ impl ProductionValidatorClient { /// Instantiates the validator client, _without_ starting the timers to trigger block /// and attestation production. pub async fn new(context: RuntimeContext, config: Config) -> Result { - let log = context.log().clone(); - // Attempt to raise soft fd limit. The behavior is OS specific: // `linux` - raise soft fd limit to hard // `macos` - raise soft fd limit to `min(kernel limit, hard fd limit)` @@ -115,25 +112,20 @@ impl ProductionValidatorClient { match fdlimit::raise_fd_limit().map_err(|e| format!("Unable to raise fd limit: {}", e))? { fdlimit::Outcome::LimitRaised { from, to } => { debug!( - log, - "Raised soft open file descriptor resource limit"; - "old_limit" => from, - "new_limit" => to + old_limit = from, + new_limit = to, + "Raised soft open file descriptor resource limit" ); } fdlimit::Outcome::Unsupported => { - debug!( - log, - "Raising soft open file descriptor resource limit is not supported" - ); + debug!("Raising soft open file descriptor resource limit is not supported"); } }; info!( - log, - "Starting validator client"; - "beacon_nodes" => format!("{:?}", &config.beacon_nodes), - "validator_dir" => format!("{:?}", config.validator_dir), + beacon_nodes = ?config.beacon_nodes, + validator_dir = ?config.validator_dir, + "Starting validator client" ); // Optionally start the metrics server. @@ -148,12 +140,11 @@ impl ProductionValidatorClient { Arc::new(validator_http_metrics::Context { config: config.http_metrics.clone(), shared: RwLock::new(shared), - log: log.clone(), }); let exit = context.executor.exit(); - let (_listen_addr, server) = validator_http_metrics::serve::(ctx.clone(), exit) + let (_listen_addr, server) = validator_http_metrics::serve(ctx.clone(), exit) .map_err(|e| format!("Unable to start metrics API server: {:?}", e))?; context @@ -163,15 +154,14 @@ impl ProductionValidatorClient { Some(ctx) } else { - info!(log, "HTTP metrics server is disabled"); + info!("HTTP metrics server is disabled"); None }; // Start the explorer client which periodically sends validator process // and system metrics to the configured endpoint. if let Some(monitoring_config) = &config.monitoring_api { - let monitoring_client = - MonitoringHttpClient::new(monitoring_config, context.log().clone())?; + let monitoring_client = MonitoringHttpClient::new(monitoring_config)?; monitoring_client.auto_update( context.executor.clone(), vec![ProcessType::Validator, ProcessType::System], @@ -183,7 +173,7 @@ impl ProductionValidatorClient { if !config.disable_auto_discover { let new_validators = validator_defs - .discover_local_keystores(&config.validator_dir, &config.secrets_dir, &log) + .discover_local_keystores(&config.validator_dir, &config.secrets_dir) .map_err(|e| format!("Unable to discover local validator keystores: {:?}", e))?; validator_defs.save(&config.validator_dir).map_err(|e| { format!( @@ -191,18 +181,13 @@ impl ProductionValidatorClient { e ) })?; - info!( - log, - "Completed validator discovery"; - "new_validators" => new_validators, - ); + info!(new_validators, "Completed validator discovery"); } let validators = InitializedValidators::from_definitions( validator_defs, config.validator_dir.clone(), config.initialized_validators.clone(), - log.clone(), ) .await .map_err(|e| { @@ -219,17 +204,17 @@ impl ProductionValidatorClient { let voting_pubkeys: Vec<_> = validators.iter_voting_pubkeys().collect(); info!( - log, - "Initialized validators"; - "disabled" => validators.num_total().saturating_sub(validators.num_enabled()), - "enabled" => validators.num_enabled(), + disabled = validators + .num_total() + .saturating_sub(validators.num_enabled()), + enabled = validators.num_enabled(), + "Initialized validators" ); if voting_pubkeys.is_empty() { warn!( - log, - "No enabled validators"; - "hint" => "create validators via the API, or the `lighthouse account` CLI command" + hint = "create validators via the API, or the `lighthouse account` CLI command", + "No enabled validators" ); } @@ -304,10 +289,7 @@ impl ProductionValidatorClient { // Use quicker timeouts if a fallback beacon node exists. let timeouts = if i < last_beacon_node_index && !config.use_long_timeouts { - info!( - log, - "Fallback endpoints are available, using optimized timeouts."; - ); + info!("Fallback endpoints are available, using optimized timeouts."); Timeouts { attestation: slot_duration / HTTP_ATTESTATION_TIMEOUT_QUOTIENT, attester_duties: slot_duration / HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT, @@ -326,7 +308,7 @@ impl ProductionValidatorClient { get_validator_block: slot_duration / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT, } } else { - Timeouts::set_all(slot_duration) + Timeouts::set_all(slot_duration.saturating_mul(config.long_timeouts_multiplier)) }; Ok(BeaconNodeHttpClient::from_components( @@ -385,14 +367,14 @@ impl ProductionValidatorClient { // Initialize the number of connected, avaliable beacon nodes to 0. set_gauge(&validator_metrics::AVAILABLE_BEACON_NODES_COUNT, 0); - let mut beacon_nodes: BeaconNodeFallback<_> = BeaconNodeFallback::new( + let mut beacon_nodes: BeaconNodeFallback<_, E> = BeaconNodeFallback::new( candidates, config.beacon_node_fallback, config.broadcast_topics.clone(), context.eth2_config.spec.clone(), ); - let mut proposer_nodes: BeaconNodeFallback<_> = BeaconNodeFallback::new( + let mut proposer_nodes: BeaconNodeFallback<_, E> = BeaconNodeFallback::new( proposer_candidates, config.beacon_node_fallback, config.broadcast_topics.clone(), @@ -401,7 +383,7 @@ impl ProductionValidatorClient { // Perform some potentially long-running initialization tasks. let (genesis_time, genesis_validators_root) = tokio::select! { - tuple = init_from_beacon_node(&beacon_nodes, &proposer_nodes, &context) => tuple?, + tuple = init_from_beacon_node(&beacon_nodes, &proposer_nodes) => tuple?, () = context.executor.exit() => return Err("Shutting down".to_string()) }; @@ -420,23 +402,18 @@ impl ProductionValidatorClient { proposer_nodes.set_slot_clock(slot_clock.clone()); let beacon_nodes = Arc::new(beacon_nodes); - start_fallback_updater_service::<_, E>(context.executor.clone(), beacon_nodes.clone())?; + start_fallback_updater_service(context.clone(), beacon_nodes.clone())?; let proposer_nodes = Arc::new(proposer_nodes); - start_fallback_updater_service::<_, E>(context.executor.clone(), proposer_nodes.clone())?; + start_fallback_updater_service(context.clone(), proposer_nodes.clone())?; let doppelganger_service = if config.enable_doppelganger_protection { - Some(Arc::new(DoppelgangerService::new( - context - .service_context(DOPPELGANGER_SERVICE_NAME.into()) - .log() - .clone(), - ))) + Some(Arc::new(DoppelgangerService::default())) } else { None }; - let validator_store = Arc::new(LighthouseValidatorStore::new( + let validator_store = Arc::new(ValidatorStore::new( validators, slashing_protection, genesis_validators_root, @@ -445,16 +422,14 @@ impl ProductionValidatorClient { slot_clock.clone(), &config.validator_store, context.executor.clone(), - log.clone(), )); // Ensure all validators are registered in doppelganger protection. validator_store.register_all_in_doppelganger_protection_if_enabled()?; info!( - log, - "Loaded validator keypair store"; - "voting_validators" => validator_store.num_voting_validators() + voting_validators = validator_store.num_voting_validators(), + "Loaded validator keypair store" ); // Perform pruning of the slashing protection database on start-up. In case the database is @@ -464,17 +439,21 @@ impl ProductionValidatorClient { validator_store.prune_slashing_protection_db(slot.epoch(E::slots_per_epoch()), true); } - let duties_service = Arc::new( - DutiesServiceBuilder::new() - .slot_clock(slot_clock.clone()) - .beacon_nodes(beacon_nodes.clone()) - .validator_store(validator_store.clone()) - .spec(context.eth2_config.spec.clone()) - .executor(context.executor.clone()) - .enable_high_validator_count_metrics(config.enable_high_validator_count_metrics) - .distributed(config.distributed) - .build()?, - ); + let duties_context = context.service_context("duties".into()); + let duties_service = Arc::new(DutiesService { + attesters: <_>::default(), + proposers: <_>::default(), + sync_duties: SyncDutiesMap::new(config.distributed), + slot_clock: slot_clock.clone(), + beacon_nodes: beacon_nodes.clone(), + validator_store: validator_store.clone(), + unknown_validator_next_poll_slots: <_>::default(), + spec: context.eth2_config.spec.clone(), + context: duties_context, + enable_high_validator_count_metrics: config.enable_high_validator_count_metrics, + distributed: config.distributed, + disable_attesting: config.disable_attesting, + }); // Update the metrics server. if let Some(ctx) = &validator_metrics_ctx { @@ -486,7 +465,7 @@ impl ProductionValidatorClient { .slot_clock(slot_clock.clone()) .validator_store(validator_store.clone()) .beacon_nodes(beacon_nodes.clone()) - .executor(context.executor.clone()) + .runtime_context(context.service_context("block".into())) .graffiti(config.graffiti) .graffiti_file(config.graffiti_file.clone()); @@ -502,15 +481,15 @@ impl ProductionValidatorClient { .slot_clock(slot_clock.clone()) .validator_store(validator_store.clone()) .beacon_nodes(beacon_nodes.clone()) - .executor(context.executor.clone()) - .chain_spec(context.eth2_config.spec.clone()) + .runtime_context(context.service_context("attestation".into())) + .disable(config.disable_attesting) .build()?; let preparation_service = PreparationServiceBuilder::new() .slot_clock(slot_clock.clone()) .validator_store(validator_store.clone()) .beacon_nodes(beacon_nodes.clone()) - .executor(context.executor.clone()) + .runtime_context(context.service_context("preparation".into())) .builder_registration_timestamp_override(config.builder_registration_timestamp_override) .validator_registration_batch_size(config.validator_registration_batch_size) .build()?; @@ -520,7 +499,7 @@ impl ProductionValidatorClient { validator_store.clone(), slot_clock.clone(), beacon_nodes.clone(), - context.executor.clone(), + context.service_context("sync_committee".into()), ); Ok(Self { @@ -546,7 +525,6 @@ impl ProductionValidatorClient { // whole epoch! let channel_capacity = E::slots_per_epoch() as usize; let (block_service_tx, block_service_rx) = mpsc::channel(channel_capacity); - let log = self.context.log(); let api_secret = ApiSecret::create_or_open(&self.config.http_api.http_token_path)?; @@ -564,12 +542,12 @@ impl ProductionValidatorClient { config: self.config.http_api.clone(), sse_logging_components: self.context.sse_logging_components.clone(), slot_clock: self.slot_clock.clone(), - log: log.clone(), + _phantom: PhantomData, }); let exit = self.context.executor.exit(); - let (listen_addr, server) = validator_http_api::serve::<_, E>(ctx, exit) + let (listen_addr, server) = validator_http_api::serve(ctx, exit) .map_err(|e| format!("Unable to start HTTP API server: {:?}", e))?; self.context @@ -579,12 +557,12 @@ impl ProductionValidatorClient { Some(listen_addr) } else { - info!(log, "HTTP API server is disabled"); + info!("HTTP API server is disabled"); None }; // Wait until genesis has occurred. - wait_for_genesis(&self.beacon_nodes, self.genesis_time, &self.context).await?; + wait_for_genesis(&self.beacon_nodes, self.genesis_time).await?; duties_service::start_update_service(self.duties_service.clone(), block_service_tx); @@ -619,7 +597,7 @@ impl ProductionValidatorClient { ) .map_err(|e| format!("Unable to start doppelganger service: {}", e))? } else { - info!(log, "Doppelganger protection disabled.") + info!("Doppelganger protection disabled.") } spawn_notifier(self).map_err(|e| format!("Failed to start notifier: {}", e))?; @@ -637,13 +615,12 @@ impl ProductionValidatorClient { } async fn init_from_beacon_node( - beacon_nodes: &BeaconNodeFallback, - proposer_nodes: &BeaconNodeFallback, - context: &RuntimeContext, + beacon_nodes: &BeaconNodeFallback, + proposer_nodes: &BeaconNodeFallback, ) -> Result<(u64, Hash256), String> { loop { - beacon_nodes.update_all_candidates::().await; - proposer_nodes.update_all_candidates::().await; + beacon_nodes.update_all_candidates().await; + proposer_nodes.update_all_candidates().await; let num_available = beacon_nodes.num_available().await; let num_total = beacon_nodes.num_total().await; @@ -653,41 +630,37 @@ async fn init_from_beacon_node( if proposer_total > 0 && proposer_available == 0 { warn!( - context.log(), - "Unable to connect to a proposer node"; - "retry in" => format!("{} seconds", RETRY_DELAY.as_secs()), - "total_proposers" => proposer_total, - "available_proposers" => proposer_available, - "total_beacon_nodes" => num_total, - "available_beacon_nodes" => num_available, + retry_in = format!("{} seconds", RETRY_DELAY.as_secs()), + total_proposers = proposer_total, + available_proposers = proposer_available, + total_beacon_nodes = num_total, + available_beacon_nodes = num_available, + "Unable to connect to a proposer node" ); } if num_available > 0 && proposer_available == 0 { info!( - context.log(), - "Initialized beacon node connections"; - "total" => num_total, - "available" => num_available, + total = num_total, + available = num_available, + "Initialized beacon node connections" ); break; } else if num_available > 0 { info!( - context.log(), - "Initialized beacon node connections"; - "total" => num_total, - "available" => num_available, - "proposers_available" => proposer_available, - "proposers_total" => proposer_total, + total = num_total, + available = num_available, + proposer_available, + proposer_total, + "Initialized beacon node connections" ); break; } else { warn!( - context.log(), - "Unable to connect to a beacon node"; - "retry in" => format!("{} seconds", RETRY_DELAY.as_secs()), - "total" => num_total, - "available" => num_available, + retry_in = format!("{} seconds", RETRY_DELAY.as_secs()), + total = num_total, + available = num_available, + "Unable to connect to a beacon node" ); sleep(RETRY_DELAY).await; } @@ -708,15 +681,11 @@ async fn init_from_beacon_node( .filter_map(|(_, e)| e.request_failure()) .any(|e| e.status() == Some(StatusCode::NOT_FOUND)) { - info!( - context.log(), - "Waiting for genesis"; - ); + info!("Waiting for genesis"); } else { error!( - context.log(), - "Errors polling beacon node"; - "error" => %errors + %errors, + "Errors polling beacon node" ); } } @@ -729,9 +698,8 @@ async fn init_from_beacon_node( } async fn wait_for_genesis( - beacon_nodes: &BeaconNodeFallback, + beacon_nodes: &BeaconNodeFallback, genesis_time: u64, - context: &RuntimeContext, ) -> Result<(), String> { let now = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -745,28 +713,25 @@ async fn wait_for_genesis( // the slot clock. if now < genesis_time { info!( - context.log(), - "Starting node prior to genesis"; - "seconds_to_wait" => (genesis_time - now).as_secs() + seconds_to_wait = (genesis_time - now).as_secs(), + "Starting node prior to genesis" ); // Start polling the node for pre-genesis information, cancelling the polling as soon as the // timer runs out. tokio::select! { - result = poll_whilst_waiting_for_genesis(beacon_nodes, genesis_time, context.log()) => result?, + result = poll_whilst_waiting_for_genesis(beacon_nodes, genesis_time) => result?, () = sleep(genesis_time - now) => () }; info!( - context.log(), - "Genesis has occurred"; - "ms_since_genesis" => (genesis_time - now).as_millis() + ms_since_genesis = (genesis_time - now).as_millis(), + "Genesis has occurred" ); } else { info!( - context.log(), - "Genesis has already occurred"; - "seconds_ago" => (now - genesis_time).as_secs() + seconds_ago = (now - genesis_time).as_secs(), + "Genesis has already occurred" ); } @@ -775,10 +740,9 @@ async fn wait_for_genesis( /// Request the version from the node, looping back and trying again on failure. Exit once the node /// has been contacted. -async fn poll_whilst_waiting_for_genesis( - beacon_nodes: &BeaconNodeFallback, +async fn poll_whilst_waiting_for_genesis( + beacon_nodes: &BeaconNodeFallback, genesis_time: Duration, - log: &Logger, ) -> Result<(), String> { loop { match beacon_nodes @@ -792,19 +756,17 @@ async fn poll_whilst_waiting_for_genesis( if !is_staking { error!( - log, - "Staking is disabled for beacon node"; - "msg" => "this will caused missed duties", - "info" => "see the --staking CLI flag on the beacon node" + msg = "this will caused missed duties", + info = "see the --staking CLI flag on the beacon node", + "Staking is disabled for beacon node" ); } if now < genesis_time { info!( - log, - "Waiting for genesis"; - "bn_staking_enabled" => is_staking, - "seconds_to_wait" => (genesis_time - now).as_secs() + bn_staking_enabled = is_staking, + seconds_to_wait = (genesis_time - now).as_secs(), + "Waiting for genesis" ); } else { break Ok(()); @@ -812,9 +774,8 @@ async fn poll_whilst_waiting_for_genesis( } Err(e) => { error!( - log, - "Error polling beacon node"; - "error" => %e + error = %e, + "Error polling beacon node" ); } } diff --git a/validator_client/src/notifier.rs b/validator_client/src/notifier.rs index c0d73cc45c..75b3d46457 100644 --- a/validator_client/src/notifier.rs +++ b/validator_client/src/notifier.rs @@ -1,9 +1,8 @@ use crate::{DutiesService, ProductionValidatorClient}; -use lighthouse_validator_store::LighthouseValidatorStore; use metrics::set_gauge; -use slog::{debug, error, info, Logger}; use slot_clock::SlotClock; use tokio::time::{sleep, Duration}; +use tracing::{debug, error, info}; use types::EthSpec; /// Spawns a notifier service which periodically logs information about the node. @@ -15,14 +14,12 @@ pub fn spawn_notifier(client: &ProductionValidatorClient) -> Resu let slot_duration = Duration::from_secs(context.eth2_config.spec.seconds_per_slot); let interval_fut = async move { - let log = context.log(); - loop { if let Some(duration_to_next_slot) = duties_service.slot_clock.duration_to_next_slot() { sleep(duration_to_next_slot + slot_duration / 2).await; - notify::<_, E>(&duties_service, log).await; + notify(&duties_service).await; } else { - error!(log, "Failed to read slot clock"); + error!("Failed to read slot clock"); // If we can't read the slot clock, just wait another slot. sleep(slot_duration).await; continue; @@ -35,10 +32,7 @@ pub fn spawn_notifier(client: &ProductionValidatorClient) -> Resu } /// Performs a single notification routine. -async fn notify( - duties_service: &DutiesService, T>, - log: &Logger, -) { +async fn notify(duties_service: &DutiesService) { let (candidate_info, num_available, num_synced) = duties_service.beacon_nodes.get_notifier_info().await; let num_total = candidate_info.len(); @@ -62,20 +56,18 @@ async fn notify( .map(|candidate| candidate.endpoint.as_str()) .unwrap_or("None"); info!( - log, - "Connected to beacon node(s)"; - "primary" => primary, - "total" => num_total, - "available" => num_available, - "synced" => num_synced, + primary, + total = num_total, + available = num_available, + synced = num_synced, + "Connected to beacon node(s)" ) } else { error!( - log, - "No synced beacon nodes"; - "total" => num_total, - "available" => num_available, - "synced" => num_synced, + total = num_total, + available = num_available, + synced = num_synced, + "No synced beacon nodes" ) } if num_synced_fallback > 0 { @@ -87,23 +79,21 @@ async fn notify( for info in candidate_info { if let Ok(health) = info.health { debug!( - log, - "Beacon node info"; - "status" => "Connected", - "index" => info.index, - "endpoint" => info.endpoint, - "head_slot" => %health.head, - "is_optimistic" => ?health.optimistic_status, - "execution_engine_status" => ?health.execution_status, - "health_tier" => %health.health_tier, + status = "Connected", + index = info.index, + endpoint = info.endpoint, + head_slot = %health.head, + is_optimistic = ?health.optimistic_status, + execution_engine_status = ?health.execution_status, + health_tier = %health.health_tier, + "Beacon node info" ); } else { debug!( - log, - "Beacon node info"; - "status" => "Disconnected", - "index" => info.index, - "endpoint" => info.endpoint, + status = "Disconnected", + index = info.index, + endpoint = info.endpoint, + "Beacon node info" ); } } @@ -117,45 +107,44 @@ async fn notify( let doppelganger_detecting_validators = duties_service.doppelganger_detecting_count(); if doppelganger_detecting_validators > 0 { - info!(log, "Listening for doppelgangers"; "doppelganger_detecting_validators" => doppelganger_detecting_validators) + info!( + doppelganger_detecting_validators, + "Listening for doppelgangers" + ) } if total_validators == 0 { info!( - log, - "No validators present"; - "msg" => "see `lighthouse vm create --help` or the HTTP API documentation" + msg = "see `lighthouse vm create --help` or the HTTP API documentation", + "No validators present" ) } else if total_validators == attesting_validators { info!( - log, - "All validators active"; - "current_epoch_proposers" => proposing_validators, - "active_validators" => attesting_validators, - "total_validators" => total_validators, - "epoch" => format!("{}", epoch), - "slot" => format!("{}", slot), + current_epoch_proposers = proposing_validators, + active_validators = attesting_validators, + total_validators = total_validators, + %epoch, + %slot, + "All validators active" ); } else if attesting_validators > 0 { info!( - log, - "Some validators active"; - "current_epoch_proposers" => proposing_validators, - "active_validators" => attesting_validators, - "total_validators" => total_validators, - "epoch" => format!("{}", epoch), - "slot" => format!("{}", slot), + current_epoch_proposers = proposing_validators, + active_validators = attesting_validators, + total_validators = total_validators, + %epoch, + %slot, + "Some validators active" ); } else { info!( - log, - "Awaiting activation"; - "validators" => total_validators, - "epoch" => format!("{}", epoch), - "slot" => format!("{}", slot), + validators = total_validators, + %epoch, + %slot, + "Awaiting activation" ); } } else { - error!(log, "Unable to read slot clock"); + error!("Unable to read slot clock"); } } diff --git a/validator_client/validator_services/Cargo.toml b/validator_client/validator_services/Cargo.toml index 86208dadef..4b023bb40a 100644 --- a/validator_client/validator_services/Cargo.toml +++ b/validator_client/validator_services/Cargo.toml @@ -6,8 +6,10 @@ authors = ["Sigma Prime "] [dependencies] beacon_node_fallback = { workspace = true } -bls = { workspace = true } +bls = { workspace = true } +doppelganger_service = { workspace = true } either = { workspace = true } +environment = { workspace = true } eth2 = { workspace = true } futures = { workspace = true } graffiti_file = { workspace = true } @@ -15,7 +17,6 @@ logging = { workspace = true } parking_lot = { workspace = true } safe_arith = { workspace = true } slot_clock = { workspace = true } -task_executor = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } tree_hash = { workspace = true } diff --git a/validator_client/validator_services/src/attestation_service.rs b/validator_client/validator_services/src/attestation_service.rs index 62dfa0bfa3..8e098b81b0 100644 --- a/validator_client/validator_services/src/attestation_service.rs +++ b/validator_client/validator_services/src/attestation_service.rs @@ -1,13 +1,13 @@ use crate::duties_service::{DutiesService, DutyAndProof}; use beacon_node_fallback::{ApiTopic, BeaconNodeFallback}; use either::Either; +use environment::RuntimeContext; use futures::future::join_all; use logging::crit; use slot_clock::SlotClock; use std::collections::HashMap; use std::ops::Deref; use std::sync::Arc; -use task_executor::TaskExecutor; use tokio::time::{sleep, sleep_until, Duration, Instant}; use tracing::{debug, error, info, trace, warn}; use tree_hash::TreeHash; @@ -16,33 +16,33 @@ use validator_store::{Error as ValidatorStoreError, ValidatorStore}; /// Builds an `AttestationService`. #[derive(Default)] -pub struct AttestationServiceBuilder { - duties_service: Option>>, - validator_store: Option>, +pub struct AttestationServiceBuilder { + duties_service: Option>>, + validator_store: Option>>, slot_clock: Option, - beacon_nodes: Option>>, - executor: Option, - chain_spec: Option>, + beacon_nodes: Option>>, + context: Option>, + disable: bool, } -impl AttestationServiceBuilder { +impl AttestationServiceBuilder { pub fn new() -> Self { Self { duties_service: None, validator_store: None, slot_clock: None, beacon_nodes: None, - executor: None, - chain_spec: None, + context: None, + disable: false, } } - pub fn duties_service(mut self, service: Arc>) -> Self { + pub fn duties_service(mut self, service: Arc>) -> Self { self.duties_service = Some(service); self } - pub fn validator_store(mut self, store: Arc) -> Self { + pub fn validator_store(mut self, store: Arc>) -> Self { self.validator_store = Some(store); self } @@ -52,22 +52,22 @@ impl AttestationServiceBuil self } - pub fn beacon_nodes(mut self, beacon_nodes: Arc>) -> Self { + pub fn beacon_nodes(mut self, beacon_nodes: Arc>) -> Self { self.beacon_nodes = Some(beacon_nodes); self } - pub fn executor(mut self, executor: TaskExecutor) -> Self { - self.executor = Some(executor); + pub fn runtime_context(mut self, context: RuntimeContext) -> Self { + self.context = Some(context); self } - pub fn chain_spec(mut self, chain_spec: Arc) -> Self { - self.chain_spec = Some(chain_spec); + pub fn disable(mut self, disable: bool) -> Self { + self.disable = disable; self } - pub fn build(self) -> Result, String> { + pub fn build(self) -> Result, String> { Ok(AttestationService { inner: Arc::new(Inner { duties_service: self @@ -82,25 +82,23 @@ impl AttestationServiceBuil beacon_nodes: self .beacon_nodes .ok_or("Cannot build AttestationService without beacon_nodes")?, - executor: self - .executor - .ok_or("Cannot build AttestationService without executor")?, - chain_spec: self - .chain_spec - .ok_or("Cannot build AttestationService without chain_spec")?, + context: self + .context + .ok_or("Cannot build AttestationService without runtime_context")?, + disable: self.disable, }), }) } } /// Helper to minimise `Arc` usage. -pub struct Inner { - duties_service: Arc>, - validator_store: Arc, +pub struct Inner { + duties_service: Arc>, + validator_store: Arc>, slot_clock: T, - beacon_nodes: Arc>, - executor: TaskExecutor, - chain_spec: Arc, + beacon_nodes: Arc>, + context: RuntimeContext, + disable: bool, } /// Attempts to produce attestations for all known validators 1/3rd of the way through each slot. @@ -108,11 +106,11 @@ pub struct Inner { /// If any validators are on the same committee, a single attestation will be downloaded and /// returned to the beacon node. This attestation will have a signature from each of the /// validators. -pub struct AttestationService { - inner: Arc>, +pub struct AttestationService { + inner: Arc>, } -impl Clone for AttestationService { +impl Clone for AttestationService { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -120,17 +118,22 @@ impl Clone for AttestationService { } } -impl Deref for AttestationService { - type Target = Inner; +impl Deref for AttestationService { + type Target = Inner; fn deref(&self) -> &Self::Target { self.inner.deref() } } -impl AttestationService { +impl AttestationService { /// Starts the service which periodically produces attestations. pub fn start_update_service(self, spec: &ChainSpec) -> Result<(), String> { + if self.disable { + info!("Attestation service disabled"); + return Ok(()); + } + let slot_duration = Duration::from_secs(spec.seconds_per_slot); let duration_to_next_slot = self .slot_clock @@ -142,7 +145,7 @@ impl AttestationService AttestationService AttestationService AttestationService(attestation_data, &self.chain_spec) { + if !duty.match_attestation_data::(attestation_data, &self.context.eth2_config.spec) { crit!( validator = ?duty.pubkey, - duty_slot = ?duty.slot, + duty_slot = %duty.slot, attestation_slot = %attestation_data.slot, duty_index = duty.committee_index, attestation_index = attestation_data.index, @@ -366,14 +369,14 @@ impl AttestationService::empty_for_signing( duty.committee_index, duty.committee_length as usize, attestation_data.slot, attestation_data.beacon_block_root, attestation_data.source, attestation_data.target, - &self.chain_spec, + &self.context.eth2_config.spec, ) { Ok(attestation) => attestation, Err(err) => { @@ -436,8 +439,10 @@ impl AttestationService(attestation_data.slot); + .context + .eth2_config + .spec + .fork_name_at_slot::(attestation_data.slot); // Post the attestations to the BN. match self @@ -462,7 +467,7 @@ impl AttestationService AttestationService>(); beacon_node - .post_beacon_pool_attestations_v2::( + .post_beacon_pool_attestations_v2::( Either::Right(single_attestations), fork_name, ) @@ -533,8 +538,10 @@ impl AttestationService(attestation_data.slot); + .context + .eth2_config + .spec + .fork_name_at_slot::(attestation_data.slot); let aggregated_attestation = &self .beacon_nodes @@ -578,7 +585,7 @@ impl AttestationService(attestation_data, &self.chain_spec) { + if !duty.match_attestation_data::(attestation_data, &self.context.eth2_config.spec) { crit!("Inconsistent validator duties during signing"); return None; } @@ -682,11 +689,11 @@ impl AttestationService> for BlockError { /// Builds a `BlockService`. #[derive(Default)] -pub struct BlockServiceBuilder { - validator_store: Option>, +pub struct BlockServiceBuilder { + validator_store: Option>>, slot_clock: Option>, - beacon_nodes: Option>>, - proposer_nodes: Option>>, - executor: Option, - chain_spec: Option>, + beacon_nodes: Option>>, + proposer_nodes: Option>>, + context: Option>, graffiti: Option, graffiti_file: Option, } -impl BlockServiceBuilder { +impl BlockServiceBuilder { pub fn new() -> Self { Self { validator_store: None, slot_clock: None, beacon_nodes: None, proposer_nodes: None, - executor: None, - chain_spec: None, + context: None, graffiti: None, graffiti_file: None, } } - pub fn validator_store(mut self, store: Arc) -> Self { + pub fn validator_store(mut self, store: Arc>) -> Self { self.validator_store = Some(store); self } @@ -80,23 +78,18 @@ impl BlockServiceBuilder { self } - pub fn beacon_nodes(mut self, beacon_nodes: Arc>) -> Self { + pub fn beacon_nodes(mut self, beacon_nodes: Arc>) -> Self { self.beacon_nodes = Some(beacon_nodes); self } - pub fn proposer_nodes(mut self, proposer_nodes: Arc>) -> Self { + pub fn proposer_nodes(mut self, proposer_nodes: Arc>) -> Self { self.proposer_nodes = Some(proposer_nodes); self } - pub fn executor(mut self, executor: TaskExecutor) -> Self { - self.executor = Some(executor); - self - } - - pub fn chain_spec(mut self, chain_spec: Arc) -> Self { - self.chain_spec = Some(chain_spec); + pub fn runtime_context(mut self, context: RuntimeContext) -> Self { + self.context = Some(context); self } @@ -110,7 +103,7 @@ impl BlockServiceBuilder { self } - pub fn build(self) -> Result, String> { + pub fn build(self) -> Result, String> { Ok(BlockService { inner: Arc::new(Inner { validator_store: self @@ -122,12 +115,9 @@ impl BlockServiceBuilder { beacon_nodes: self .beacon_nodes .ok_or("Cannot build BlockService without beacon_node")?, - executor: self - .executor - .ok_or("Cannot build BlockService without executor")?, - chain_spec: self - .chain_spec - .ok_or("Cannot build BlockService without chain_spec")?, + context: self + .context + .ok_or("Cannot build BlockService without runtime_context")?, proposer_nodes: self.proposer_nodes, graffiti: self.graffiti, graffiti_file: self.graffiti_file, @@ -138,12 +128,12 @@ impl BlockServiceBuilder { // Combines a set of non-block-proposing `beacon_nodes` and only-block-proposing // `proposer_nodes`. -pub struct ProposerFallback { - beacon_nodes: Arc>, - proposer_nodes: Option>>, +pub struct ProposerFallback { + beacon_nodes: Arc>, + proposer_nodes: Option>>, } -impl ProposerFallback { +impl ProposerFallback { // Try `func` on `self.proposer_nodes` first. If that doesn't work, try `self.beacon_nodes`. pub async fn request_proposers_first(&self, func: F) -> Result<(), Errors> where @@ -188,23 +178,22 @@ impl ProposerFallback { } /// Helper to minimise `Arc` usage. -pub struct Inner { - validator_store: Arc, +pub struct Inner { + validator_store: Arc>, slot_clock: Arc, - pub beacon_nodes: Arc>, - pub proposer_nodes: Option>>, - executor: TaskExecutor, - chain_spec: Arc, + pub beacon_nodes: Arc>, + pub proposer_nodes: Option>>, + context: RuntimeContext, graffiti: Option, graffiti_file: Option, } /// Attempts to produce attestations for any block producer(s) at the start of the epoch. -pub struct BlockService { - inner: Arc>, +pub struct BlockService { + inner: Arc>, } -impl Clone for BlockService { +impl Clone for BlockService { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -212,8 +201,8 @@ impl Clone for BlockService { } } -impl Deref for BlockService { - type Target = Inner; +impl Deref for BlockService { + type Target = Inner; fn deref(&self) -> &Self::Target { self.inner.deref() @@ -226,14 +215,14 @@ pub struct BlockServiceNotification { pub block_proposers: Vec, } -impl BlockService { +impl BlockService { pub fn start_update_service( self, mut notification_rx: mpsc::Receiver, ) -> Result<(), String> { info!("Block production service started"); - let executor = self.inner.executor.clone(); + let executor = self.inner.context.executor.clone(); executor.spawn( async move { @@ -269,7 +258,7 @@ impl BlockService { return Ok(()); } - if slot == self.chain_spec.genesis_slot { + if slot == self.context.eth2_config.spec.genesis_slot { debug!( proposers = format!("{:?}", notification.block_proposers), "Not producing block at genesis slot" @@ -298,7 +287,7 @@ impl BlockService { for validator_pubkey in proposers { let builder_boost_factor = self.get_builder_boost_factor(&validator_pubkey); let service = self.clone(); - self.inner.executor.spawn( + self.inner.context.executor.spawn( async move { let result = service .publish_block(slot, validator_pubkey, builder_boost_factor) @@ -325,35 +314,30 @@ impl BlockService { #[allow(clippy::too_many_arguments)] async fn sign_and_publish_block( &self, - proposer_fallback: ProposerFallback, + proposer_fallback: ProposerFallback, slot: Slot, graffiti: Option, validator_pubkey: &PublicKeyBytes, - unsigned_block: UnsignedBlock, + unsigned_block: UnsignedBlock, ) -> Result<(), BlockError> { let signing_timer = validator_metrics::start_timer(&validator_metrics::BLOCK_SIGNING_TIMES); - let (block, maybe_blobs) = match unsigned_block { + let res = match unsigned_block { UnsignedBlock::Full(block_contents) => { let (block, maybe_blobs) = block_contents.deconstruct(); - (block.into(), maybe_blobs) + self.validator_store + .sign_block(*validator_pubkey, block, slot) + .await + .map(|b| SignedBlock::Full(PublishBlockRequest::new(Arc::new(b), maybe_blobs))) } - UnsignedBlock::Blinded(block) => (block.into(), None), + UnsignedBlock::Blinded(block) => self + .validator_store + .sign_block(*validator_pubkey, block, slot) + .await + .map(Arc::new) + .map(SignedBlock::Blinded), }; - let res = self - .validator_store - .sign_block(*validator_pubkey, block, slot) - .await - .map(|block| match block { - validator_store::SignedBlock::Full(block) => { - SignedBlock::Full(PublishBlockRequest::new(Arc::new(block), maybe_blobs)) - } - validator_store::SignedBlock::Blinded(block) => { - SignedBlock::Blinded(Arc::new(block)) - } - }); - let signed_block = match res { Ok(block) => block, Err(ValidatorStoreError::UnknownPubkey(pubkey)) => { @@ -420,7 +404,7 @@ impl BlockService { let randao_reveal = match self .validator_store - .randao_reveal(validator_pubkey, slot.epoch(S::E::slots_per_epoch())) + .randao_reveal(validator_pubkey, slot.epoch(E::slots_per_epoch())) .await { Ok(signature) => signature.into(), @@ -503,7 +487,7 @@ impl BlockService { async fn publish_signed_block_contents( &self, - signed_block: &SignedBlock, + signed_block: &SignedBlock, beacon_node: BeaconNodeHttpClient, ) -> Result<(), BlockError> { let slot = signed_block.slot(); @@ -539,9 +523,9 @@ impl BlockService { graffiti: Option, proposer_index: Option, builder_boost_factor: Option, - ) -> Result, BlockError> { + ) -> Result, BlockError> { let (block_response, _) = beacon_node - .get_validator_blocks_v3::( + .get_validator_blocks_v3::( slot, randao_reveal_ref, graffiti.as_ref(), @@ -579,9 +563,15 @@ impl BlockService { // Apply per validator configuration first. let validator_builder_boost_factor = self .validator_store - .determine_builder_boost_factor(validator_pubkey); + .determine_validator_builder_boost_factor(validator_pubkey); - if let Some(builder_boost_factor) = validator_builder_boost_factor { + // Fallback to process-wide configuration if needed. + let maybe_builder_boost_factor = validator_builder_boost_factor.or_else(|| { + self.validator_store + .determine_default_builder_boost_factor() + }); + + if let Some(builder_boost_factor) = maybe_builder_boost_factor { // if builder boost factor is set to 100 it should be treated // as None to prevent unnecessary calculations that could // lead to loss of information. diff --git a/validator_client/validator_services/src/duties_service.rs b/validator_client/validator_services/src/duties_service.rs index 3f987b63ed..0921f95298 100644 --- a/validator_client/validator_services/src/duties_service.rs +++ b/validator_client/validator_services/src/duties_service.rs @@ -10,6 +10,8 @@ use crate::block_service::BlockServiceNotification; use crate::sync::poll_sync_committee_duties; use crate::sync::SyncDutiesMap; use beacon_node_fallback::{ApiTopic, BeaconNodeFallback}; +use doppelganger_service::DoppelgangerStatus; +use environment::RuntimeContext; use eth2::types::{ AttesterData, BeaconCommitteeSubscription, DutiesResponse, ProposerData, StateId, ValidatorId, }; @@ -22,12 +24,11 @@ use std::collections::{hash_map, BTreeMap, HashMap, HashSet}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; -use task_executor::TaskExecutor; use tokio::{sync::mpsc::Sender, time::sleep}; use tracing::{debug, error, info, warn}; use types::{ChainSpec, Epoch, EthSpec, Hash256, PublicKeyBytes, SelectionProof, Slot}; use validator_metrics::{get_int_gauge, set_int_gauge, ATTESTATION_DUTY}; -use validator_store::{DoppelgangerStatus, Error as ValidatorStoreError, ValidatorStore}; +use validator_store::{Error as ValidatorStoreError, ValidatorStore}; /// Only retain `HISTORICAL_DUTIES_EPOCHS` duties prior to the current epoch. const HISTORICAL_DUTIES_EPOCHS: u64 = 2; @@ -86,16 +87,16 @@ const _: () = assert!(ATTESTATION_SUBSCRIPTION_OFFSETS[0] > MIN_ATTESTATION_SUBS // The info in the enum variants is displayed in logging, clippy thinks it's dead code. #[derive(Debug)] -pub enum Error { +pub enum Error { UnableToReadSlotClock, FailedToDownloadAttesters(#[allow(dead_code)] String), - FailedToProduceSelectionProof(#[allow(dead_code)] ValidatorStoreError), + FailedToProduceSelectionProof(#[allow(dead_code)] ValidatorStoreError), InvalidModulo(#[allow(dead_code)] ArithError), Arith(#[allow(dead_code)] ArithError), SyncDutiesNotFound(#[allow(dead_code)] u64), } -impl From for Error { +impl From for Error { fn from(e: ArithError) -> Self { Self::Arith(e) } @@ -124,11 +125,11 @@ pub struct SubscriptionSlots { /// Create a selection proof for `duty`. /// /// Return `Ok(None)` if the attesting validator is not an aggregator. -async fn make_selection_proof( +async fn make_selection_proof( duty: &AttesterData, - validator_store: &S, + validator_store: &ValidatorStore, spec: &ChainSpec, -) -> Result, Error> { +) -> Result, Error> { let selection_proof = validator_store .produce_selection_proof(duty.pubkey, duty.slot) .await @@ -204,133 +205,35 @@ type DependentRoot = Hash256; type AttesterMap = HashMap>; type ProposerMap = HashMap)>; -pub struct DutiesServiceBuilder { - /// Provides the canonical list of locally-managed validators. - validator_store: Option>, - /// Tracks the current slot. - slot_clock: Option, - /// Provides HTTP access to remote beacon nodes. - beacon_nodes: Option>>, - /// The runtime for spawning tasks. - executor: Option, - /// The current chain spec. - spec: Option>, - //// Whether we permit large validator counts in the metrics. - enable_high_validator_count_metrics: bool, - /// If this validator is running in distributed mode. - distributed: bool, -} - -impl Default for DutiesServiceBuilder { - fn default() -> Self { - Self::new() - } -} - -impl DutiesServiceBuilder { - pub fn new() -> Self { - Self { - validator_store: None, - slot_clock: None, - beacon_nodes: None, - executor: None, - spec: None, - enable_high_validator_count_metrics: false, - distributed: false, - } - } - - pub fn validator_store(mut self, validator_store: Arc) -> Self { - self.validator_store = Some(validator_store); - self - } - - pub fn slot_clock(mut self, slot_clock: T) -> Self { - self.slot_clock = Some(slot_clock); - self - } - - pub fn beacon_nodes(mut self, beacon_nodes: Arc>) -> Self { - self.beacon_nodes = Some(beacon_nodes); - self - } - - pub fn executor(mut self, executor: TaskExecutor) -> Self { - self.executor = Some(executor); - self - } - - pub fn spec(mut self, spec: Arc) -> Self { - self.spec = Some(spec); - self - } - - pub fn enable_high_validator_count_metrics( - mut self, - enable_high_validator_count_metrics: bool, - ) -> Self { - self.enable_high_validator_count_metrics = enable_high_validator_count_metrics; - self - } - - pub fn distributed(mut self, distributed: bool) -> Self { - self.distributed = distributed; - self - } - - pub fn build(self) -> Result, String> { - Ok(DutiesService { - attesters: Default::default(), - proposers: Default::default(), - sync_duties: SyncDutiesMap::new(self.distributed), - validator_store: self - .validator_store - .ok_or("Cannot build DutiesService without validator_store")?, - unknown_validator_next_poll_slots: Default::default(), - slot_clock: self - .slot_clock - .ok_or("Cannot build DutiesService without slot_clock")?, - beacon_nodes: self - .beacon_nodes - .ok_or("Cannot build DutiesService without beacon_nodes")?, - executor: self - .executor - .ok_or("Cannot build DutiesService without executor")?, - spec: self.spec.ok_or("Cannot build DutiesService without spec")?, - enable_high_validator_count_metrics: self.enable_high_validator_count_metrics, - distributed: self.distributed, - }) - } -} - /// See the module-level documentation. -pub struct DutiesService { +pub struct DutiesService { /// Maps a validator public key to their duties for each epoch. pub attesters: RwLock, /// Maps an epoch to all *local* proposers in this epoch. Notably, this does not contain /// proposals for any validators which are not registered locally. pub proposers: RwLock, /// Map from validator index to sync committee duties. - pub sync_duties: SyncDutiesMap, + pub sync_duties: SyncDutiesMap, /// Provides the canonical list of locally-managed validators. - pub validator_store: Arc, + pub validator_store: Arc>, /// Maps unknown validator pubkeys to the next slot time when a poll should be conducted again. pub unknown_validator_next_poll_slots: RwLock>, /// Tracks the current slot. pub slot_clock: T, /// Provides HTTP access to remote beacon nodes. - pub beacon_nodes: Arc>, + pub beacon_nodes: Arc>, /// The runtime for spawning tasks. - pub executor: TaskExecutor, + pub context: RuntimeContext, /// The current chain spec. pub spec: Arc, //// Whether we permit large validator counts in the metrics. pub enable_high_validator_count_metrics: bool, /// If this validator is running in distributed mode. pub distributed: bool, + pub disable_attesting: bool, } -impl DutiesService { +impl DutiesService { /// Returns the total number of validators known to the duties service. pub fn total_validator_count(&self) -> usize { self.validator_store.num_voting_validators() @@ -380,8 +283,8 @@ impl DutiesService { /// /// It is possible that multiple validators have an identical proposal slot, however that is /// likely the result of heavy forking (lol) or inconsistent beacon node connections. - pub fn block_proposers(&self, slot: Slot) -> HashSet { - let epoch = slot.epoch(S::E::slots_per_epoch()); + pub fn block_proposers(&self, slot: Slot) -> HashSet { + let epoch = slot.epoch(E::slots_per_epoch()); // Only collect validators that are considered safe in terms of doppelganger protection. let signing_pubkeys: HashSet<_> = self @@ -406,7 +309,7 @@ impl DutiesService { /// Returns all `ValidatorDuty` for the given `slot`. pub fn attesters(&self, slot: Slot) -> Vec { - let epoch = slot.epoch(S::E::slots_per_epoch()); + let epoch = slot.epoch(E::slots_per_epoch()); // Only collect validators that are considered safe in terms of doppelganger protection. let signing_pubkeys: HashSet<_> = self @@ -444,15 +347,15 @@ impl DutiesService { /// process every slot, which has the chance of creating a theoretically unlimited backlog of tasks. /// It was a conscious decision to choose to drop tasks on an overloaded/latent system rather than /// overload it even more. -pub fn start_update_service( - core_duties_service: Arc>, +pub fn start_update_service( + core_duties_service: Arc>, mut block_service_tx: Sender, ) { /* * Spawn the task which updates the map of pubkey to validator index. */ let duties_service = core_duties_service.clone(); - core_duties_service.executor.spawn( + core_duties_service.context.executor.spawn( async move { loop { // Run this poll before the wait, this should hopefully download all the indices @@ -475,7 +378,7 @@ pub fn start_update_service * Spawn the task which keeps track of local block proposal duties. */ let duties_service = core_duties_service.clone(); - core_duties_service.executor.spawn( + core_duties_service.context.executor.spawn( async move { loop { if let Some(duration) = duties_service.slot_clock.duration_to_next_slot() { @@ -499,11 +402,16 @@ pub fn start_update_service "duties_service_proposers", ); + // Skip starting attestation duties or sync committee services. + if core_duties_service.disable_attesting { + return; + } + /* * Spawn the task which keeps track of local attestation duties. */ let duties_service = core_duties_service.clone(); - core_duties_service.executor.spawn( + core_duties_service.context.executor.spawn( async move { loop { if let Some(duration) = duties_service.slot_clock.duration_to_next_slot() { @@ -528,7 +436,7 @@ pub fn start_update_service // Spawn the task which keeps track of local sync committee duties. let duties_service = core_duties_service.clone(); - core_duties_service.executor.spawn( + core_duties_service.context.executor.spawn( async move { loop { if let Err(e) = poll_sync_committee_duties(&duties_service).await { @@ -558,8 +466,8 @@ pub fn start_update_service /// Iterate through all the voting pubkeys in the `ValidatorStore` and attempt to learn any unknown /// validator indices. -async fn poll_validator_indices( - duties_service: &DutiesService, +async fn poll_validator_indices( + duties_service: &DutiesService, ) { let _timer = validator_metrics::start_timer_vec( &validator_metrics::DUTIES_SERVICE_TIMES, @@ -578,14 +486,16 @@ async fn poll_validator_indices( // This is on its own line to avoid some weirdness with locks and if statements. let is_known = duties_service .validator_store - .validator_index(&pubkey) + .initialized_validators() + .read() + .get_index(&pubkey) .is_some(); if !is_known { let current_slot_opt = duties_service.slot_clock.now(); if let Some(current_slot) = current_slot_opt { - let is_first_slot_of_epoch = current_slot % S::E::slots_per_epoch() == 0; + let is_first_slot_of_epoch = current_slot % E::slots_per_epoch() == 0; // Query an unknown validator later if it was queried within the last epoch, or if // the current slot is the first slot of an epoch. @@ -636,7 +546,9 @@ async fn poll_validator_indices( ); duties_service .validator_store - .set_validator_index(&pubkey, response.data.index); + .initialized_validators() + .write() + .set_index(&pubkey, response.data.index); duties_service .unknown_validator_next_poll_slots @@ -647,7 +559,7 @@ async fn poll_validator_indices( // the beacon chain. Ok(None) => { if let Some(current_slot) = current_slot_opt { - let next_poll_slot = current_slot.saturating_add(S::E::slots_per_epoch()); + let next_poll_slot = current_slot.saturating_add(E::slots_per_epoch()); duties_service .unknown_validator_next_poll_slots .write() @@ -678,9 +590,9 @@ async fn poll_validator_indices( /// 2. As above, but for the next-epoch. /// 3. Push out any attestation subnet subscriptions to the BN. /// 4. Prune old entries from `duties_service.attesters`. -async fn poll_beacon_attesters( - duties_service: &Arc>, -) -> Result<(), Error> { +async fn poll_beacon_attesters( + duties_service: &Arc>, +) -> Result<(), Error> { let current_epoch_timer = validator_metrics::start_timer_vec( &validator_metrics::DUTIES_SERVICE_TIMES, &[validator_metrics::UPDATE_ATTESTERS_CURRENT_EPOCH], @@ -690,7 +602,7 @@ async fn poll_beacon_attesters(duties_service, current_epoch, current_slot); drop(current_epoch_timer); let next_epoch_timer = validator_metrics::start_timer_vec( @@ -750,7 +664,7 @@ async fn poll_beacon_attesters(duties_service, next_epoch, current_slot); drop(next_epoch_timer); let subscriptions_timer = validator_metrics::start_timer_vec( @@ -771,7 +685,7 @@ async fn poll_beacon_attesters( - duties_service: &Arc>, +async fn poll_beacon_attesters_for_epoch( + duties_service: &Arc>, epoch: Epoch, local_indices: &[u64], local_pubkeys: &HashSet, -) -> Result<(), Error> { +) -> Result<(), Error> { // No need to bother the BN if we don't have any validators. if local_indices.is_empty() { debug!( @@ -910,10 +824,10 @@ async fn poll_beacon_attesters_for_epoch>() @@ -1016,7 +930,7 @@ async fn poll_beacon_attesters_for_epoch( - duties_service: &Arc>, +fn get_uninitialized_validators( + duties_service: &Arc>, epoch: &Epoch, local_pubkeys: &HashSet, ) -> Vec { @@ -1038,14 +952,14 @@ fn get_uninitialized_validators( .filter(|pubkey| { attesters .get(pubkey) - .map_or(true, |duties| !duties.contains_key(epoch)) + .is_none_or(|duties| !duties.contains_key(epoch)) }) .filter_map(|pubkey| duties_service.validator_store.validator_index(pubkey)) .collect::>() } -fn update_per_validator_duty_metrics( - duties_service: &Arc>, +fn update_per_validator_duty_metrics( + duties_service: &Arc>, epoch: Epoch, current_slot: Slot, ) { @@ -1060,14 +974,14 @@ fn update_per_validator_duty_metrics( get_int_gauge(&ATTESTATION_DUTY, &[&validator_index.to_string()]) { let existing_slot = Slot::new(existing_slot_gauge.get() as u64); - let existing_epoch = existing_slot.epoch(S::E::slots_per_epoch()); + let existing_epoch = existing_slot.epoch(E::slots_per_epoch()); // First condition ensures that we switch to the next epoch duty slot // once the current epoch duty slot passes. // Second condition is to ensure that next epoch duties don't override // current epoch duties. if existing_slot < current_slot - || (duty_slot.epoch(S::E::slots_per_epoch()) <= existing_epoch + || (duty_slot.epoch(E::slots_per_epoch()) <= existing_epoch && duty_slot > current_slot && duty_slot != existing_slot) { @@ -1085,11 +999,11 @@ fn update_per_validator_duty_metrics( } } -async fn post_validator_duties_attester( - duties_service: &Arc>, +async fn post_validator_duties_attester( + duties_service: &Arc>, epoch: Epoch, validator_indices: &[u64], -) -> Result>, Error> { +) -> Result>, Error> { duties_service .beacon_nodes .first_success(|beacon_node| async move { @@ -1109,8 +1023,8 @@ async fn post_validator_duties_attester( - duties_service: Arc>, +async fn fill_in_selection_proofs( + duties_service: Arc>, duties: Vec, dependent_root: Hash256, ) { @@ -1161,7 +1075,7 @@ async fn fill_in_selection_proofs { // No need to update duties for which no proof was computed. @@ -1277,10 +1191,10 @@ async fn fill_in_selection_proofs( - duties_service: &DutiesService, +async fn poll_beacon_proposers( + duties_service: &DutiesService, block_service_tx: &mut Sender, -) -> Result<(), Error> { +) -> Result<(), Error> { let _timer = validator_metrics::start_timer_vec( &validator_metrics::DUTIES_SERVICE_TIMES, &[validator_metrics::UPDATE_PROPOSERS], @@ -1290,17 +1204,17 @@ async fn poll_beacon_proposers( .slot_clock .now() .ok_or(Error::UnableToReadSlotClock)?; - let current_epoch = current_slot.epoch(S::E::slots_per_epoch()); + let current_epoch = current_slot.epoch(E::slots_per_epoch()); // Notify the block proposal service for any proposals that we have in our cache. // // See the function-level documentation for more information. - let initial_block_proposers = duties_service.block_proposers::(current_slot); - notify_block_production_service::( + let initial_block_proposers = duties_service.block_proposers(current_slot); + notify_block_production_service( current_slot, &initial_block_proposers, block_service_tx, - duties_service.validator_store.as_ref(), + &duties_service.validator_store, ) .await; @@ -1372,7 +1286,7 @@ async fn poll_beacon_proposers( // Then, compute the difference between these two sets to obtain a set of block proposers // which were not included in the initial notification to the `BlockService`. let additional_block_producers = duties_service - .block_proposers::(current_slot) + .block_proposers(current_slot) .difference(&initial_block_proposers) .copied() .collect::>(); @@ -1382,11 +1296,11 @@ async fn poll_beacon_proposers( // // See the function-level documentation for more reasoning about this behaviour. if !additional_block_producers.is_empty() { - notify_block_production_service::( + notify_block_production_service( current_slot, &additional_block_producers, block_service_tx, - duties_service.validator_store.as_ref(), + &duties_service.validator_store, ) .await; debug!( @@ -1407,11 +1321,11 @@ async fn poll_beacon_proposers( } /// Notify the block service if it should produce a block. -async fn notify_block_production_service( +async fn notify_block_production_service( current_slot: Slot, block_proposers: &HashSet, block_service_tx: &mut Sender, - validator_store: &S, + validator_store: &ValidatorStore, ) { let non_doppelganger_proposers = block_proposers .iter() diff --git a/validator_client/validator_services/src/preparation_service.rs b/validator_client/validator_services/src/preparation_service.rs index 1ac2a012e1..3367f2d6ca 100644 --- a/validator_client/validator_services/src/preparation_service.rs +++ b/validator_client/validator_services/src/preparation_service.rs @@ -1,5 +1,7 @@ use beacon_node_fallback::{ApiTopic, BeaconNodeFallback}; use bls::PublicKeyBytes; +use doppelganger_service::DoppelgangerStatus; +use environment::RuntimeContext; use parking_lot::RwLock; use slot_clock::SlotClock; use std::collections::HashMap; @@ -7,16 +9,13 @@ use std::hash::Hash; use std::ops::Deref; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; -use task_executor::TaskExecutor; use tokio::time::{sleep, Duration}; use tracing::{debug, error, info, warn}; use types::{ Address, ChainSpec, EthSpec, ProposerPreparationData, SignedValidatorRegistrationData, ValidatorRegistrationData, }; -use validator_store::{ - DoppelgangerStatus, Error as ValidatorStoreError, ProposalData, ValidatorStore, -}; +use validator_store::{Error as ValidatorStoreError, ProposalData, ValidatorStore}; /// Number of epochs before the Bellatrix hard fork to begin posting proposer preparations. const PROPOSER_PREPARATION_LOOKAHEAD_EPOCHS: u64 = 2; @@ -26,28 +25,28 @@ const EPOCHS_PER_VALIDATOR_REGISTRATION_SUBMISSION: u64 = 1; /// Builds an `PreparationService`. #[derive(Default)] -pub struct PreparationServiceBuilder { - validator_store: Option>, +pub struct PreparationServiceBuilder { + validator_store: Option>>, slot_clock: Option, - beacon_nodes: Option>>, - executor: Option, + beacon_nodes: Option>>, + context: Option>, builder_registration_timestamp_override: Option, validator_registration_batch_size: Option, } -impl PreparationServiceBuilder { +impl PreparationServiceBuilder { pub fn new() -> Self { Self { validator_store: None, slot_clock: None, beacon_nodes: None, - executor: None, + context: None, builder_registration_timestamp_override: None, validator_registration_batch_size: None, } } - pub fn validator_store(mut self, store: Arc) -> Self { + pub fn validator_store(mut self, store: Arc>) -> Self { self.validator_store = Some(store); self } @@ -57,13 +56,13 @@ impl PreparationServiceBuilder self } - pub fn beacon_nodes(mut self, beacon_nodes: Arc>) -> Self { + pub fn beacon_nodes(mut self, beacon_nodes: Arc>) -> Self { self.beacon_nodes = Some(beacon_nodes); self } - pub fn executor(mut self, executor: TaskExecutor) -> Self { - self.executor = Some(executor); + pub fn runtime_context(mut self, context: RuntimeContext) -> Self { + self.context = Some(context); self } @@ -83,7 +82,7 @@ impl PreparationServiceBuilder self } - pub fn build(self) -> Result, String> { + pub fn build(self) -> Result, String> { Ok(PreparationService { inner: Arc::new(Inner { validator_store: self @@ -95,9 +94,9 @@ impl PreparationServiceBuilder beacon_nodes: self .beacon_nodes .ok_or("Cannot build PreparationService without beacon_nodes")?, - executor: self - .executor - .ok_or("Cannot build PreparationService without executor")?, + context: self + .context + .ok_or("Cannot build PreparationService without runtime_context")?, builder_registration_timestamp_override: self .builder_registration_timestamp_override, validator_registration_batch_size: self.validator_registration_batch_size.ok_or( @@ -110,11 +109,11 @@ impl PreparationServiceBuilder } /// Helper to minimise `Arc` usage. -pub struct Inner { - validator_store: Arc, +pub struct Inner { + validator_store: Arc>, slot_clock: T, - beacon_nodes: Arc>, - executor: TaskExecutor, + beacon_nodes: Arc>, + context: RuntimeContext, builder_registration_timestamp_override: Option, // Used to track unpublished validator registration changes. validator_registration_cache: @@ -146,11 +145,11 @@ impl From for ValidatorRegistrationKey { } /// Attempts to produce proposer preparations for all known validators at the beginning of each epoch. -pub struct PreparationService { - inner: Arc>, +pub struct PreparationService { + inner: Arc>, } -impl Clone for PreparationService { +impl Clone for PreparationService { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -158,15 +157,15 @@ impl Clone for PreparationService { } } -impl Deref for PreparationService { - type Target = Inner; +impl Deref for PreparationService { + type Target = Inner; fn deref(&self) -> &Self::Target { self.inner.deref() } } -impl PreparationService { +impl PreparationService { pub fn start_update_service(self, spec: &ChainSpec) -> Result<(), String> { self.clone().start_validator_registration_service(spec)?; self.start_proposer_prepare_service(spec) @@ -177,7 +176,7 @@ impl PreparationService PreparationService PreparationService bool { - let current_epoch = self.slot_clock.now().map_or(S::E::genesis_epoch(), |slot| { - slot.epoch(S::E::slots_per_epoch()) - }); + let current_epoch = self + .slot_clock + .now() + .map_or(E::genesis_epoch(), |slot| slot.epoch(E::slots_per_epoch())); spec.bellatrix_fork_epoch.is_some_and(|fork_epoch| { current_epoch + PROPOSER_PREPARATION_LOOKAHEAD_EPOCHS >= fork_epoch }) @@ -367,8 +367,7 @@ impl PreparationService PreparationService PreparationService PreparationService info!( - count = batch.len(), - "Published validator registrations to the builder network" - ), + Ok(()) => { + info!( + count = batch.len(), + "Published validator registrations to the builder network" + ); + let mut guard = self.validator_registration_cache.write(); + for signed_data in batch { + guard.insert( + ValidatorRegistrationKey::from(signed_data.message.clone()), + signed_data.clone(), + ); + } + } Err(e) => warn!( error = %e, "Unable to publish validator registrations to the builder network" diff --git a/validator_client/validator_services/src/sync.rs b/validator_client/validator_services/src/sync.rs index 3b2228e686..5151633514 100644 --- a/validator_client/validator_services/src/sync.rs +++ b/validator_client/validator_services/src/sync.rs @@ -1,13 +1,15 @@ use crate::duties_service::{DutiesService, Error}; +use doppelganger_service::DoppelgangerStatus; use futures::future::join_all; use logging::crit; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; use slot_clock::SlotClock; use std::collections::{HashMap, HashSet}; +use std::marker::PhantomData; use std::sync::Arc; use tracing::{debug, info, warn}; use types::{ChainSpec, EthSpec, PublicKeyBytes, Slot, SyncDuty, SyncSelectionProof, SyncSubnetId}; -use validator_store::{DoppelgangerStatus, Error as ValidatorStoreError, ValidatorStore}; +use validator_store::Error as ValidatorStoreError; /// Number of epochs in advance to compute selection proofs when not in `distributed` mode. pub const AGGREGATION_PRE_COMPUTE_EPOCHS: u64 = 2; @@ -26,11 +28,12 @@ pub const AGGREGATION_PRE_COMPUTE_SLOTS_DISTRIBUTED: u64 = 1; /// 2. One-at-a-time locking. For the innermost locks on the aggregator duties, all of the functions /// in this file take care to only lock one validator at a time. We never hold a lock while /// trying to obtain another one (hence no lock ordering issues). -pub struct SyncDutiesMap { +pub struct SyncDutiesMap { /// Map from sync committee period to duties for members of that sync committee. committees: RwLock>, /// Whether we are in `distributed` mode and using reduced lookahead for aggregate pre-compute. distributed: bool, + _phantom: PhantomData, } /// Duties for a single sync committee period. @@ -78,11 +81,12 @@ pub struct SlotDuties { pub aggregators: HashMap>, } -impl SyncDutiesMap { +impl SyncDutiesMap { pub fn new(distributed: bool) -> Self { Self { committees: RwLock::new(HashMap::new()), distributed, + _phantom: PhantomData, } } @@ -100,7 +104,7 @@ impl SyncDutiesMap { } /// Number of slots in advance to compute selection proofs - fn aggregation_pre_compute_slots(&self) -> u64 { + fn aggregation_pre_compute_slots(&self) -> u64 { if self.distributed { AGGREGATION_PRE_COMPUTE_SLOTS_DISTRIBUTED } else { @@ -113,7 +117,7 @@ impl SyncDutiesMap { /// Return the slot up to which proofs should be pre-computed, as well as a vec of /// `(previous_pre_compute_slot, sync_duty)` pairs for all validators which need to have proofs /// computed. See `fill_in_aggregation_proofs` for the actual calculation. - fn prepare_for_aggregator_pre_compute( + fn prepare_for_aggregator_pre_compute( &self, committee_period: u64, current_slot: Slot, @@ -123,7 +127,7 @@ impl SyncDutiesMap { current_slot, first_slot_of_period::(committee_period, spec), ); - let pre_compute_lookahead_slots = self.aggregation_pre_compute_slots::(); + let pre_compute_lookahead_slots = self.aggregation_pre_compute_slots(); let pre_compute_slot = std::cmp::min( current_slot + pre_compute_lookahead_slots, last_slot_of_period::(committee_period, spec), @@ -183,7 +187,7 @@ impl SyncDutiesMap { /// Get duties for all validators for the given `wall_clock_slot`. /// /// This is the entry-point for the sync committee service. - pub fn get_duties_for_slot( + pub fn get_duties_for_slot( &self, wall_clock_slot: Slot, spec: &ChainSpec, @@ -280,21 +284,21 @@ fn last_slot_of_period(sync_committee_period: u64, spec: &ChainSpec) first_slot_of_period::(sync_committee_period + 1, spec) - 1 } -pub async fn poll_sync_committee_duties( - duties_service: &Arc>, -) -> Result<(), Error> { +pub async fn poll_sync_committee_duties( + duties_service: &Arc>, +) -> Result<(), Error> { let sync_duties = &duties_service.sync_duties; let spec = &duties_service.spec; let current_slot = duties_service .slot_clock .now() .ok_or(Error::UnableToReadSlotClock)?; - let current_epoch = current_slot.epoch(S::E::slots_per_epoch()); + let current_epoch = current_slot.epoch(E::slots_per_epoch()); // If the Altair fork is yet to be activated, do not attempt to poll for duties. if spec .altair_fork_epoch - .map_or(true, |altair_epoch| current_epoch < altair_epoch) + .is_none_or(|altair_epoch| current_epoch < altair_epoch) { return Ok(()); } @@ -313,8 +317,10 @@ pub async fn poll_sync_committee_duties( - current_sync_committee_period, - current_slot, - spec, - ); + .prepare_for_aggregator_pre_compute(current_sync_committee_period, current_slot, spec); if !new_pre_compute_duties.is_empty() { let sub_duties_service = duties_service.clone(); - duties_service.executor.spawn( + duties_service.context.executor.spawn( async move { fill_in_aggregation_proofs( sub_duties_service, @@ -377,22 +379,18 @@ pub async fn poll_sync_committee_duties(); + let aggregate_pre_compute_lookahead_slots = sync_duties.aggregation_pre_compute_slots(); if (current_slot + aggregate_pre_compute_lookahead_slots) - .epoch(S::E::slots_per_epoch()) + .epoch(E::slots_per_epoch()) .sync_committee_period(spec)? == next_sync_committee_period { let (pre_compute_slot, new_pre_compute_duties) = sync_duties - .prepare_for_aggregator_pre_compute::( - next_sync_committee_period, - current_slot, - spec, - ); + .prepare_for_aggregator_pre_compute(next_sync_committee_period, current_slot, spec); if !new_pre_compute_duties.is_empty() { let sub_duties_service = duties_service.clone(); - duties_service.executor.spawn( + duties_service.context.executor.spawn( async move { fill_in_aggregation_proofs( sub_duties_service, @@ -411,11 +409,11 @@ pub async fn poll_sync_committee_duties( - duties_service: &Arc>, +pub async fn poll_sync_committee_duties_for_period( + duties_service: &Arc>, local_indices: &[u64], sync_committee_period: u64, -) -> Result<(), Error> { +) -> Result<(), Error> { let spec = &duties_service.spec; // no local validators don't need to poll for sync committee @@ -473,7 +471,7 @@ pub async fn poll_sync_committee_duties_for_period( - duties_service: Arc>, +pub async fn fill_in_aggregation_proofs( + duties_service: Arc>, pre_compute_duties: &[(Slot, SyncDuty)], sync_committee_period: u64, current_slot: Slot, @@ -521,7 +519,7 @@ pub async fn fill_in_aggregation_proofs() { + let subnet_ids = match duty.subnet_ids::() { Ok(subnet_ids) => subnet_ids, Err(e) => { crit!( @@ -566,7 +564,7 @@ pub async fn fill_in_aggregation_proofs() { + match proof.is_aggregator::() { Ok(true) => { debug!( validator_index = duty.validator_index, diff --git a/validator_client/validator_services/src/sync_committee_service.rs b/validator_client/validator_services/src/sync_committee_service.rs index 081447b9b7..d99c0d3107 100644 --- a/validator_client/validator_services/src/sync_committee_service.rs +++ b/validator_client/validator_services/src/sync_committee_service.rs @@ -1,5 +1,6 @@ use crate::duties_service::DutiesService; use beacon_node_fallback::{ApiTopic, BeaconNodeFallback}; +use environment::RuntimeContext; use eth2::types::BlockId; use futures::future::join_all; use futures::future::FutureExt; @@ -9,7 +10,6 @@ use std::collections::HashMap; use std::ops::Deref; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use task_executor::TaskExecutor; use tokio::time::{sleep, sleep_until, Duration, Instant}; use tracing::{debug, error, info, trace, warn}; use types::{ @@ -20,11 +20,11 @@ use validator_store::{Error as ValidatorStoreError, ValidatorStore}; pub const SUBSCRIPTION_LOOKAHEAD_EPOCHS: u64 = 4; -pub struct SyncCommitteeService { - inner: Arc>, +pub struct SyncCommitteeService { + inner: Arc>, } -impl Clone for SyncCommitteeService { +impl Clone for SyncCommitteeService { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -32,33 +32,33 @@ impl Clone for SyncCommitteeService Deref for SyncCommitteeService { - type Target = Inner; +impl Deref for SyncCommitteeService { + type Target = Inner; fn deref(&self) -> &Self::Target { self.inner.deref() } } -pub struct Inner { - duties_service: Arc>, - validator_store: Arc, +pub struct Inner { + duties_service: Arc>, + validator_store: Arc>, slot_clock: T, - beacon_nodes: Arc>, - executor: TaskExecutor, + beacon_nodes: Arc>, + context: RuntimeContext, /// Boolean to track whether the service has posted subscriptions to the BN at least once. /// /// This acts as a latch that fires once upon start-up, and then never again. first_subscription_done: AtomicBool, } -impl SyncCommitteeService { +impl SyncCommitteeService { pub fn new( - duties_service: Arc>, - validator_store: Arc, + duties_service: Arc>, + validator_store: Arc>, slot_clock: T, - beacon_nodes: Arc>, - executor: TaskExecutor, + beacon_nodes: Arc>, + context: RuntimeContext, ) -> Self { Self { inner: Arc::new(Inner { @@ -66,7 +66,7 @@ impl SyncCommitteeService SyncCommitteeService= fork_epoch) }) .unwrap_or(false) } pub fn start_update_service(self, spec: &ChainSpec) -> Result<(), String> { + if self.duties_service.disable_attesting { + info!("Sync committee service disabled"); + return Ok(()); + } + let slot_duration = Duration::from_secs(spec.seconds_per_slot); let duration_to_next_slot = self .slot_clock @@ -98,7 +103,7 @@ impl SyncCommitteeService SyncCommitteeService(slot, &self.duties_service.spec) + .get_duties_for_slot(slot, &self.duties_service.spec) else { debug!("No duties known for slot {}", slot); return Ok(()); @@ -197,7 +202,7 @@ impl SyncCommitteeService SyncCommitteeService SyncCommitteeService SyncCommitteeService(&sync_contribution_data) .await }) .await @@ -435,7 +440,7 @@ impl SyncCommitteeService SyncCommitteeService(slot, spec)?; + let current_period = sync_period_of_slot::(slot, spec)?; if !self.first_subscription_done.load(Ordering::Relaxed) - || slot.as_u64() % S::E::slots_per_epoch() == 0 + || slot.as_u64() % E::slots_per_epoch() == 0 { duty_slots.push((slot, current_period)); } @@ -469,9 +474,9 @@ impl SyncCommitteeService(lookahead_slot, spec)?; + let lookahead_period = sync_period_of_slot::(lookahead_slot, spec)?; if lookahead_period > current_period { duty_slots.push((lookahead_slot, lookahead_period)); @@ -489,7 +494,7 @@ impl SyncCommitteeService(duty_slot, spec) + .get_duties_for_slot(duty_slot, spec) { Some(duties) => subscriptions.extend(subscriptions_from_sync_duties( duties.duties, diff --git a/validator_client/validator_store/Cargo.toml b/validator_client/validator_store/Cargo.toml index 91df9dc3ab..1338c2a07e 100644 --- a/validator_client/validator_store/Cargo.toml +++ b/validator_client/validator_store/Cargo.toml @@ -4,6 +4,21 @@ version = "0.1.0" edition = { workspace = true } authors = ["Sigma Prime "] +[lib] +name = "validator_store" +path = "src/lib.rs" + [dependencies] +account_utils = { workspace = true } +doppelganger_service = { workspace = true } +initialized_validators = { workspace = true } +logging = { workspace = true } +parking_lot = { workspace = true } +serde = { workspace = true } +signing_method = { workspace = true } slashing_protection = { workspace = true } +slot_clock = { workspace = true } +task_executor = { workspace = true } +tracing = { workspace = true } types = { workspace = true } +validator_metrics = { workspace = true } diff --git a/validator_client/validator_store/src/lib.rs b/validator_client/validator_store/src/lib.rs index 6e0a3b0e2e..9b2576847d 100644 --- a/validator_client/validator_store/src/lib.rs +++ b/validator_client/validator_store/src/lib.rs @@ -1,17 +1,31 @@ -use slashing_protection::NotSafe; -use std::fmt::Debug; -use std::future::Future; +use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition}; +use doppelganger_service::{DoppelgangerService, DoppelgangerStatus, DoppelgangerValidatorStore}; +use initialized_validators::InitializedValidators; +use logging::crit; +use parking_lot::{Mutex, RwLock}; +use serde::{Deserialize, Serialize}; +use signing_method::{Error as SigningError, SignableMessage, SigningContext, SigningMethod}; +use slashing_protection::{ + interchange::Interchange, InterchangeError, NotSafe, Safe, SlashingDatabase, +}; +use slot_clock::SlotClock; +use std::marker::PhantomData; +use std::path::Path; +use std::sync::Arc; +use task_executor::TaskExecutor; +use tracing::{error, info, warn}; use types::{ - Address, Attestation, AttestationError, BeaconBlock, BlindedBeaconBlock, Epoch, EthSpec, - Graffiti, Hash256, PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, - SignedBeaconBlock, SignedBlindedBeaconBlock, SignedContributionAndProof, - SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncCommitteeContribution, - SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, - VoluntaryExit, + attestation::Error as AttestationError, graffiti::GraffitiString, AbstractExecPayload, Address, + AggregateAndProof, Attestation, BeaconBlock, BlindedPayload, ChainSpec, ContributionAndProof, + Domain, Epoch, EthSpec, Fork, Graffiti, Hash256, PublicKeyBytes, SelectionProof, Signature, + SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, SignedRoot, + SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncAggregatorSelectionData, + SyncCommitteeContribution, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, + ValidatorRegistrationData, VoluntaryExit, }; -#[derive(Debug, PartialEq, Clone)] -pub enum Error { +#[derive(Debug, PartialEq)] +pub enum Error { DoppelgangerProtected(PublicKeyBytes), UnknownToDoppelgangerService(PublicKeyBytes), UnknownPubkey(PublicKeyBytes), @@ -20,15 +34,31 @@ pub enum Error { GreaterThanCurrentSlot { slot: Slot, current_slot: Slot }, GreaterThanCurrentEpoch { epoch: Epoch, current_epoch: Epoch }, UnableToSignAttestation(AttestationError), - SpecificError(T), + UnableToSign(SigningError), } -impl From for Error { - fn from(e: T) -> Self { - Error::SpecificError(e) +impl From for Error { + fn from(e: SigningError) -> Self { + Error::UnableToSign(e) } } +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Config { + /// Fallback fee recipient address. + pub fee_recipient: Option
, + /// Fallback gas limit. + pub gas_limit: Option, + /// Enable use of the blinded block endpoints during proposals. + pub builder_proposals: bool, + /// Enable slashing protection even while using web3signer keys. + pub enable_web3signer_slashing_protection: bool, + /// If true, Lighthouse will prefer builder proposals, if available. + pub prefer_builder_proposals: bool, + /// Specifies the boost factor, a percentage multiplier to apply to the builder's payload value. + pub builder_boost_factor: Option, +} + /// A helper struct, used for passing data from the validator store to services. pub struct ProposalData { pub validator_index: Option, @@ -37,9 +67,185 @@ pub struct ProposalData { pub builder_proposals: bool, } -pub trait ValidatorStore: Send + Sync { - type Error: Debug + Send + Sync; - type E: EthSpec; +/// Number of epochs of slashing protection history to keep. +/// +/// This acts as a maximum safe-guard against clock drift. +const SLASHING_PROTECTION_HISTORY_EPOCHS: u64 = 512; + +/// Currently used as the default gas limit in execution clients. +/// +/// https://github.com/ethereum/builder-specs/issues/17 +pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000; + +pub struct ValidatorStore { + validators: Arc>, + slashing_protection: SlashingDatabase, + slashing_protection_last_prune: Arc>, + genesis_validators_root: Hash256, + spec: Arc, + doppelganger_service: Option>, + slot_clock: T, + fee_recipient_process: Option
, + gas_limit: Option, + builder_proposals: bool, + enable_web3signer_slashing_protection: bool, + prefer_builder_proposals: bool, + builder_boost_factor: Option, + task_executor: TaskExecutor, + _phantom: PhantomData, +} + +impl DoppelgangerValidatorStore for ValidatorStore { + fn get_validator_index(&self, pubkey: &PublicKeyBytes) -> Option { + self.validator_index(pubkey) + } +} + +impl ValidatorStore { + // All arguments are different types. Making the fields `pub` is undesired. A builder seems + // unnecessary. + #[allow(clippy::too_many_arguments)] + pub fn new( + validators: InitializedValidators, + slashing_protection: SlashingDatabase, + genesis_validators_root: Hash256, + spec: Arc, + doppelganger_service: Option>, + slot_clock: T, + config: &Config, + task_executor: TaskExecutor, + ) -> Self { + Self { + validators: Arc::new(RwLock::new(validators)), + slashing_protection, + slashing_protection_last_prune: Arc::new(Mutex::new(Epoch::new(0))), + genesis_validators_root, + spec, + doppelganger_service, + slot_clock, + fee_recipient_process: config.fee_recipient, + gas_limit: config.gas_limit, + builder_proposals: config.builder_proposals, + enable_web3signer_slashing_protection: config.enable_web3signer_slashing_protection, + prefer_builder_proposals: config.prefer_builder_proposals, + builder_boost_factor: config.builder_boost_factor, + task_executor, + _phantom: PhantomData, + } + } + + /// Register all local validators in doppelganger protection to try and prevent instances of + /// duplicate validators operating on the network at the same time. + /// + /// This function has no effect if doppelganger protection is disabled. + pub fn register_all_in_doppelganger_protection_if_enabled(&self) -> Result<(), String> { + if let Some(doppelganger_service) = &self.doppelganger_service { + for pubkey in self.validators.read().iter_voting_pubkeys() { + doppelganger_service.register_new_validator::(*pubkey, &self.slot_clock)? + } + } + + Ok(()) + } + + /// Returns `true` if doppelganger protection is enabled, or else `false`. + pub fn doppelganger_protection_enabled(&self) -> bool { + self.doppelganger_service.is_some() + } + + pub fn initialized_validators(&self) -> Arc> { + self.validators.clone() + } + + /// Indicates if the `voting_public_key` exists in self and is enabled. + pub fn has_validator(&self, voting_public_key: &PublicKeyBytes) -> bool { + self.validators + .read() + .validator(voting_public_key) + .is_some() + } + + /// Insert a new validator to `self`, where the validator is represented by an EIP-2335 + /// keystore on the filesystem. + #[allow(clippy::too_many_arguments)] + pub async fn add_validator_keystore>( + &self, + voting_keystore_path: P, + password_storage: PasswordStorage, + enable: bool, + graffiti: Option, + suggested_fee_recipient: Option
, + gas_limit: Option, + builder_proposals: Option, + builder_boost_factor: Option, + prefer_builder_proposals: Option, + ) -> Result { + let mut validator_def = ValidatorDefinition::new_keystore_with_password( + voting_keystore_path, + password_storage, + graffiti, + suggested_fee_recipient, + gas_limit, + builder_proposals, + builder_boost_factor, + prefer_builder_proposals, + ) + .map_err(|e| format!("failed to create validator definitions: {:?}", e))?; + + validator_def.enabled = enable; + + self.add_validator(validator_def).await + } + + /// Insert a new validator to `self`. + /// + /// This function includes: + /// + /// - Adding the validator definition to the YAML file, saving it to the filesystem. + /// - Enabling the validator with the slashing protection database. + /// - If `enable == true`, starting to perform duties for the validator. + // FIXME: ignore this clippy lint until the validator store is refactored to use async locks + #[allow(clippy::await_holding_lock)] + pub async fn add_validator( + &self, + validator_def: ValidatorDefinition, + ) -> Result { + let validator_pubkey = validator_def.voting_public_key.compress(); + + self.slashing_protection + .register_validator(validator_pubkey) + .map_err(|e| format!("failed to register validator: {:?}", e))?; + + if let Some(doppelganger_service) = &self.doppelganger_service { + doppelganger_service + .register_new_validator::(validator_pubkey, &self.slot_clock)?; + } + + self.validators + .write() + .add_definition_replace_disabled(validator_def.clone()) + .await + .map_err(|e| format!("Unable to add definition: {:?}", e))?; + + Ok(validator_def) + } + + /// Returns `ProposalData` for the provided `pubkey` if it exists in `InitializedValidators`. + /// `ProposalData` fields include defaulting logic described in `get_fee_recipient_defaulting`, + /// `get_gas_limit_defaulting`, and `get_builder_proposals_defaulting`. + pub fn proposal_data(&self, pubkey: &PublicKeyBytes) -> Option { + self.validators + .read() + .validator(pubkey) + .map(|validator| ProposalData { + validator_index: validator.get_index(), + fee_recipient: self + .get_fee_recipient_defaulting(validator.get_suggested_fee_recipient()), + gas_limit: self.get_gas_limit_defaulting(validator.get_gas_limit()), + builder_proposals: self + .get_builder_proposals_defaulting(validator.get_builder_proposals()), + }) + } /// Attempts to resolve the pubkey to a validator index. /// @@ -47,7 +253,9 @@ pub trait ValidatorStore: Send + Sync { /// /// - Unknown. /// - Known, but with an unknown index. - fn validator_index(&self, pubkey: &PublicKeyBytes) -> Option; + pub fn validator_index(&self, pubkey: &PublicKeyBytes) -> Option { + self.validators.read().get_index(pubkey) + } /// Returns all voting pubkeys for all enabled validators. /// @@ -58,25 +266,255 @@ pub trait ValidatorStore: Send + Sync { /// protection and are safe-enough to sign messages. /// - `DoppelgangerStatus::ignored`: returns all the pubkeys from `only_safe` *plus* those still /// undergoing protection. This is useful for collecting duties or other non-signing tasks. - fn voting_pubkeys(&self, filter_func: F) -> I + #[allow(clippy::needless_collect)] // Collect is required to avoid holding a lock. + pub fn voting_pubkeys(&self, filter_func: F) -> I where I: FromIterator, - F: Fn(DoppelgangerStatus) -> Option; + F: Fn(DoppelgangerStatus) -> Option, + { + // Collect all the pubkeys first to avoid interleaving locks on `self.validators` and + // `self.doppelganger_service()`. + let pubkeys = self + .validators + .read() + .iter_voting_pubkeys() + .cloned() + .collect::>(); + + pubkeys + .into_iter() + .map(|pubkey| { + self.doppelganger_service + .as_ref() + .map(|doppelganger_service| doppelganger_service.validator_status(pubkey)) + // Allow signing on all pubkeys if doppelganger protection is disabled. + .unwrap_or_else(|| DoppelgangerStatus::SigningEnabled(pubkey)) + }) + .filter_map(filter_func) + .collect() + } + + /// Returns doppelganger statuses for all enabled validators. + #[allow(clippy::needless_collect)] // Collect is required to avoid holding a lock. + pub fn doppelganger_statuses(&self) -> Vec { + // Collect all the pubkeys first to avoid interleaving locks on `self.validators` and + // `self.doppelganger_service`. + let pubkeys = self + .validators + .read() + .iter_voting_pubkeys() + .cloned() + .collect::>(); + + pubkeys + .into_iter() + .map(|pubkey| { + self.doppelganger_service + .as_ref() + .map(|doppelganger_service| doppelganger_service.validator_status(pubkey)) + // Allow signing on all pubkeys if doppelganger protection is disabled. + .unwrap_or_else(|| DoppelgangerStatus::SigningEnabled(pubkey)) + }) + .collect() + } /// Check if the `validator_pubkey` is permitted by the doppleganger protection to sign /// messages. - fn doppelganger_protection_allows_signing(&self, validator_pubkey: PublicKeyBytes) -> bool; + pub fn doppelganger_protection_allows_signing(&self, validator_pubkey: PublicKeyBytes) -> bool { + self.doppelganger_service + .as_ref() + // If there's no doppelganger service then we assume it is purposefully disabled and + // declare that all keys are safe with regard to it. + .is_none_or(|doppelganger_service| { + doppelganger_service + .validator_status(validator_pubkey) + .only_safe() + .is_some() + }) + } - fn num_voting_validators(&self) -> usize; - fn graffiti(&self, validator_pubkey: &PublicKeyBytes) -> Option; + pub fn num_voting_validators(&self) -> usize { + self.validators.read().num_enabled() + } + + fn fork(&self, epoch: Epoch) -> Fork { + self.spec.fork_at_epoch(epoch) + } + + /// Returns a `SigningMethod` for `validator_pubkey` *only if* that validator is considered safe + /// by doppelganger protection. + fn doppelganger_checked_signing_method( + &self, + validator_pubkey: PublicKeyBytes, + ) -> Result, Error> { + if self.doppelganger_protection_allows_signing(validator_pubkey) { + self.validators + .read() + .signing_method(&validator_pubkey) + .ok_or(Error::UnknownPubkey(validator_pubkey)) + } else { + Err(Error::DoppelgangerProtected(validator_pubkey)) + } + } + + /// Returns a `SigningMethod` for `validator_pubkey` regardless of that validators doppelganger + /// protection status. + /// + /// ## Warning + /// + /// This method should only be used for signing non-slashable messages. + fn doppelganger_bypassed_signing_method( + &self, + validator_pubkey: PublicKeyBytes, + ) -> Result, Error> { + self.validators + .read() + .signing_method(&validator_pubkey) + .ok_or(Error::UnknownPubkey(validator_pubkey)) + } + + fn signing_context(&self, domain: Domain, signing_epoch: Epoch) -> SigningContext { + if domain == Domain::VoluntaryExit { + if self.spec.fork_name_at_epoch(signing_epoch).deneb_enabled() { + // EIP-7044 + SigningContext { + domain, + epoch: signing_epoch, + fork: Fork { + previous_version: self.spec.capella_fork_version, + current_version: self.spec.capella_fork_version, + epoch: signing_epoch, + }, + genesis_validators_root: self.genesis_validators_root, + } + } else { + SigningContext { + domain, + epoch: signing_epoch, + fork: self.fork(signing_epoch), + genesis_validators_root: self.genesis_validators_root, + } + } + } else { + SigningContext { + domain, + epoch: signing_epoch, + fork: self.fork(signing_epoch), + genesis_validators_root: self.genesis_validators_root, + } + } + } + + pub async fn randao_reveal( + &self, + validator_pubkey: PublicKeyBytes, + signing_epoch: Epoch, + ) -> Result { + let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; + let signing_context = self.signing_context(Domain::Randao, signing_epoch); + + let signature = signing_method + .get_signature::>( + SignableMessage::RandaoReveal(signing_epoch), + signing_context, + &self.spec, + &self.task_executor, + ) + .await?; + + Ok(signature) + } + + pub fn graffiti(&self, validator_pubkey: &PublicKeyBytes) -> Option { + self.validators.read().graffiti(validator_pubkey) + } /// Returns the fee recipient for the given public key. The priority order for fetching /// the fee recipient is: /// 1. validator_definitions.yml /// 2. process level fee recipient - fn get_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option
; + pub fn get_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option
{ + // If there is a `suggested_fee_recipient` in the validator definitions yaml + // file, use that value. + self.get_fee_recipient_defaulting(self.suggested_fee_recipient(validator_pubkey)) + } - /// Translate the `builder_proposals`, `builder_boost_factor` and + pub fn get_fee_recipient_defaulting(&self, fee_recipient: Option
) -> Option
{ + // If there's nothing in the file, try the process-level default value. + fee_recipient.or(self.fee_recipient_process) + } + + /// Returns the suggested_fee_recipient from `validator_definitions.yml` if any. + /// This has been pulled into a private function so the read lock is dropped easily + fn suggested_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option
{ + self.validators + .read() + .suggested_fee_recipient(validator_pubkey) + } + + /// Returns the gas limit for the given public key. The priority order for fetching + /// the gas limit is: + /// + /// 1. validator_definitions.yml + /// 2. process level gas limit + /// 3. `DEFAULT_GAS_LIMIT` + pub fn get_gas_limit(&self, validator_pubkey: &PublicKeyBytes) -> u64 { + self.get_gas_limit_defaulting(self.validators.read().gas_limit(validator_pubkey)) + } + + fn get_gas_limit_defaulting(&self, gas_limit: Option) -> u64 { + // If there is a `gas_limit` in the validator definitions yaml + // file, use that value. + gas_limit + // If there's nothing in the file, try the process-level default value. + .or(self.gas_limit) + // If there's no process-level default, use the `DEFAULT_GAS_LIMIT`. + .unwrap_or(DEFAULT_GAS_LIMIT) + } + + /// Returns a `bool` for the given public key that denotes whether this validator should use the + /// builder API. The priority order for fetching this value is: + /// + /// 1. validator_definitions.yml + /// 2. process level flag + pub fn get_builder_proposals(&self, validator_pubkey: &PublicKeyBytes) -> bool { + // If there is a `suggested_fee_recipient` in the validator definitions yaml + // file, use that value. + self.get_builder_proposals_defaulting( + self.validators.read().builder_proposals(validator_pubkey), + ) + } + + /// Returns a `u64` for the given public key that denotes the builder boost factor. The priority order for fetching this value is: + /// + /// 1. validator_definitions.yml + /// 2. process level flag + pub fn get_builder_boost_factor(&self, validator_pubkey: &PublicKeyBytes) -> Option { + self.validators + .read() + .builder_boost_factor(validator_pubkey) + .or(self.builder_boost_factor) + } + + /// Returns a `bool` for the given public key that denotes whether this validator should prefer a + /// builder payload. The priority order for fetching this value is: + /// + /// 1. validator_definitions.yml + /// 2. process level flag + pub fn get_prefer_builder_proposals(&self, validator_pubkey: &PublicKeyBytes) -> bool { + self.validators + .read() + .prefer_builder_proposals(validator_pubkey) + .unwrap_or(self.prefer_builder_proposals) + } + + fn get_builder_proposals_defaulting(&self, builder_proposals: Option) -> bool { + builder_proposals + // If there's nothing in the file, try the process-level default value. + .unwrap_or(self.builder_proposals) + } + + /// Translate the per validator `builder_proposals`, `builder_boost_factor` and /// `prefer_builder_proposals` to a boost factor, if available. /// - If `prefer_builder_proposals` is true, set boost factor to `u64::MAX` to indicate a /// preference for builder payloads. @@ -84,193 +522,576 @@ pub trait ValidatorStore: Send + Sync { /// - If `builder_proposals` is set to false, set boost factor to 0 to indicate a preference for /// local payloads. /// - Else return `None` to indicate no preference between builder and local payloads. - fn determine_builder_boost_factor(&self, validator_pubkey: &PublicKeyBytes) -> Option; + pub fn determine_validator_builder_boost_factor( + &self, + validator_pubkey: &PublicKeyBytes, + ) -> Option { + let validator_prefer_builder_proposals = self + .validators + .read() + .prefer_builder_proposals(validator_pubkey); - fn randao_reveal( + if matches!(validator_prefer_builder_proposals, Some(true)) { + return Some(u64::MAX); + } + + self.validators + .read() + .builder_boost_factor(validator_pubkey) + .or_else(|| { + if matches!( + self.validators.read().builder_proposals(validator_pubkey), + Some(false) + ) { + return Some(0); + } + None + }) + } + + /// Translate the process-wide `builder_proposals`, `builder_boost_factor` and + /// `prefer_builder_proposals` configurations to a boost factor. + /// - If `prefer_builder_proposals` is true, set boost factor to `u64::MAX` to indicate a + /// preference for builder payloads. + /// - If `builder_boost_factor` is a value other than None, return its value as the boost factor. + /// - If `builder_proposals` is set to false, set boost factor to 0 to indicate a preference for + /// local payloads. + /// - Else return `None` to indicate no preference between builder and local payloads. + pub fn determine_default_builder_boost_factor(&self) -> Option { + if self.prefer_builder_proposals { + return Some(u64::MAX); + } + self.builder_boost_factor.or({ + if !self.builder_proposals { + Some(0) + } else { + None + } + }) + } + + pub async fn sign_block>( &self, validator_pubkey: PublicKeyBytes, - signing_epoch: Epoch, - ) -> impl Future>> + Send; - - fn set_validator_index(&self, validator_pubkey: &PublicKeyBytes, index: u64); - - fn sign_block( - &self, - validator_pubkey: PublicKeyBytes, - block: UnsignedBlock, + block: BeaconBlock, current_slot: Slot, - ) -> impl Future, Error>> + Send; + ) -> Result, Error> { + // Make sure the block slot is not higher than the current slot to avoid potential attacks. + if block.slot() > current_slot { + warn!( + block_slot = block.slot().as_u64(), + current_slot = current_slot.as_u64(), + "Not signing block with slot greater than current slot" + ); + return Err(Error::GreaterThanCurrentSlot { + slot: block.slot(), + current_slot, + }); + } - fn sign_attestation( + let signing_epoch = block.epoch(); + let signing_context = self.signing_context(Domain::BeaconProposer, signing_epoch); + let domain_hash = signing_context.domain_hash(&self.spec); + + let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; + + // Check for slashing conditions. + let slashing_status = if signing_method + .requires_local_slashing_protection(self.enable_web3signer_slashing_protection) + { + self.slashing_protection.check_and_insert_block_proposal( + &validator_pubkey, + &block.block_header(), + domain_hash, + ) + } else { + Ok(Safe::Valid) + }; + + match slashing_status { + // We can safely sign this block without slashing. + Ok(Safe::Valid) => { + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_BLOCKS_TOTAL, + &[validator_metrics::SUCCESS], + ); + + let signature = signing_method + .get_signature::( + SignableMessage::BeaconBlock(&block), + signing_context, + &self.spec, + &self.task_executor, + ) + .await?; + Ok(SignedBeaconBlock::from_block(block, signature)) + } + Ok(Safe::SameData) => { + warn!("Skipping signing of previously signed block"); + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_BLOCKS_TOTAL, + &[validator_metrics::SAME_DATA], + ); + Err(Error::SameData) + } + Err(NotSafe::UnregisteredValidator(pk)) => { + warn!( + msg = "Carefully consider running with --init-slashing-protection (see --help)", + public_key = format!("{:?}", pk), + "Not signing block for unregistered validator" + ); + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_BLOCKS_TOTAL, + &[validator_metrics::UNREGISTERED], + ); + Err(Error::Slashable(NotSafe::UnregisteredValidator(pk))) + } + Err(e) => { + crit!(error = format!("{:?}", e), "Not signing slashable block"); + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_BLOCKS_TOTAL, + &[validator_metrics::SLASHABLE], + ); + Err(Error::Slashable(e)) + } + } + } + + pub async fn sign_attestation( &self, validator_pubkey: PublicKeyBytes, validator_committee_position: usize, - attestation: &mut Attestation, + attestation: &mut Attestation, current_epoch: Epoch, - ) -> impl Future>> + Send; + ) -> Result<(), Error> { + // Make sure the target epoch is not higher than the current epoch to avoid potential attacks. + if attestation.data().target.epoch > current_epoch { + return Err(Error::GreaterThanCurrentEpoch { + epoch: attestation.data().target.epoch, + current_epoch, + }); + } - fn sign_voluntary_exit( + // Get the signing method and check doppelganger protection. + let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; + + // Checking for slashing conditions. + let signing_epoch = attestation.data().target.epoch; + let signing_context = self.signing_context(Domain::BeaconAttester, signing_epoch); + let domain_hash = signing_context.domain_hash(&self.spec); + let slashing_status = if signing_method + .requires_local_slashing_protection(self.enable_web3signer_slashing_protection) + { + self.slashing_protection.check_and_insert_attestation( + &validator_pubkey, + attestation.data(), + domain_hash, + ) + } else { + Ok(Safe::Valid) + }; + + match slashing_status { + // We can safely sign this attestation. + Ok(Safe::Valid) => { + let signature = signing_method + .get_signature::>( + SignableMessage::AttestationData(attestation.data()), + signing_context, + &self.spec, + &self.task_executor, + ) + .await?; + attestation + .add_signature(&signature, validator_committee_position) + .map_err(Error::UnableToSignAttestation)?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, + &[validator_metrics::SUCCESS], + ); + + Ok(()) + } + Ok(Safe::SameData) => { + warn!("Skipping signing of previously signed attestation"); + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, + &[validator_metrics::SAME_DATA], + ); + Err(Error::SameData) + } + Err(NotSafe::UnregisteredValidator(pk)) => { + warn!( + msg = "Carefully consider running with --init-slashing-protection (see --help)", + public_key = format!("{:?}", pk), + "Not signing attestation for unregistered validator" + ); + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, + &[validator_metrics::UNREGISTERED], + ); + Err(Error::Slashable(NotSafe::UnregisteredValidator(pk))) + } + Err(e) => { + crit!( + attestation = format!("{:?}", attestation.data()), + error = format!("{:?}", e), + "Not signing slashable attestation" + ); + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_ATTESTATIONS_TOTAL, + &[validator_metrics::SLASHABLE], + ); + Err(Error::Slashable(e)) + } + } + } + + pub async fn sign_voluntary_exit( &self, validator_pubkey: PublicKeyBytes, voluntary_exit: VoluntaryExit, - ) -> impl Future>> + Send; + ) -> Result { + let signing_epoch = voluntary_exit.epoch; + let signing_context = self.signing_context(Domain::VoluntaryExit, signing_epoch); + let signing_method = self.doppelganger_bypassed_signing_method(validator_pubkey)?; - fn sign_validator_registration_data( + let signature = signing_method + .get_signature::>( + SignableMessage::VoluntaryExit(&voluntary_exit), + signing_context, + &self.spec, + &self.task_executor, + ) + .await?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_VOLUNTARY_EXITS_TOTAL, + &[validator_metrics::SUCCESS], + ); + + Ok(SignedVoluntaryExit { + message: voluntary_exit, + signature, + }) + } + + pub async fn sign_validator_registration_data( &self, validator_registration_data: ValidatorRegistrationData, - ) -> impl Future>> + Send; + ) -> Result { + let domain_hash = self.spec.get_builder_domain(); + let signing_root = validator_registration_data.signing_root(domain_hash); + + let signing_method = + self.doppelganger_bypassed_signing_method(validator_registration_data.pubkey)?; + let signature = signing_method + .get_signature_from_root::>( + SignableMessage::ValidatorRegistration(&validator_registration_data), + signing_root, + &self.task_executor, + None, + ) + .await?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_VALIDATOR_REGISTRATIONS_TOTAL, + &[validator_metrics::SUCCESS], + ); + + Ok(SignedValidatorRegistrationData { + message: validator_registration_data, + signature, + }) + } /// Signs an `AggregateAndProof` for a given validator. /// /// The resulting `SignedAggregateAndProof` is sent on the aggregation channel and cannot be /// modified by actors other than the signing validator. - fn produce_signed_aggregate_and_proof( + pub async fn produce_signed_aggregate_and_proof( &self, validator_pubkey: PublicKeyBytes, aggregator_index: u64, - aggregate: Attestation, + aggregate: Attestation, selection_proof: SelectionProof, - ) -> impl Future, Error>> + Send; + ) -> Result, Error> { + let signing_epoch = aggregate.data().target.epoch; + let signing_context = self.signing_context(Domain::AggregateAndProof, signing_epoch); + + let message = + AggregateAndProof::from_attestation(aggregator_index, aggregate, selection_proof); + + let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; + let signature = signing_method + .get_signature::>( + SignableMessage::SignedAggregateAndProof(message.to_ref()), + signing_context, + &self.spec, + &self.task_executor, + ) + .await?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_AGGREGATES_TOTAL, + &[validator_metrics::SUCCESS], + ); + + Ok(SignedAggregateAndProof::from_aggregate_and_proof( + message, signature, + )) + } /// Produces a `SelectionProof` for the `slot`, signed by with corresponding secret key to /// `validator_pubkey`. - fn produce_selection_proof( + pub async fn produce_selection_proof( &self, validator_pubkey: PublicKeyBytes, slot: Slot, - ) -> impl Future>> + Send; + ) -> Result { + let signing_epoch = slot.epoch(E::slots_per_epoch()); + let signing_context = self.signing_context(Domain::SelectionProof, signing_epoch); + + // Bypass the `with_validator_signing_method` function. + // + // This is because we don't care about doppelganger protection when it comes to selection + // proofs. They are not slashable and we need them to subscribe to subnets on the BN. + // + // As long as we disallow `SignedAggregateAndProof` then these selection proofs will never + // be published on the network. + let signing_method = self.doppelganger_bypassed_signing_method(validator_pubkey)?; + + let signature = signing_method + .get_signature::>( + SignableMessage::SelectionProof(slot), + signing_context, + &self.spec, + &self.task_executor, + ) + .await + .map_err(Error::UnableToSign)?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_SELECTION_PROOFS_TOTAL, + &[validator_metrics::SUCCESS], + ); + + Ok(signature.into()) + } /// Produce a `SyncSelectionProof` for `slot` signed by the secret key of `validator_pubkey`. - fn produce_sync_selection_proof( + pub async fn produce_sync_selection_proof( &self, validator_pubkey: &PublicKeyBytes, slot: Slot, subnet_id: SyncSubnetId, - ) -> impl Future>> + Send; + ) -> Result { + let signing_epoch = slot.epoch(E::slots_per_epoch()); + let signing_context = + self.signing_context(Domain::SyncCommitteeSelectionProof, signing_epoch); - fn produce_sync_committee_signature( + // Bypass `with_validator_signing_method`: sync committee messages are not slashable. + let signing_method = self.doppelganger_bypassed_signing_method(*validator_pubkey)?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_SYNC_SELECTION_PROOFS_TOTAL, + &[validator_metrics::SUCCESS], + ); + + let message = SyncAggregatorSelectionData { + slot, + subcommittee_index: subnet_id.into(), + }; + + let signature = signing_method + .get_signature::>( + SignableMessage::SyncSelectionProof(&message), + signing_context, + &self.spec, + &self.task_executor, + ) + .await + .map_err(Error::UnableToSign)?; + + Ok(signature.into()) + } + + pub async fn produce_sync_committee_signature( &self, slot: Slot, beacon_block_root: Hash256, validator_index: u64, validator_pubkey: &PublicKeyBytes, - ) -> impl Future>> + Send; + ) -> Result { + let signing_epoch = slot.epoch(E::slots_per_epoch()); + let signing_context = self.signing_context(Domain::SyncCommittee, signing_epoch); - fn produce_signed_contribution_and_proof( + // Bypass `with_validator_signing_method`: sync committee messages are not slashable. + let signing_method = self.doppelganger_bypassed_signing_method(*validator_pubkey)?; + + let signature = signing_method + .get_signature::>( + SignableMessage::SyncCommitteeSignature { + beacon_block_root, + slot, + }, + signing_context, + &self.spec, + &self.task_executor, + ) + .await + .map_err(Error::UnableToSign)?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_SYNC_COMMITTEE_MESSAGES_TOTAL, + &[validator_metrics::SUCCESS], + ); + + Ok(SyncCommitteeMessage { + slot, + beacon_block_root, + validator_index, + signature, + }) + } + + pub async fn produce_signed_contribution_and_proof( &self, aggregator_index: u64, aggregator_pubkey: PublicKeyBytes, - contribution: SyncCommitteeContribution, + contribution: SyncCommitteeContribution, selection_proof: SyncSelectionProof, - ) -> impl Future, Error>> + Send; + ) -> Result, Error> { + let signing_epoch = contribution.slot.epoch(E::slots_per_epoch()); + let signing_context = self.signing_context(Domain::ContributionAndProof, signing_epoch); + + // Bypass `with_validator_signing_method`: sync committee messages are not slashable. + let signing_method = self.doppelganger_bypassed_signing_method(aggregator_pubkey)?; + + let message = ContributionAndProof { + aggregator_index, + contribution, + selection_proof: selection_proof.into(), + }; + + let signature = signing_method + .get_signature::>( + SignableMessage::SignedContributionAndProof(&message), + signing_context, + &self.spec, + &self.task_executor, + ) + .await + .map_err(Error::UnableToSign)?; + + validator_metrics::inc_counter_vec( + &validator_metrics::SIGNED_SYNC_COMMITTEE_CONTRIBUTIONS_TOTAL, + &[validator_metrics::SUCCESS], + ); + + Ok(SignedContributionAndProof { message, signature }) + } + + pub fn import_slashing_protection( + &self, + interchange: Interchange, + ) -> Result<(), InterchangeError> { + self.slashing_protection + .import_interchange_info(interchange, self.genesis_validators_root)?; + Ok(()) + } + + /// Export slashing protection data while also disabling the given keys in the database. + /// + /// If any key is unknown to the slashing protection database it will be silently omitted + /// from the result. It is the caller's responsibility to check whether all keys provided + /// had data returned for them. + pub fn export_slashing_protection_for_keys( + &self, + pubkeys: &[PublicKeyBytes], + ) -> Result { + self.slashing_protection.with_transaction(|txn| { + let known_pubkeys = pubkeys + .iter() + .filter_map(|pubkey| { + let validator_id = self + .slashing_protection + .get_validator_id_ignoring_status(txn, pubkey) + .ok()?; + + Some( + self.slashing_protection + .update_validator_status(txn, validator_id, false) + .map(|()| *pubkey), + ) + }) + .collect::, _>>()?; + self.slashing_protection.export_interchange_info_in_txn( + self.genesis_validators_root, + Some(&known_pubkeys), + txn, + ) + }) + } /// Prune the slashing protection database so that it remains performant. /// /// This function will only do actual pruning periodically, so it should usually be /// cheap to call. The `first_run` flag can be used to print a more verbose message when pruning /// runs. - fn prune_slashing_protection_db(&self, current_epoch: Epoch, first_run: bool); - - /// Returns `ProposalData` for the provided `pubkey` if it exists in `InitializedValidators`. - /// `ProposalData` fields include defaulting logic described in `get_fee_recipient_defaulting`, - /// `get_gas_limit_defaulting`, and `get_builder_proposals_defaulting`. - fn proposal_data(&self, pubkey: &PublicKeyBytes) -> Option; -} - -#[derive(Clone, Debug, PartialEq)] -pub enum UnsignedBlock { - Full(BeaconBlock), - Blinded(BlindedBeaconBlock), -} - -impl From> for UnsignedBlock { - fn from(block: BeaconBlock) -> Self { - UnsignedBlock::Full(block) - } -} - -impl From> for UnsignedBlock { - fn from(block: BlindedBeaconBlock) -> Self { - UnsignedBlock::Blinded(block) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum SignedBlock { - Full(SignedBeaconBlock), - Blinded(SignedBlindedBeaconBlock), -} - -impl From> for SignedBlock { - fn from(block: SignedBeaconBlock) -> Self { - SignedBlock::Full(block) - } -} - -impl From> for SignedBlock { - fn from(block: SignedBlindedBeaconBlock) -> Self { - SignedBlock::Blinded(block) - } -} - -/// A wrapper around `PublicKeyBytes` which encodes information about the status of a validator -/// pubkey with regards to doppelganger protection. -#[derive(Debug, PartialEq)] -pub enum DoppelgangerStatus { - /// Doppelganger protection has approved this for signing. - /// - /// This is because the service has waited some period of time to - /// detect other instances of this key on the network. - SigningEnabled(PublicKeyBytes), - /// Doppelganger protection is still waiting to detect other instances. - /// - /// Do not use this pubkey for signing slashable messages!! - /// - /// However, it can safely be used for other non-slashable operations (e.g., collecting duties - /// or subscribing to subnets). - SigningDisabled(PublicKeyBytes), - /// This pubkey is unknown to the doppelganger service. - /// - /// This represents a serious internal error in the program. This validator will be permanently - /// disabled! - UnknownToDoppelganger(PublicKeyBytes), -} - -impl DoppelgangerStatus { - /// Only return a pubkey if it is explicitly safe for doppelganger protection. - /// - /// If `Some(pubkey)` is returned, doppelganger has declared it safe for signing. - /// - /// ## Note - /// - /// "Safe" is only best-effort by doppelganger. There is no guarantee that a doppelganger - /// doesn't exist. - pub fn only_safe(self) -> Option { - match self { - DoppelgangerStatus::SigningEnabled(pubkey) => Some(pubkey), - DoppelgangerStatus::SigningDisabled(_) => None, - DoppelgangerStatus::UnknownToDoppelganger(_) => None, + pub fn prune_slashing_protection_db(&self, current_epoch: Epoch, first_run: bool) { + // Attempt to prune every SLASHING_PROTECTION_HISTORY_EPOCHs, with a tolerance for + // missing the epoch that aligns exactly. + let mut last_prune = self.slashing_protection_last_prune.lock(); + if current_epoch / SLASHING_PROTECTION_HISTORY_EPOCHS + <= *last_prune / SLASHING_PROTECTION_HISTORY_EPOCHS + { + return; } - } - /// Returns a key regardless of whether or not doppelganger has approved it. Such a key might be - /// used for signing non-slashable messages, duties collection or other activities. - /// - /// If the validator is unknown to doppelganger then `None` will be returned. - pub fn ignored(self) -> Option { - match self { - DoppelgangerStatus::SigningEnabled(pubkey) => Some(pubkey), - DoppelgangerStatus::SigningDisabled(pubkey) => Some(pubkey), - DoppelgangerStatus::UnknownToDoppelganger(_) => None, + if first_run { + info!( + epoch = %current_epoch, + msg = "pruning may take several minutes the first time it runs", + "Pruning slashing protection DB" + ); + } else { + info!(epoch = %current_epoch, "Pruning slashing protection DB"); } - } - /// Only return a pubkey if it will not be used for signing due to doppelganger detection. - pub fn only_unsafe(self) -> Option { - match self { - DoppelgangerStatus::SigningEnabled(_) => None, - DoppelgangerStatus::SigningDisabled(pubkey) => Some(pubkey), - DoppelgangerStatus::UnknownToDoppelganger(pubkey) => Some(pubkey), + let _timer = + validator_metrics::start_timer(&validator_metrics::SLASHING_PROTECTION_PRUNE_TIMES); + + let new_min_target_epoch = current_epoch.saturating_sub(SLASHING_PROTECTION_HISTORY_EPOCHS); + let new_min_slot = new_min_target_epoch.start_slot(E::slots_per_epoch()); + + let all_pubkeys: Vec<_> = self.voting_pubkeys(DoppelgangerStatus::ignored); + + if let Err(e) = self + .slashing_protection + .prune_all_signed_attestations(all_pubkeys.iter(), new_min_target_epoch) + { + error!( + error = ?e, + "Error during pruning of signed attestations" + ); + return; } + + if let Err(e) = self + .slashing_protection + .prune_all_signed_blocks(all_pubkeys.iter(), new_min_slot) + { + error!( + error = ?e, + "Error during pruning of signed blocks" + ); + return; + } + + *last_prune = current_epoch; + + info!("Completed pruning of slashing protection DB"); } } diff --git a/watch/.gitignore b/watch/.gitignore deleted file mode 100644 index 5b6b0720c9..0000000000 --- a/watch/.gitignore +++ /dev/null @@ -1 +0,0 @@ -config.yaml diff --git a/watch/Cargo.toml b/watch/Cargo.toml deleted file mode 100644 index 41cfb58e28..0000000000 --- a/watch/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "watch" -version = "0.1.0" -edition = { workspace = true } - -[lib] -name = "watch" -path = "src/lib.rs" - -[[bin]] -name = "watch" -path = "src/main.rs" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -axum = "0.7" -beacon_node = { workspace = true } -bls = { workspace = true } -clap = { workspace = true } -clap_utils = { workspace = true } -diesel = { version = "2.0.2", features = ["postgres", "r2d2"] } -diesel_migrations = { version = "2.0.0", features = ["postgres"] } -env_logger = { workspace = true } -eth2 = { workspace = true } -hyper = { workspace = true } -log = { workspace = true } -r2d2 = { workspace = true } -rand = { workspace = true } -reqwest = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -serde_yaml = { workspace = true } -tokio = { workspace = true } -types = { workspace = true } -url = { workspace = true } - -[dev-dependencies] -beacon_chain = { workspace = true } -http_api = { workspace = true } -logging = { workspace = true } -network = { workspace = true } -task_executor = { workspace = true } -testcontainers = "0.15" -tokio-postgres = "0.7.5" -unused_port = { workspace = true } diff --git a/watch/README.md b/watch/README.md deleted file mode 100644 index 877cddf234..0000000000 --- a/watch/README.md +++ /dev/null @@ -1,458 +0,0 @@ -## beacon.watch - ->beacon.watch is pre-MVP and still under active development and subject to change. - -beacon.watch is an Ethereum Beacon Chain monitoring platform whose goal is to provide fast access to -data which is: -1. Not already stored natively in the Beacon Chain -2. Too specialized for Block Explorers -3. Too sensitive for public Block Explorers - - -### Requirements -- `git` -- `rust` : https://rustup.rs/ -- `libpq` : https://www.postgresql.org/download/ -- `diesel_cli` : -``` -cargo install diesel_cli --no-default-features --features postgres -``` -- `docker` : https://docs.docker.com/engine/install/ -- `docker-compose` : https://docs.docker.com/compose/install/ - -### Setup -1. Setup the database: -``` -cd postgres_docker_compose -docker-compose up -``` - -1. Ensure the tests pass: -``` -cargo test --release -``` - -1. Drop the database (if it already exists) and run the required migrations: -``` -diesel database reset --database-url postgres://postgres:postgres@localhost/dev -``` - -1. Ensure a synced Lighthouse beacon node with historical states is available -at `localhost:5052`. - -1. Run the updater daemon: -``` -cargo run --release -- run-updater -``` - -1. Start the HTTP API server: -``` -cargo run --release -- serve -``` - -1. Ensure connectivity: -``` -curl "http://localhost:5059/v1/slots/highest" -``` - -> Functionality on MacOS has not been tested. Windows is not supported. - - -### Configuration -beacon.watch can be configured through the use of a config file. -Available options can be seen in `config.yaml.default`. - -You can specify a config file during runtime: -``` -cargo run -- run-updater --config path/to/config.yaml -cargo run -- serve --config path/to/config.yaml -``` - -You can specify only the parts of the config file which you need changed. -Missing values will remain as their defaults. - -For example, if you wish to run with default settings but only wish to alter `log_level` -your config file would be: -```yaml -# config.yaml -log_level = "info" -``` - -### Available Endpoints -As beacon.watch continues to develop, more endpoints will be added. - -> In these examples any data containing information from blockprint has either been redacted or fabricated. - -#### `/v1/slots/{slot}` -```bash -curl "http://localhost:5059/v1/slots/4635296" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "skipped": false, - "beacon_block": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" -} -``` - -#### `/v1/slots?start_slot={}&end_slot={}` -```bash -curl "http://localhost:5059/v1/slots?start_slot=4635296&end_slot=4635297" -``` -```json -[ - { - "slot": "4635297", - "root": "0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182", - "skipped": false, - "beacon_block": "0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182" - }, - { - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "skipped": false, - "beacon_block": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" - } -] -``` - -#### `/v1/slots/lowest` -```bash -curl "http://localhost:5059/v1/slots/lowest" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "skipped": false, - "beacon_block": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" -} -``` - -#### `/v1/slots/highest` -```bash -curl "http://localhost:5059/v1/slots/highest" -``` -```json -{ - "slot": "4635358", - "root": "0xe9eff13560688f1bf15cf07b60c84963d4d04a4a885ed0eb19ceb8450011894b", - "skipped": false, - "beacon_block": "0xe9eff13560688f1bf15cf07b60c84963d4d04a4a885ed0eb19ceb8450011894b" -} -``` - -#### `v1/slots/{slot}/block` -```bash -curl "http://localhost:5059/v1/slots/4635296/block" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" -} -``` - -#### `/v1/blocks/{block_id}` -```bash -curl "http://localhost:5059/v1/blocks/4635296" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" -} -``` - -#### `/v1/blocks?start_slot={}&end_slot={}` -```bash -curl "http://localhost:5059/v1/blocks?start_slot=4635296&end_slot=4635297" -``` -```json -[ - { - "slot": "4635297", - "root": "0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182", - "parent_root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" - }, - { - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" - } -] -``` - -#### `/v1/blocks/{block_id}/previous` -```bash -curl "http://localhost:5059/v1/blocks/4635297/previous" -# OR -curl "http://localhost:5059/v1/blocks/0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182/previous" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" -} -``` - -#### `/v1/blocks/{block_id}/next` -```bash -curl "http://localhost:5059/v1/blocks/4635296/next" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62/next" -``` -```json -{ - "slot": "4635297", - "root": "0x04ad2e963811207e344bebeba5b1217805bcc3a9e2ed9fcf2205d491778c6182", - "parent_root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62" -} -``` - -#### `/v1/blocks/lowest` -```bash -curl "http://localhost:5059/v1/blocks/lowest" -``` -```json -{ - "slot": "4635296", - "root": "0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62", - "parent_root": "0x7c4860b420a23de9d126da71f9043b3744af98c847efd9e1440f2da8fbf7f31b" -} -``` - -#### `/v1/blocks/highest` -```bash -curl "http://localhost:5059/v1/blocks/highest" -``` -```json -{ - "slot": "4635358", - "root": "0xe9eff13560688f1bf15cf07b60c84963d4d04a4a885ed0eb19ceb8450011894b", - "parent_root": "0xb66e05418bb5b1d4a965c994e1f0e5b5f0d7b780e0df12f3f6321510654fa1d2" -} -``` - -#### `/v1/blocks/{block_id}/proposer` -```bash -curl "http://localhost:5059/v1/blocks/4635296/proposer" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62/proposer" - -``` -```json -{ - "slot": "4635296", - "proposer_index": 223126, - "graffiti": "" -} -``` - -#### `/v1/blocks/{block_id}/rewards` -```bash -curl "http://localhost:5059/v1/blocks/4635296/reward" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62/reward" - -``` -```json -{ - "slot": "4635296", - "total": 25380059, - "attestation_reward": 24351867, - "sync_committee_reward": 1028192 -} -``` - -#### `/v1/blocks/{block_id}/packing` -```bash -curl "http://localhost:5059/v1/blocks/4635296/packing" -# OR -curl "http://localhost:5059/v1/blocks/0xf7063a9d6c663682e59bd0b41d29ce80c3ff0b089049ff8676d6f9ee79622c62/packing" - -``` -```json -{ - "slot": "4635296", - "available": 16152, - "included": 13101, - "prior_skip_slots": 0 -} -``` - -#### `/v1/validators/{validator}` -```bash -curl "http://localhost:5059/v1/validators/1" -# OR -curl "http://localhost:5059/v1/validators/0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c" -``` -```json -{ - "index": 1, - "public_key": "0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c", - "status": "active_ongoing", - "client": null, - "activation_epoch": 0, - "exit_epoch": null -} -``` - -#### `/v1/validators/{validator}/attestation/{epoch}` -```bash -curl "http://localhost:5059/v1/validators/1/attestation/144853" -# OR -curl "http://localhost:5059/v1/validators/0xa1d1ad0714035353258038e964ae9675dc0252ee22cea896825c01458e1807bfad2f9969338798548d9858a571f7425c/attestation/144853" -``` -```json -{ - "index": 1, - "epoch": "144853", - "source": true, - "head": true, - "target": true -} -``` - -#### `/v1/validators/missed/{vote}/{epoch}` -```bash -curl "http://localhost:5059/v1/validators/missed/head/144853" -``` -```json -[ - 63, - 67, - 98, - ... -] -``` - -#### `/v1/validators/missed/{vote}/{epoch}/graffiti` -```bash -curl "http://localhost:5059/v1/validators/missed/head/144853/graffiti" -``` -```json -{ - "Mr F was here": 3, - "Lighthouse/v3.1.0-aa022f4": 5, - ... -} -``` - -#### `/v1/clients/missed/{vote}/{epoch}` -```bash -curl "http://localhost:5059/v1/clients/missed/source/144853" -``` -```json -{ - "Lighthouse": 100, - "Lodestar": 100, - "Nimbus": 100, - "Prysm": 100, - "Teku": 100, - "Unknown": 100 -} -``` - -#### `/v1/clients/missed/{vote}/{epoch}/percentages` -Note that this endpoint expresses the following: -``` -What percentage of each client implementation missed this vote? -``` - -```bash -curl "http://localhost:5059/v1/clients/missed/target/144853/percentages" -``` -```json -{ - "Lighthouse": 0.51234567890, - "Lodestar": 0.51234567890, - "Nimbus": 0.51234567890, - "Prysm": 0.09876543210, - "Teku": 0.09876543210, - "Unknown": 0.05647382910 -} -``` - -#### `/v1/clients/missed/{vote}/{epoch}/percentages/relative` -Note that this endpoint expresses the following: -``` -For the validators which did miss this vote, what percentage of them were from each client implementation? -``` -You can check these values against the output of `/v1/clients/percentages` to see any discrepancies. - -```bash -curl "http://localhost:5059/v1/clients/missed/target/144853/percentages/relative" -``` -```json -{ - "Lighthouse": 11.11111111111111, - "Lodestar": 11.11111111111111, - "Nimbus": 11.11111111111111, - "Prysm": 16.66666666666667, - "Teku": 16.66666666666667, - "Unknown": 33.33333333333333 -} - -``` - -#### `/v1/clients` -```bash -curl "http://localhost:5059/v1/clients" -``` -```json -{ - "Lighthouse": 5000, - "Lodestar": 5000, - "Nimbus": 5000, - "Prysm": 5000, - "Teku": 5000, - "Unknown": 5000 -} -``` - -#### `/v1/clients/percentages` -```bash -curl "http://localhost:5059/v1/clients/percentages" -``` -```json -{ - "Lighthouse": 16.66666666666667, - "Lodestar": 16.66666666666667, - "Nimbus": 16.66666666666667, - "Prysm": 16.66666666666667, - "Teku": 16.66666666666667, - "Unknown": 16.66666666666667 -} -``` - -### Future work -- New tables - - `skip_slots`? - - -- More API endpoints - - `/v1/proposers?start_epoch={}&end_epoch={}` and similar - - `/v1/validators/{status}/count` - - -- Concurrently backfill and forwards fill, so forwards fill is not bottlenecked by large backfills. - - -- Better/prettier (async?) logging. - - -- Connect to a range of beacon_nodes to sync different components concurrently. -Generally, processing certain api queries such as `block_packing` and `attestation_performance` take the longest to sync. - - -### Architecture -Connection Pooling: -- 1 Pool for Updater (read and write) -- 1 Pool for HTTP Server (should be read only, although not sure if we can enforce this) diff --git a/watch/config.yaml.default b/watch/config.yaml.default deleted file mode 100644 index 131609237c..0000000000 --- a/watch/config.yaml.default +++ /dev/null @@ -1,49 +0,0 @@ ---- -database: - user: "postgres" - password: "postgres" - dbname: "dev" - default_dbname: "postgres" - host: "localhost" - port: 5432 - connect_timeout_millis: 2000 - -server: - listen_addr: "127.0.0.1" - listen_port: 5059 - -updater: - # The URL of the Beacon Node to perform sync tasks with. - # Cannot yet accept multiple beacon nodes. - beacon_node_url: "http://localhost:5052" - # The number of epochs to backfill. Must be below 100. - max_backfill_size_epochs: 2 - # The epoch at which to stop backfilling. - backfill_stop_epoch: 0 - # Whether to sync the attestations table. - attestations: true - # Whether to sync the proposer_info table. - proposer_info: true - # Whether to sync the block_rewards table. - block_rewards: true - # Whether to sync the block_packing table. - block_packing: true - -blockprint: - # Whether to sync client information from blockprint. - enabled: false - # The URL of the blockprint server. - url: "" - # The username used to authenticate to the blockprint server. - username: "" - # The password used to authenticate to the blockprint server. - password: "" - -# Log level. -# Valid options are: -# - "trace" -# - "debug" -# - "info" -# - "warn" -# - "error" -log_level: "debug" diff --git a/watch/diesel.toml b/watch/diesel.toml deleted file mode 100644 index bfb01bccf0..0000000000 --- a/watch/diesel.toml +++ /dev/null @@ -1,5 +0,0 @@ -# For documentation on how to configure this file, -# see diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/database/schema.rs" diff --git a/watch/migrations/.gitkeep b/watch/migrations/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/watch/migrations/00000000000000_diesel_initial_setup/down.sql b/watch/migrations/00000000000000_diesel_initial_setup/down.sql deleted file mode 100644 index a9f5260911..0000000000 --- a/watch/migrations/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - -DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); -DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/watch/migrations/00000000000000_diesel_initial_setup/up.sql b/watch/migrations/00000000000000_diesel_initial_setup/up.sql deleted file mode 100644 index d68895b1a7..0000000000 --- a/watch/migrations/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - - - - --- Sets up a trigger for the given table to automatically set a column called --- `updated_at` whenever the row is modified (unless `updated_at` was included --- in the modified columns) --- --- # Example --- --- ```sql --- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); --- --- SELECT diesel_manage_updated_at('users'); --- ``` -CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ -BEGIN - EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s - FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ -BEGIN - IF ( - NEW IS DISTINCT FROM OLD AND - NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at - ) THEN - NEW.updated_at := current_timestamp; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; diff --git a/watch/migrations/2022-01-01-000000_canonical_slots/down.sql b/watch/migrations/2022-01-01-000000_canonical_slots/down.sql deleted file mode 100644 index 551ed6605c..0000000000 --- a/watch/migrations/2022-01-01-000000_canonical_slots/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE canonical_slots diff --git a/watch/migrations/2022-01-01-000000_canonical_slots/up.sql b/watch/migrations/2022-01-01-000000_canonical_slots/up.sql deleted file mode 100644 index 2629f11a4c..0000000000 --- a/watch/migrations/2022-01-01-000000_canonical_slots/up.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE canonical_slots ( - slot integer PRIMARY KEY, - root bytea NOT NULL, - skipped boolean NOT NULL, - beacon_block bytea UNIQUE -) diff --git a/watch/migrations/2022-01-01-000001_beacon_blocks/down.sql b/watch/migrations/2022-01-01-000001_beacon_blocks/down.sql deleted file mode 100644 index 8901956f47..0000000000 --- a/watch/migrations/2022-01-01-000001_beacon_blocks/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE beacon_blocks diff --git a/watch/migrations/2022-01-01-000001_beacon_blocks/up.sql b/watch/migrations/2022-01-01-000001_beacon_blocks/up.sql deleted file mode 100644 index 250c667b23..0000000000 --- a/watch/migrations/2022-01-01-000001_beacon_blocks/up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE beacon_blocks ( - slot integer PRIMARY KEY REFERENCES canonical_slots(slot) ON DELETE CASCADE, - root bytea REFERENCES canonical_slots(beacon_block) NOT NULL, - parent_root bytea NOT NULL, - attestation_count integer NOT NULL, - transaction_count integer -) diff --git a/watch/migrations/2022-01-01-000002_validators/down.sql b/watch/migrations/2022-01-01-000002_validators/down.sql deleted file mode 100644 index 17819fc349..0000000000 --- a/watch/migrations/2022-01-01-000002_validators/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE validators diff --git a/watch/migrations/2022-01-01-000002_validators/up.sql b/watch/migrations/2022-01-01-000002_validators/up.sql deleted file mode 100644 index 69cfef6772..0000000000 --- a/watch/migrations/2022-01-01-000002_validators/up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE validators ( - index integer PRIMARY KEY, - public_key bytea NOT NULL, - status text NOT NULL, - activation_epoch integer, - exit_epoch integer -) diff --git a/watch/migrations/2022-01-01-000003_proposer_info/down.sql b/watch/migrations/2022-01-01-000003_proposer_info/down.sql deleted file mode 100644 index d61330be5b..0000000000 --- a/watch/migrations/2022-01-01-000003_proposer_info/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE proposer_info diff --git a/watch/migrations/2022-01-01-000003_proposer_info/up.sql b/watch/migrations/2022-01-01-000003_proposer_info/up.sql deleted file mode 100644 index 488aedb273..0000000000 --- a/watch/migrations/2022-01-01-000003_proposer_info/up.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE proposer_info ( - slot integer PRIMARY KEY REFERENCES beacon_blocks(slot) ON DELETE CASCADE, - proposer_index integer REFERENCES validators(index) ON DELETE CASCADE NOT NULL, - graffiti text NOT NULL -) diff --git a/watch/migrations/2022-01-01-000004_active_config/down.sql b/watch/migrations/2022-01-01-000004_active_config/down.sql deleted file mode 100644 index b4304eb7b7..0000000000 --- a/watch/migrations/2022-01-01-000004_active_config/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE active_config diff --git a/watch/migrations/2022-01-01-000004_active_config/up.sql b/watch/migrations/2022-01-01-000004_active_config/up.sql deleted file mode 100644 index 476a091160..0000000000 --- a/watch/migrations/2022-01-01-000004_active_config/up.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE active_config ( - id integer PRIMARY KEY CHECK (id=1), - config_name text NOT NULL, - slots_per_epoch integer NOT NULL -) diff --git a/watch/migrations/2022-01-01-000010_blockprint/down.sql b/watch/migrations/2022-01-01-000010_blockprint/down.sql deleted file mode 100644 index fa53325dad..0000000000 --- a/watch/migrations/2022-01-01-000010_blockprint/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE blockprint diff --git a/watch/migrations/2022-01-01-000010_blockprint/up.sql b/watch/migrations/2022-01-01-000010_blockprint/up.sql deleted file mode 100644 index 2d5741f50b..0000000000 --- a/watch/migrations/2022-01-01-000010_blockprint/up.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE blockprint ( - slot integer PRIMARY KEY REFERENCES beacon_blocks(slot) ON DELETE CASCADE, - best_guess text NOT NULL -) diff --git a/watch/migrations/2022-01-01-000011_block_rewards/down.sql b/watch/migrations/2022-01-01-000011_block_rewards/down.sql deleted file mode 100644 index 2dc87995c7..0000000000 --- a/watch/migrations/2022-01-01-000011_block_rewards/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE block_rewards diff --git a/watch/migrations/2022-01-01-000011_block_rewards/up.sql b/watch/migrations/2022-01-01-000011_block_rewards/up.sql deleted file mode 100644 index 47cb4304f0..0000000000 --- a/watch/migrations/2022-01-01-000011_block_rewards/up.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE block_rewards ( - slot integer PRIMARY KEY REFERENCES beacon_blocks(slot) ON DELETE CASCADE, - total integer NOT NULL, - attestation_reward integer NOT NULL, - sync_committee_reward integer NOT NULL -) diff --git a/watch/migrations/2022-01-01-000012_block_packing/down.sql b/watch/migrations/2022-01-01-000012_block_packing/down.sql deleted file mode 100644 index e9e7755e3e..0000000000 --- a/watch/migrations/2022-01-01-000012_block_packing/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE block_packing diff --git a/watch/migrations/2022-01-01-000012_block_packing/up.sql b/watch/migrations/2022-01-01-000012_block_packing/up.sql deleted file mode 100644 index 63a9925f92..0000000000 --- a/watch/migrations/2022-01-01-000012_block_packing/up.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE block_packing ( - slot integer PRIMARY KEY REFERENCES beacon_blocks(slot) ON DELETE CASCADE, - available integer NOT NULL, - included integer NOT NULL, - prior_skip_slots integer NOT NULL -) diff --git a/watch/migrations/2022-01-01-000013_suboptimal_attestations/down.sql b/watch/migrations/2022-01-01-000013_suboptimal_attestations/down.sql deleted file mode 100644 index 0f32b6b4f3..0000000000 --- a/watch/migrations/2022-01-01-000013_suboptimal_attestations/down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE suboptimal_attestations diff --git a/watch/migrations/2022-01-01-000013_suboptimal_attestations/up.sql b/watch/migrations/2022-01-01-000013_suboptimal_attestations/up.sql deleted file mode 100644 index 5352afefc8..0000000000 --- a/watch/migrations/2022-01-01-000013_suboptimal_attestations/up.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE suboptimal_attestations ( - epoch_start_slot integer CHECK (epoch_start_slot % 32 = 0) REFERENCES canonical_slots(slot) ON DELETE CASCADE, - index integer NOT NULL REFERENCES validators(index) ON DELETE CASCADE, - source boolean NOT NULL, - head boolean NOT NULL, - target boolean NOT NULL, - PRIMARY KEY(epoch_start_slot, index) -) diff --git a/watch/migrations/2022-01-01-000020_capella/down.sql b/watch/migrations/2022-01-01-000020_capella/down.sql deleted file mode 100644 index 5903b351db..0000000000 --- a/watch/migrations/2022-01-01-000020_capella/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE beacon_blocks -DROP COLUMN withdrawal_count; diff --git a/watch/migrations/2022-01-01-000020_capella/up.sql b/watch/migrations/2022-01-01-000020_capella/up.sql deleted file mode 100644 index b52b4b0099..0000000000 --- a/watch/migrations/2022-01-01-000020_capella/up.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE beacon_blocks -ADD COLUMN withdrawal_count integer; - diff --git a/watch/postgres_docker_compose/compose.yml b/watch/postgres_docker_compose/compose.yml deleted file mode 100644 index eae4de4a2b..0000000000 --- a/watch/postgres_docker_compose/compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: "3" - -services: - postgres: - image: postgres:12.3-alpine - restart: always - environment: - POSTGRES_PASSWORD: postgres - POSTGRES_USER: postgres - volumes: - - postgres:/var/lib/postgresql/data - ports: - - 127.0.0.1:5432:5432 - -volumes: - postgres: diff --git a/watch/src/block_packing/database.rs b/watch/src/block_packing/database.rs deleted file mode 100644 index f7375431cb..0000000000 --- a/watch/src/block_packing/database.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::database::{ - schema::{beacon_blocks, block_packing}, - watch_types::{WatchHash, WatchSlot}, - Error, PgConn, MAX_SIZE_BATCH_INSERT, -}; - -use diesel::prelude::*; -use diesel::{Insertable, Queryable}; -use log::debug; -use serde::{Deserialize, Serialize}; -use std::time::Instant; - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = block_packing)] -pub struct WatchBlockPacking { - pub slot: WatchSlot, - pub available: i32, - pub included: i32, - pub prior_skip_slots: i32, -} - -/// Insert a batch of values into the `block_packing` table. -/// -/// On a conflict, it will do nothing, leaving the old value. -pub fn insert_batch_block_packing( - conn: &mut PgConn, - packing: Vec, -) -> Result<(), Error> { - use self::block_packing::dsl::*; - - let mut count = 0; - let timer = Instant::now(); - - for chunk in packing.chunks(MAX_SIZE_BATCH_INSERT) { - count += diesel::insert_into(block_packing) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - let time_taken = timer.elapsed(); - debug!("Block packing inserted, count: {count}, time taken: {time_taken:?}"); - Ok(()) -} - -/// Selects the row from the `block_packing` table where `slot` is minimum. -pub fn get_lowest_block_packing(conn: &mut PgConn) -> Result, Error> { - use self::block_packing::dsl::*; - let timer = Instant::now(); - - let result = block_packing - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block packing requested: lowest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `block_packing` table where `slot` is maximum. -pub fn get_highest_block_packing(conn: &mut PgConn) -> Result, Error> { - use self::block_packing::dsl::*; - let timer = Instant::now(); - - let result = block_packing - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block packing requested: highest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `block_packing` table corresponding to a given `root_query`. -pub fn get_block_packing_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root}; - use self::block_packing::dsl::*; - let timer = Instant::now(); - - let join = beacon_blocks.inner_join(block_packing); - - let result = join - .select((slot, available, included, prior_skip_slots)) - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block packing requested: {root_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `block_packing` table corresponding to a given `slot_query`. -pub fn get_block_packing_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::block_packing::dsl::*; - let timer = Instant::now(); - - let result = block_packing - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block packing requested: {slot_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from all rows of the `beacon_blocks` table which do not have a corresponding -/// row in `block_packing`. -#[allow(dead_code)] -pub fn get_unknown_block_packing( - conn: &mut PgConn, - slots_per_epoch: u64, -) -> Result>, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root, slot}; - use self::block_packing::dsl::block_packing; - - let join = beacon_blocks.left_join(block_packing); - - let result = join - .select(slot) - .filter(root.is_null()) - // Block packing cannot be retrieved for epoch 0 so we need to exclude them. - .filter(slot.ge(slots_per_epoch as i32)) - .order_by(slot.desc()) - .nullable() - .load::>(conn)?; - - Ok(result) -} diff --git a/watch/src/block_packing/mod.rs b/watch/src/block_packing/mod.rs deleted file mode 100644 index 5d74fc5979..0000000000 --- a/watch/src/block_packing/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub mod database; -pub mod server; -pub mod updater; - -use crate::database::watch_types::WatchSlot; -use crate::updater::error::Error; - -pub use database::{ - get_block_packing_by_root, get_block_packing_by_slot, get_highest_block_packing, - get_lowest_block_packing, get_unknown_block_packing, insert_batch_block_packing, - WatchBlockPacking, -}; -pub use server::block_packing_routes; - -use eth2::BeaconNodeHttpClient; -use types::Epoch; - -/// Sends a request to `lighthouse/analysis/block_packing`. -/// Formats the response into a vector of `WatchBlockPacking`. -/// -/// Will fail if `start_epoch == 0`. -pub async fn get_block_packing( - bn: &BeaconNodeHttpClient, - start_epoch: Epoch, - end_epoch: Epoch, -) -> Result, Error> { - Ok(bn - .get_lighthouse_analysis_block_packing(start_epoch, end_epoch) - .await? - .into_iter() - .map(|data| WatchBlockPacking { - slot: WatchSlot::from_slot(data.slot), - available: data.available_attestations as i32, - included: data.included_attestations as i32, - prior_skip_slots: data.prior_skip_slots as i32, - }) - .collect()) -} diff --git a/watch/src/block_packing/server.rs b/watch/src/block_packing/server.rs deleted file mode 100644 index 819144562a..0000000000 --- a/watch/src/block_packing/server.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::block_packing::database::{ - get_block_packing_by_root, get_block_packing_by_slot, WatchBlockPacking, -}; -use crate::database::{get_connection, PgPool, WatchHash, WatchSlot}; -use crate::server::Error; - -use axum::{extract::Path, routing::get, Extension, Json, Router}; -use eth2::types::BlockId; -use std::str::FromStr; - -pub async fn get_block_packing( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(get_block_packing_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(get_block_packing_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub fn block_packing_routes() -> Router { - Router::new().route("/v1/blocks/:block/packing", get(get_block_packing)) -} diff --git a/watch/src/block_packing/updater.rs b/watch/src/block_packing/updater.rs deleted file mode 100644 index 34847f6264..0000000000 --- a/watch/src/block_packing/updater.rs +++ /dev/null @@ -1,211 +0,0 @@ -use crate::database::{self, Error as DbError}; -use crate::updater::{Error, UpdateHandler}; - -use crate::block_packing::get_block_packing; - -use eth2::types::{Epoch, EthSpec}; -use log::{debug, error, warn}; - -const MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING: u64 = 50; - -impl UpdateHandler { - /// Forward fills the `block_packing` table starting from the entry with the - /// highest slot. - /// - /// It constructs a request to the `get_block_packing` API with: - /// `start_epoch` -> highest completely filled epoch + 1 (or epoch of lowest beacon block) - /// `end_epoch` -> epoch of highest beacon block - /// - /// It will resync the latest epoch if it is not fully filled. - /// That is, `if highest_filled_slot % slots_per_epoch != 31` - /// This means that if the last slot of an epoch is a skip slot, the whole epoch will be - //// resynced during the next head update. - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING`. - pub async fn fill_block_packing(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - // Get the slot of the highest entry in the `block_packing` table. - let highest_filled_slot_opt = if self.config.block_packing { - database::get_highest_block_packing(&mut conn)?.map(|packing| packing.slot) - } else { - return Err(Error::NotEnabled("block_packing".to_string())); - }; - - let mut start_epoch = if let Some(highest_filled_slot) = highest_filled_slot_opt { - if highest_filled_slot.as_slot() % self.slots_per_epoch - == self.slots_per_epoch.saturating_sub(1) - { - // The whole epoch is filled so we can begin syncing the next one. - highest_filled_slot.as_slot().epoch(self.slots_per_epoch) + 1 - } else { - // The epoch is only partially synced. Try to sync it fully. - highest_filled_slot.as_slot().epoch(self.slots_per_epoch) - } - } else { - // No entries in the `block_packing` table. Use `beacon_blocks` instead. - if let Some(lowest_beacon_block) = database::get_lowest_beacon_block(&mut conn)? { - lowest_beacon_block - .slot - .as_slot() - .epoch(self.slots_per_epoch) - } else { - // There are no blocks in the database, do not fill the `block_packing` table. - warn!("Refusing to fill block packing as there are no blocks in the database"); - return Ok(()); - } - }; - - // The `get_block_packing` API endpoint cannot accept `start_epoch == 0`. - if start_epoch == 0 { - start_epoch += 1 - } - - if let Some(highest_block_slot) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot.as_slot()) - { - let mut end_epoch = highest_block_slot.epoch(self.slots_per_epoch); - - if start_epoch > end_epoch { - debug!("Block packing is up to date with the head of the database"); - return Ok(()); - } - - // Ensure the size of the request does not exceed the maximum allowed value. - if start_epoch < end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING) { - end_epoch = start_epoch + MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING - } - - if let Some(lowest_block_slot) = - database::get_lowest_beacon_block(&mut conn)?.map(|block| block.slot.as_slot()) - { - let mut packing = get_block_packing(&self.bn, start_epoch, end_epoch).await?; - - // Since we pull a full epoch of data but are not guaranteed to have all blocks of - // that epoch available, only insert blocks with corresponding `beacon_block`s. - packing.retain(|packing| { - packing.slot.as_slot() >= lowest_block_slot - && packing.slot.as_slot() <= highest_block_slot - }); - database::insert_batch_block_packing(&mut conn, packing)?; - } else { - return Err(Error::Database(DbError::Other( - "Database did not return a lowest block when one exists".to_string(), - ))); - } - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the - // `block_packing` table. This is a critical failure. It usually means someone has - // manually tampered with the database tables and should not occur during normal - // operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } - - /// Backfill the `block_packing` table starting from the entry with the lowest slot. - /// - /// It constructs a request to the `get_block_packing` function with: - /// `start_epoch` -> epoch of lowest_beacon_block - /// `end_epoch` -> epoch of lowest filled `block_packing` - 1 (or epoch of highest beacon block) - /// - /// It will resync the lowest epoch if it is not fully filled. - /// That is, `if lowest_filled_slot % slots_per_epoch != 0` - /// This means that if the last slot of an epoch is a skip slot, the whole epoch will be - //// resynced during the next head update. - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING`. - pub async fn backfill_block_packing(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let max_block_packing_backfill = self.config.max_backfill_size_epochs; - - // Get the slot of the lowest entry in the `block_packing` table. - let lowest_filled_slot_opt = if self.config.block_packing { - database::get_lowest_block_packing(&mut conn)?.map(|packing| packing.slot) - } else { - return Err(Error::NotEnabled("block_packing".to_string())); - }; - - let end_epoch = if let Some(lowest_filled_slot) = lowest_filled_slot_opt { - if lowest_filled_slot.as_slot() % self.slots_per_epoch == 0 { - lowest_filled_slot - .as_slot() - .epoch(self.slots_per_epoch) - .saturating_sub(Epoch::new(1)) - } else { - // The epoch is only partially synced. Try to sync it fully. - lowest_filled_slot.as_slot().epoch(self.slots_per_epoch) - } - } else { - // No entries in the `block_packing` table. Use `beacon_blocks` instead. - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - highest_beacon_block.as_slot().epoch(self.slots_per_epoch) - } else { - // There are no blocks in the database, do not backfill the `block_packing` table. - warn!("Refusing to backfill block packing as there are no blocks in the database"); - return Ok(()); - } - }; - - if end_epoch <= 1 { - debug!("Block packing backfill is complete"); - return Ok(()); - } - - if let Some(lowest_block_slot) = - database::get_lowest_beacon_block(&mut conn)?.map(|block| block.slot.as_slot()) - { - let mut start_epoch = lowest_block_slot.epoch(self.slots_per_epoch); - - if start_epoch >= end_epoch { - debug!("Block packing is up to date with the base of the database"); - return Ok(()); - } - - // Ensure that the request range does not exceed `max_block_packing_backfill` or - // `MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING`. - if start_epoch < end_epoch.saturating_sub(max_block_packing_backfill) { - start_epoch = end_epoch.saturating_sub(max_block_packing_backfill) - } - if start_epoch < end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING) { - start_epoch = end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_PACKING) - } - - // The `block_packing` API cannot accept `start_epoch == 0`. - if start_epoch == 0 { - start_epoch += 1 - } - - if let Some(highest_block_slot) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot.as_slot()) - { - let mut packing = get_block_packing(&self.bn, start_epoch, end_epoch).await?; - - // Only insert blocks with corresponding `beacon_block`s. - packing.retain(|packing| { - packing.slot.as_slot() >= lowest_block_slot - && packing.slot.as_slot() <= highest_block_slot - }); - - database::insert_batch_block_packing(&mut conn, packing)?; - } else { - return Err(Error::Database(DbError::Other( - "Database did not return a lowest block when one exists".to_string(), - ))); - } - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the - // `block_packing` table. This is a critical failure. It usually means someone has - // manually tampered with the database tables and should not occur during normal - // operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } -} diff --git a/watch/src/block_rewards/database.rs b/watch/src/block_rewards/database.rs deleted file mode 100644 index a2bf49f3e4..0000000000 --- a/watch/src/block_rewards/database.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::database::{ - schema::{beacon_blocks, block_rewards}, - watch_types::{WatchHash, WatchSlot}, - Error, PgConn, MAX_SIZE_BATCH_INSERT, -}; - -use diesel::prelude::*; -use diesel::{Insertable, Queryable}; -use log::debug; -use serde::{Deserialize, Serialize}; -use std::time::Instant; - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = block_rewards)] -pub struct WatchBlockRewards { - pub slot: WatchSlot, - pub total: i32, - pub attestation_reward: i32, - pub sync_committee_reward: i32, -} - -/// Insert a batch of values into the `block_rewards` table. -/// -/// On a conflict, it will do nothing, leaving the old value. -pub fn insert_batch_block_rewards( - conn: &mut PgConn, - rewards: Vec, -) -> Result<(), Error> { - use self::block_rewards::dsl::*; - - let mut count = 0; - let timer = Instant::now(); - - for chunk in rewards.chunks(MAX_SIZE_BATCH_INSERT) { - count += diesel::insert_into(block_rewards) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - let time_taken = timer.elapsed(); - debug!("Block rewards inserted, count: {count}, time_taken: {time_taken:?}"); - Ok(()) -} - -/// Selects the row from the `block_rewards` table where `slot` is minimum. -pub fn get_lowest_block_rewards(conn: &mut PgConn) -> Result, Error> { - use self::block_rewards::dsl::*; - let timer = Instant::now(); - - let result = block_rewards - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block rewards requested: lowest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `block_rewards` table where `slot` is maximum. -pub fn get_highest_block_rewards(conn: &mut PgConn) -> Result, Error> { - use self::block_rewards::dsl::*; - let timer = Instant::now(); - - let result = block_rewards - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block rewards requested: highest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `block_rewards` table corresponding to a given `root_query`. -pub fn get_block_rewards_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root}; - use self::block_rewards::dsl::*; - let timer = Instant::now(); - - let join = beacon_blocks.inner_join(block_rewards); - - let result = join - .select((slot, total, attestation_reward, sync_committee_reward)) - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block rewards requested: {root_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `block_rewards` table corresponding to a given `slot_query`. -pub fn get_block_rewards_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::block_rewards::dsl::*; - let timer = Instant::now(); - - let result = block_rewards - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Block rewards requested: {slot_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from all rows of the `beacon_blocks` table which do not have a corresponding -/// row in `block_rewards`. -#[allow(dead_code)] -pub fn get_unknown_block_rewards(conn: &mut PgConn) -> Result>, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root, slot}; - use self::block_rewards::dsl::block_rewards; - - let join = beacon_blocks.left_join(block_rewards); - - let result = join - .select(slot) - .filter(root.is_null()) - // Block rewards cannot be retrieved for `slot == 0` so we need to exclude it. - .filter(slot.ne(0)) - .order_by(slot.desc()) - .nullable() - .load::>(conn)?; - - Ok(result) -} diff --git a/watch/src/block_rewards/mod.rs b/watch/src/block_rewards/mod.rs deleted file mode 100644 index 0dac88ea58..0000000000 --- a/watch/src/block_rewards/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub mod database; -mod server; -mod updater; - -use crate::database::watch_types::WatchSlot; -use crate::updater::error::Error; - -pub use database::{ - get_block_rewards_by_root, get_block_rewards_by_slot, get_highest_block_rewards, - get_lowest_block_rewards, get_unknown_block_rewards, insert_batch_block_rewards, - WatchBlockRewards, -}; -pub use server::block_rewards_routes; - -use eth2::BeaconNodeHttpClient; -use types::Slot; - -/// Sends a request to `lighthouse/analysis/block_rewards`. -/// Formats the response into a vector of `WatchBlockRewards`. -/// -/// Will fail if `start_slot == 0`. -pub async fn get_block_rewards( - bn: &BeaconNodeHttpClient, - start_slot: Slot, - end_slot: Slot, -) -> Result, Error> { - Ok(bn - .get_lighthouse_analysis_block_rewards(start_slot, end_slot) - .await? - .into_iter() - .map(|data| WatchBlockRewards { - slot: WatchSlot::from_slot(data.meta.slot), - total: data.total as i32, - attestation_reward: data.attestation_rewards.total as i32, - sync_committee_reward: data.sync_committee_rewards as i32, - }) - .collect()) -} diff --git a/watch/src/block_rewards/server.rs b/watch/src/block_rewards/server.rs deleted file mode 100644 index 480346e25b..0000000000 --- a/watch/src/block_rewards/server.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::block_rewards::database::{ - get_block_rewards_by_root, get_block_rewards_by_slot, WatchBlockRewards, -}; -use crate::database::{get_connection, PgPool, WatchHash, WatchSlot}; -use crate::server::Error; - -use axum::{extract::Path, routing::get, Extension, Json, Router}; -use eth2::types::BlockId; -use std::str::FromStr; - -pub async fn get_block_rewards( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(get_block_rewards_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(get_block_rewards_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub fn block_rewards_routes() -> Router { - Router::new().route("/v1/blocks/:block/rewards", get(get_block_rewards)) -} diff --git a/watch/src/block_rewards/updater.rs b/watch/src/block_rewards/updater.rs deleted file mode 100644 index e2893ad0fe..0000000000 --- a/watch/src/block_rewards/updater.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crate::database::{self, Error as DbError}; -use crate::updater::{Error, UpdateHandler}; - -use crate::block_rewards::get_block_rewards; - -use eth2::types::EthSpec; -use log::{debug, error, warn}; - -const MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS: u64 = 1600; - -impl UpdateHandler { - /// Forward fills the `block_rewards` table starting from the entry with the - /// highest slot. - /// - /// It constructs a request to the `get_block_rewards` API with: - /// `start_slot` -> highest filled `block_rewards` + 1 (or lowest beacon block) - /// `end_slot` -> highest beacon block - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS`. - pub async fn fill_block_rewards(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - // Get the slot of the highest entry in the `block_rewards` table. - let highest_filled_slot_opt = if self.config.block_rewards { - database::get_highest_block_rewards(&mut conn)?.map(|reward| reward.slot) - } else { - return Err(Error::NotEnabled("block_rewards".to_string())); - }; - - let mut start_slot = if let Some(highest_filled_slot) = highest_filled_slot_opt { - highest_filled_slot.as_slot() + 1 - } else { - // No entries in the `block_rewards` table. Use `beacon_blocks` instead. - if let Some(lowest_beacon_block) = - database::get_lowest_beacon_block(&mut conn)?.map(|block| block.slot) - { - lowest_beacon_block.as_slot() - } else { - // There are no blocks in the database, do not fill the `block_rewards` table. - warn!("Refusing to fill block rewards as there are no blocks in the database"); - return Ok(()); - } - }; - - // The `block_rewards` API cannot accept `start_slot == 0`. - if start_slot == 0 { - start_slot += 1; - } - - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - let mut end_slot = highest_beacon_block.as_slot(); - - if start_slot > end_slot { - debug!("Block rewards are up to date with the head of the database"); - return Ok(()); - } - - // Ensure the size of the request does not exceed the maximum allowed value. - if start_slot < end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS) { - end_slot = start_slot + MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS - } - - let rewards = get_block_rewards(&self.bn, start_slot, end_slot).await?; - database::insert_batch_block_rewards(&mut conn, rewards)?; - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the - // `block_rewards` table. This is a critical failure. It usually means someone has - // manually tampered with the database tables and should not occur during normal - // operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } - - /// Backfill the `block_rewards` tables starting from the entry with the - /// lowest slot. - /// - /// It constructs a request to the `get_block_rewards` API with: - /// `start_slot` -> lowest_beacon_block - /// `end_slot` -> lowest filled `block_rewards` - 1 (or highest beacon block) - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS`. - pub async fn backfill_block_rewards(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let max_block_reward_backfill = self.config.max_backfill_size_epochs * self.slots_per_epoch; - - // Get the slot of the lowest entry in the `block_rewards` table. - let lowest_filled_slot_opt = if self.config.block_rewards { - database::get_lowest_block_rewards(&mut conn)?.map(|reward| reward.slot) - } else { - return Err(Error::NotEnabled("block_rewards".to_string())); - }; - - let end_slot = if let Some(lowest_filled_slot) = lowest_filled_slot_opt { - lowest_filled_slot.as_slot().saturating_sub(1_u64) - } else { - // No entries in the `block_rewards` table. Use `beacon_blocks` instead. - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - highest_beacon_block.as_slot() - } else { - // There are no blocks in the database, do not backfill the `block_rewards` table. - warn!("Refusing to backfill block rewards as there are no blocks in the database"); - return Ok(()); - } - }; - - if end_slot <= 1 { - debug!("Block rewards backfill is complete"); - return Ok(()); - } - - if let Some(lowest_block_slot) = database::get_lowest_beacon_block(&mut conn)? { - let mut start_slot = lowest_block_slot.slot.as_slot(); - - if start_slot >= end_slot { - debug!("Block rewards are up to date with the base of the database"); - return Ok(()); - } - - // Ensure that the request range does not exceed `max_block_reward_backfill` or - // `MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS`. - if start_slot < end_slot.saturating_sub(max_block_reward_backfill) { - start_slot = end_slot.saturating_sub(max_block_reward_backfill) - } - - if start_slot < end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS) { - start_slot = end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCK_REWARDS) - } - - // The `block_rewards` API cannot accept `start_slot == 0`. - if start_slot == 0 { - start_slot += 1 - } - - let rewards = get_block_rewards(&self.bn, start_slot, end_slot).await?; - - if self.config.block_rewards { - database::insert_batch_block_rewards(&mut conn, rewards)?; - } - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the - // `block_rewards` table. This is a critical failure. It usually means someone has - // manually tampered with the database tables and should not occur during normal - // operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } -} diff --git a/watch/src/blockprint/config.rs b/watch/src/blockprint/config.rs deleted file mode 100644 index 721fa7cb19..0000000000 --- a/watch/src/blockprint/config.rs +++ /dev/null @@ -1,40 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub const fn enabled() -> bool { - false -} - -pub const fn url() -> Option { - None -} - -pub const fn username() -> Option { - None -} - -pub const fn password() -> Option { - None -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - #[serde(default = "enabled")] - pub enabled: bool, - #[serde(default = "url")] - pub url: Option, - #[serde(default = "username")] - pub username: Option, - #[serde(default = "password")] - pub password: Option, -} - -impl Default for Config { - fn default() -> Self { - Config { - enabled: enabled(), - url: url(), - username: username(), - password: password(), - } - } -} diff --git a/watch/src/blockprint/database.rs b/watch/src/blockprint/database.rs deleted file mode 100644 index f0bc3f8ac8..0000000000 --- a/watch/src/blockprint/database.rs +++ /dev/null @@ -1,225 +0,0 @@ -use crate::database::{ - self, - schema::{beacon_blocks, blockprint}, - watch_types::{WatchHash, WatchSlot}, - Error, PgConn, MAX_SIZE_BATCH_INSERT, -}; - -use diesel::prelude::*; -use diesel::sql_types::{Integer, Text}; -use diesel::{Insertable, Queryable}; -use log::debug; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::time::Instant; - -type WatchConsensusClient = String; -pub fn list_consensus_clients() -> Vec { - vec![ - "Lighthouse".to_string(), - "Lodestar".to_string(), - "Nimbus".to_string(), - "Prysm".to_string(), - "Teku".to_string(), - "Unknown".to_string(), - ] -} - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = blockprint)] -pub struct WatchBlockprint { - pub slot: WatchSlot, - pub best_guess: WatchConsensusClient, -} - -#[derive(Debug, QueryableByName, diesel::FromSqlRow)] -#[allow(dead_code)] -pub struct WatchValidatorBlockprint { - #[diesel(sql_type = Integer)] - pub proposer_index: i32, - #[diesel(sql_type = Text)] - pub best_guess: WatchConsensusClient, - #[diesel(sql_type = Integer)] - pub slot: WatchSlot, -} - -/// Insert a batch of values into the `blockprint` table. -/// -/// On a conflict, it will do nothing, leaving the old value. -pub fn insert_batch_blockprint( - conn: &mut PgConn, - prints: Vec, -) -> Result<(), Error> { - use self::blockprint::dsl::*; - - let mut count = 0; - let timer = Instant::now(); - - for chunk in prints.chunks(MAX_SIZE_BATCH_INSERT) { - count += diesel::insert_into(blockprint) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - let time_taken = timer.elapsed(); - debug!("Blockprint inserted, count: {count}, time_taken: {time_taken:?}"); - Ok(()) -} - -/// Selects the row from the `blockprint` table where `slot` is minimum. -pub fn get_lowest_blockprint(conn: &mut PgConn) -> Result, Error> { - use self::blockprint::dsl::*; - let timer = Instant::now(); - - let result = blockprint - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Blockprint requested: lowest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `blockprint` table where `slot` is maximum. -pub fn get_highest_blockprint(conn: &mut PgConn) -> Result, Error> { - use self::blockprint::dsl::*; - let timer = Instant::now(); - - let result = blockprint - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Blockprint requested: highest, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `blockprint` table corresponding to a given `root_query`. -pub fn get_blockprint_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root}; - use self::blockprint::dsl::*; - let timer = Instant::now(); - - let join = beacon_blocks.inner_join(blockprint); - - let result = join - .select((slot, best_guess)) - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Blockprint requested: {root_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `blockprint` table corresponding to a given `slot_query`. -pub fn get_blockprint_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::blockprint::dsl::*; - let timer = Instant::now(); - - let result = blockprint - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Blockprint requested: {slot_query}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from all rows of the `beacon_blocks` table which do not have a corresponding -/// row in `blockprint`. -#[allow(dead_code)] -pub fn get_unknown_blockprint(conn: &mut PgConn) -> Result>, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root, slot}; - use self::blockprint::dsl::blockprint; - - let join = beacon_blocks.left_join(blockprint); - - let result = join - .select(slot) - .filter(root.is_null()) - .order_by(slot.desc()) - .nullable() - .load::>(conn)?; - - Ok(result) -} - -/// Constructs a HashMap of `index` -> `best_guess` for each validator's latest proposal at or before -/// `target_slot`. -/// Inserts `"Unknown" if no prior proposals exist. -pub fn construct_validator_blockprints_at_slot( - conn: &mut PgConn, - target_slot: WatchSlot, - slots_per_epoch: u64, -) -> Result, Error> { - use self::blockprint::dsl::{blockprint, slot}; - - let total_validators = - database::count_validators_activated_before_slot(conn, target_slot, slots_per_epoch)? - as usize; - - let mut blockprint_map = HashMap::with_capacity(total_validators); - - let latest_proposals = - database::get_all_validators_latest_proposer_info_at_slot(conn, target_slot)?; - - let latest_proposal_slots: Vec = latest_proposals.clone().into_keys().collect(); - - let result = blockprint - .filter(slot.eq_any(latest_proposal_slots)) - .load::(conn)?; - - // Insert the validators which have available blockprints. - for print in result { - if let Some(proposer) = latest_proposals.get(&print.slot) { - blockprint_map.insert(*proposer, print.best_guess); - } - } - - // Insert the rest of the unknown validators. - for validator_index in 0..total_validators { - blockprint_map - .entry(validator_index as i32) - .or_insert_with(|| "Unknown".to_string()); - } - - Ok(blockprint_map) -} - -/// Counts the number of occurances of each `client` present in the `validators` table at or before some -/// `target_slot`. -pub fn get_validators_clients_at_slot( - conn: &mut PgConn, - target_slot: WatchSlot, - slots_per_epoch: u64, -) -> Result, Error> { - let mut client_map: HashMap = HashMap::new(); - - // This includes all validators which were activated at or before `target_slot`. - let validator_blockprints = - construct_validator_blockprints_at_slot(conn, target_slot, slots_per_epoch)?; - - for client in list_consensus_clients() { - let count = validator_blockprints - .iter() - .filter(|(_, v)| (*v).clone() == client) - .count(); - client_map.insert(client, count); - } - - Ok(client_map) -} diff --git a/watch/src/blockprint/mod.rs b/watch/src/blockprint/mod.rs deleted file mode 100644 index 319090c656..0000000000 --- a/watch/src/blockprint/mod.rs +++ /dev/null @@ -1,150 +0,0 @@ -pub mod database; -pub mod server; -pub mod updater; - -mod config; - -use crate::database::WatchSlot; - -use eth2::SensitiveUrl; -use reqwest::{Client, Response, Url}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::time::Duration; -use types::Slot; - -pub use config::Config; -pub use database::{ - get_blockprint_by_root, get_blockprint_by_slot, get_highest_blockprint, get_lowest_blockprint, - get_unknown_blockprint, get_validators_clients_at_slot, insert_batch_blockprint, - WatchBlockprint, -}; -pub use server::blockprint_routes; - -const TIMEOUT: Duration = Duration::from_secs(50); - -#[derive(Debug)] -#[allow(dead_code)] -pub enum Error { - Reqwest(reqwest::Error), - Url(url::ParseError), - BlockprintNotSynced, - Other(String), -} - -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Error::Reqwest(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Error::Url(e) - } -} - -pub struct WatchBlockprintClient { - pub client: Client, - pub server: SensitiveUrl, - pub username: Option, - pub password: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct BlockprintSyncingResponse { - pub greatest_block_slot: Slot, - pub synced: bool, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct BlockprintResponse { - pub proposer_index: i32, - pub slot: Slot, - pub best_guess_single: String, -} - -impl WatchBlockprintClient { - async fn get(&self, url: Url) -> Result { - let mut builder = self.client.get(url).timeout(TIMEOUT); - if let Some(username) = &self.username { - builder = builder.basic_auth(username, self.password.as_ref()); - } - let response = builder.send().await.map_err(Error::Reqwest)?; - - if !response.status().is_success() { - return Err(Error::Other(response.text().await?)); - } - - Ok(response) - } - - // Returns the `greatest_block_slot` as reported by the Blockprint server. - // Will error if the Blockprint server is not synced. - #[allow(dead_code)] - pub async fn ensure_synced(&self) -> Result { - let url = self.server.full.join("sync/")?.join("status")?; - - let response = self.get(url).await?; - - let result = response.json::().await?; - if !result.synced { - return Err(Error::BlockprintNotSynced); - } - - Ok(result.greatest_block_slot) - } - - // Pulls the latest blockprint for all validators. - #[allow(dead_code)] - pub async fn blockprint_all_validators( - &self, - highest_validator: i32, - ) -> Result, Error> { - let url = self - .server - .full - .join("validator/")? - .join("blocks/")? - .join("latest")?; - - let response = self.get(url).await?; - - let mut result = response.json::>().await?; - result.retain(|print| print.proposer_index <= highest_validator); - - let mut map: HashMap = HashMap::with_capacity(result.len()); - for print in result { - map.insert(print.proposer_index, print.best_guess_single); - } - - Ok(map) - } - - // Construct a request to the Blockprint server for a range of slots between `start_slot` and - // `end_slot`. - pub async fn get_blockprint( - &self, - start_slot: Slot, - end_slot: Slot, - ) -> Result, Error> { - let url = self - .server - .full - .join("blocks/")? - .join(&format!("{start_slot}/{end_slot}"))?; - - let response = self.get(url).await?; - - let result = response - .json::>() - .await? - .iter() - .map(|response| WatchBlockprint { - slot: WatchSlot::from_slot(response.slot), - best_guess: response.best_guess_single.clone(), - }) - .collect(); - Ok(result) - } -} diff --git a/watch/src/blockprint/server.rs b/watch/src/blockprint/server.rs deleted file mode 100644 index 488af15717..0000000000 --- a/watch/src/blockprint/server.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::blockprint::database::{ - get_blockprint_by_root, get_blockprint_by_slot, WatchBlockprint, -}; -use crate::database::{get_connection, PgPool, WatchHash, WatchSlot}; -use crate::server::Error; - -use axum::{extract::Path, routing::get, Extension, Json, Router}; -use eth2::types::BlockId; -use std::str::FromStr; - -pub async fn get_blockprint( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(get_blockprint_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(get_blockprint_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub fn blockprint_routes() -> Router { - Router::new().route("/v1/blocks/:block/blockprint", get(get_blockprint)) -} diff --git a/watch/src/blockprint/updater.rs b/watch/src/blockprint/updater.rs deleted file mode 100644 index 7ec56dd9c8..0000000000 --- a/watch/src/blockprint/updater.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::database::{self, Error as DbError}; -use crate::updater::{Error, UpdateHandler}; - -use eth2::types::EthSpec; -use log::{debug, error, warn}; - -const MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT: u64 = 1600; - -impl UpdateHandler { - /// Forward fills the `blockprint` table starting from the entry with the - /// highest slot. - /// - /// It constructs a request to the `get_blockprint` API with: - /// `start_slot` -> highest filled `blockprint` + 1 (or lowest beacon block) - /// `end_slot` -> highest beacon block - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT`. - pub async fn fill_blockprint(&mut self) -> Result<(), Error> { - // Ensure blockprint in enabled. - if let Some(blockprint_client) = &self.blockprint { - let mut conn = database::get_connection(&self.pool)?; - - // Get the slot of the highest entry in the `blockprint` table. - let mut start_slot = if let Some(highest_filled_slot) = - database::get_highest_blockprint(&mut conn)?.map(|print| print.slot) - { - highest_filled_slot.as_slot() + 1 - } else { - // No entries in the `blockprint` table. Use `beacon_blocks` instead. - if let Some(lowest_beacon_block) = - database::get_lowest_beacon_block(&mut conn)?.map(|block| block.slot) - { - lowest_beacon_block.as_slot() - } else { - // There are no blocks in the database, do not fill the `blockprint` table. - warn!("Refusing to fill blockprint as there are no blocks in the database"); - return Ok(()); - } - }; - - // The `blockprint` API cannot accept `start_slot == 0`. - if start_slot == 0 { - start_slot += 1; - } - - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - let mut end_slot = highest_beacon_block.as_slot(); - - if start_slot > end_slot { - debug!("Blockprint is up to date with the head of the database"); - return Ok(()); - } - - // Ensure the size of the request does not exceed the maximum allowed value. - if start_slot < end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT) { - end_slot = start_slot + MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT - } - - let mut prints = blockprint_client - .get_blockprint(start_slot, end_slot) - .await?; - - // Ensure the prints returned from blockprint are for slots which exist in the - // `beacon_blocks` table. - prints.retain(|print| { - database::get_beacon_block_by_slot(&mut conn, print.slot) - .ok() - .flatten() - .is_some() - }); - - database::insert_batch_blockprint(&mut conn, prints)?; - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in either - // `blockprint` table. This is a critical failure. It usually means - // someone has manually tampered with the database tables and should not occur during - // normal operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - } - - Ok(()) - } - - /// Backfill the `blockprint` table starting from the entry with the lowest slot. - /// - /// It constructs a request to the `get_blockprint` API with: - /// `start_slot` -> lowest_beacon_block - /// `end_slot` -> lowest filled `blockprint` - 1 (or highest beacon block) - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT`. - pub async fn backfill_blockprint(&mut self) -> Result<(), Error> { - // Ensure blockprint in enabled. - if let Some(blockprint_client) = &self.blockprint { - let mut conn = database::get_connection(&self.pool)?; - let max_blockprint_backfill = - self.config.max_backfill_size_epochs * self.slots_per_epoch; - - // Get the slot of the lowest entry in the `blockprint` table. - let end_slot = if let Some(lowest_filled_slot) = - database::get_lowest_blockprint(&mut conn)?.map(|print| print.slot) - { - lowest_filled_slot.as_slot().saturating_sub(1_u64) - } else { - // No entries in the `blockprint` table. Use `beacon_blocks` instead. - if let Some(highest_beacon_block) = - database::get_highest_beacon_block(&mut conn)?.map(|block| block.slot) - { - highest_beacon_block.as_slot() - } else { - // There are no blocks in the database, do not backfill the `blockprint` table. - warn!("Refusing to backfill blockprint as there are no blocks in the database"); - return Ok(()); - } - }; - - if end_slot <= 1 { - debug!("Blockprint backfill is complete"); - return Ok(()); - } - - if let Some(lowest_block_slot) = database::get_lowest_beacon_block(&mut conn)? { - let mut start_slot = lowest_block_slot.slot.as_slot(); - - if start_slot >= end_slot { - debug!("Blockprint are up to date with the base of the database"); - return Ok(()); - } - - // Ensure that the request range does not exceed `max_blockprint_backfill` or - // `MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT`. - if start_slot < end_slot.saturating_sub(max_blockprint_backfill) { - start_slot = end_slot.saturating_sub(max_blockprint_backfill) - } - - if start_slot < end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT) { - start_slot = end_slot.saturating_sub(MAX_SIZE_SINGLE_REQUEST_BLOCKPRINT) - } - - // The `blockprint` API cannot accept `start_slot == 0`. - if start_slot == 0 { - start_slot += 1 - } - - let mut prints = blockprint_client - .get_blockprint(start_slot, end_slot) - .await?; - - // Ensure the prints returned from blockprint are for slots which exist in the - // `beacon_blocks` table. - prints.retain(|print| { - database::get_beacon_block_by_slot(&mut conn, print.slot) - .ok() - .flatten() - .is_some() - }); - - database::insert_batch_blockprint(&mut conn, prints)?; - } else { - // There are no blocks in the `beacon_blocks` database, but there are entries in the `blockprint` - // table. This is a critical failure. It usually means someone has manually tampered with the - // database tables and should not occur during normal operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - } - Ok(()) - } -} diff --git a/watch/src/cli.rs b/watch/src/cli.rs deleted file mode 100644 index b7179efe5d..0000000000 --- a/watch/src/cli.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{config::Config, logger, server, updater}; -use clap::{Arg, ArgAction, Command}; -use clap_utils::get_color_style; - -pub const SERVE: &str = "serve"; -pub const RUN_UPDATER: &str = "run-updater"; -pub const CONFIG: &str = "config"; - -fn run_updater() -> Command { - Command::new(RUN_UPDATER).styles(get_color_style()) -} - -fn serve() -> Command { - Command::new(SERVE).styles(get_color_style()) -} - -pub fn app() -> Command { - Command::new("beacon_watch_daemon") - .author("Sigma Prime ") - .styles(get_color_style()) - .arg( - Arg::new(CONFIG) - .long(CONFIG) - .value_name("PATH_TO_CONFIG") - .help("Path to configuration file") - .action(ArgAction::Set) - .global(true), - ) - .subcommand(run_updater()) - .subcommand(serve()) -} - -pub async fn run() -> Result<(), String> { - let matches = app().get_matches(); - - let config = match matches.get_one::(CONFIG) { - Some(path) => Config::load_from_file(path.to_string())?, - None => Config::default(), - }; - - logger::init_logger(&config.log_level); - - match matches.subcommand() { - Some((RUN_UPDATER, _)) => updater::run_updater(config) - .await - .map_err(|e| format!("Failure: {:?}", e)), - Some((SERVE, _)) => server::serve(config) - .await - .map_err(|e| format!("Failure: {:?}", e)), - _ => Err("Unsupported subcommand. See --help".into()), - } -} diff --git a/watch/src/client.rs b/watch/src/client.rs deleted file mode 100644 index 43aaccde34..0000000000 --- a/watch/src/client.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::block_packing::WatchBlockPacking; -use crate::block_rewards::WatchBlockRewards; -use crate::database::models::{ - WatchBeaconBlock, WatchCanonicalSlot, WatchProposerInfo, WatchValidator, -}; -use crate::suboptimal_attestations::WatchAttestation; - -use eth2::types::BlockId; -use reqwest::Client; -use serde::de::DeserializeOwned; -use types::Hash256; -use url::Url; - -#[derive(Debug)] -pub enum Error { - Reqwest(reqwest::Error), - Url(url::ParseError), -} - -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Error::Reqwest(e) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Error::Url(e) - } -} - -pub struct WatchHttpClient { - pub client: Client, - pub server: Url, -} - -impl WatchHttpClient { - async fn get_opt(&self, url: Url) -> Result, Error> { - let response = self.client.get(url).send().await?; - - if response.status() == 404 { - Ok(None) - } else { - response - .error_for_status()? - .json() - .await - .map_err(Into::into) - } - } - - pub async fn get_beacon_blocks( - &self, - block_id: BlockId, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&block_id.to_string())?; - - self.get_opt(url).await - } - - pub async fn get_lowest_canonical_slot(&self) -> Result, Error> { - let url = self.server.join("v1/")?.join("slots/")?.join("lowest")?; - - self.get_opt(url).await - } - - pub async fn get_highest_canonical_slot(&self) -> Result, Error> { - let url = self.server.join("v1/")?.join("slots/")?.join("highest")?; - - self.get_opt(url).await - } - - pub async fn get_lowest_beacon_block(&self) -> Result, Error> { - let url = self.server.join("v1/")?.join("blocks/")?.join("lowest")?; - - self.get_opt(url).await - } - - pub async fn get_highest_beacon_block(&self) -> Result, Error> { - let url = self.server.join("v1/")?.join("blocks/")?.join("highest")?; - - self.get_opt(url).await - } - - pub async fn get_next_beacon_block( - &self, - parent: Hash256, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&format!("{parent:?}/"))? - .join("next")?; - - self.get_opt(url).await - } - - pub async fn get_validator_by_index( - &self, - index: i32, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("validators/")? - .join(&format!("{index}"))?; - - self.get_opt(url).await - } - - pub async fn get_proposer_info( - &self, - block_id: BlockId, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&format!("{block_id}/"))? - .join("proposer")?; - - self.get_opt(url).await - } - - pub async fn get_block_reward( - &self, - block_id: BlockId, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&format!("{block_id}/"))? - .join("rewards")?; - - self.get_opt(url).await - } - - pub async fn get_block_packing( - &self, - block_id: BlockId, - ) -> Result, Error> { - let url = self - .server - .join("v1/")? - .join("blocks/")? - .join(&format!("{block_id}/"))? - .join("packing")?; - - self.get_opt(url).await - } - - pub async fn get_all_validators(&self) -> Result>, Error> { - let url = self.server.join("v1/")?.join("validators/")?.join("all")?; - - self.get_opt(url).await - } - - pub async fn get_attestations( - &self, - epoch: i32, - ) -> Result>, Error> { - let url = self - .server - .join("v1/")? - .join("validators/")? - .join("all/")? - .join("attestation/")? - .join(&format!("{epoch}"))?; - - self.get_opt(url).await - } -} diff --git a/watch/src/config.rs b/watch/src/config.rs deleted file mode 100644 index 4e61f9df9c..0000000000 --- a/watch/src/config.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::blockprint::Config as BlockprintConfig; -use crate::database::Config as DatabaseConfig; -use crate::server::Config as ServerConfig; -use crate::updater::Config as UpdaterConfig; - -use serde::{Deserialize, Serialize}; -use std::fs::File; - -pub const LOG_LEVEL: &str = "debug"; - -fn log_level() -> String { - LOG_LEVEL.to_string() -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - #[serde(default)] - pub blockprint: BlockprintConfig, - #[serde(default)] - pub database: DatabaseConfig, - #[serde(default)] - pub server: ServerConfig, - #[serde(default)] - pub updater: UpdaterConfig, - /// The minimum severity for log messages. - #[serde(default = "log_level")] - pub log_level: String, -} - -impl Default for Config { - fn default() -> Self { - Self { - blockprint: BlockprintConfig::default(), - database: DatabaseConfig::default(), - server: ServerConfig::default(), - updater: UpdaterConfig::default(), - log_level: log_level(), - } - } -} - -impl Config { - pub fn load_from_file(path_to_file: String) -> Result { - let file = - File::open(path_to_file).map_err(|e| format!("Error reading config file: {:?}", e))?; - let config: Config = serde_yaml::from_reader(file) - .map_err(|e| format!("Error parsing config file: {:?}", e))?; - Ok(config) - } -} diff --git a/watch/src/database/compat.rs b/watch/src/database/compat.rs deleted file mode 100644 index e3e9e0df6f..0000000000 --- a/watch/src/database/compat.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Implementations of PostgreSQL compatibility traits. -use crate::database::watch_types::{WatchHash, WatchPK, WatchSlot}; -use diesel::deserialize::{self, FromSql}; -use diesel::pg::{Pg, PgValue}; -use diesel::serialize::{self, Output, ToSql}; -use diesel::sql_types::{Binary, Integer}; - -macro_rules! impl_to_from_sql_int { - ($type:ty) => { - impl ToSql for $type - where - i32: ToSql, - { - fn to_sql<'a>(&'a self, out: &mut Output<'a, '_, Pg>) -> serialize::Result { - let v = i32::try_from(self.as_u64()).map_err(|e| Box::new(e))?; - >::to_sql(&v, &mut out.reborrow()) - } - } - - impl FromSql for $type { - fn from_sql(bytes: PgValue<'_>) -> deserialize::Result { - Ok(Self::new(i32::from_sql(bytes)? as u64)) - } - } - }; -} - -macro_rules! impl_to_from_sql_binary { - ($type:ty) => { - impl ToSql for $type { - fn to_sql<'a>(&'a self, out: &mut Output<'a, '_, Pg>) -> serialize::Result { - let b = self.as_bytes(); - <&[u8] as ToSql>::to_sql(&b, &mut out.reborrow()) - } - } - - impl FromSql for $type { - fn from_sql(bytes: PgValue<'_>) -> deserialize::Result { - Self::from_bytes(bytes.as_bytes()).map_err(|e| e.to_string().into()) - } - } - }; -} - -impl_to_from_sql_int!(WatchSlot); -impl_to_from_sql_binary!(WatchHash); -impl_to_from_sql_binary!(WatchPK); diff --git a/watch/src/database/config.rs b/watch/src/database/config.rs deleted file mode 100644 index dc0c70832f..0000000000 --- a/watch/src/database/config.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub const USER: &str = "postgres"; -pub const PASSWORD: &str = "postgres"; -pub const DBNAME: &str = "dev"; -pub const DEFAULT_DBNAME: &str = "postgres"; -pub const HOST: &str = "localhost"; -pub const fn port() -> u16 { - 5432 -} -pub const fn connect_timeout_millis() -> u64 { - 2_000 // 2s -} - -fn user() -> String { - USER.to_string() -} - -fn password() -> String { - PASSWORD.to_string() -} - -fn dbname() -> String { - DBNAME.to_string() -} - -fn default_dbname() -> String { - DEFAULT_DBNAME.to_string() -} - -fn host() -> String { - HOST.to_string() -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - #[serde(default = "user")] - pub user: String, - #[serde(default = "password")] - pub password: String, - #[serde(default = "dbname")] - pub dbname: String, - #[serde(default = "default_dbname")] - pub default_dbname: String, - #[serde(default = "host")] - pub host: String, - #[serde(default = "port")] - pub port: u16, - #[serde(default = "connect_timeout_millis")] - pub connect_timeout_millis: u64, -} - -impl Default for Config { - fn default() -> Self { - Self { - user: user(), - password: password(), - dbname: dbname(), - default_dbname: default_dbname(), - host: host(), - port: port(), - connect_timeout_millis: connect_timeout_millis(), - } - } -} - -impl Config { - pub fn build_database_url(&self) -> String { - format!( - "postgres://{}:{}@{}:{}/{}", - self.user, self.password, self.host, self.port, self.dbname - ) - } -} diff --git a/watch/src/database/error.rs b/watch/src/database/error.rs deleted file mode 100644 index 8c5088fa13..0000000000 --- a/watch/src/database/error.rs +++ /dev/null @@ -1,55 +0,0 @@ -use bls::Error as BlsError; -use diesel::result::{ConnectionError, Error as PgError}; -use eth2::SensitiveError; -use r2d2::Error as PoolError; -use std::fmt; -use types::BeaconStateError; - -#[derive(Debug)] -pub enum Error { - BeaconState(BeaconStateError), - Database(PgError), - DatabaseCorrupted, - InvalidSig(BlsError), - PostgresConnection(ConnectionError), - Pool(PoolError), - SensitiveUrl(SensitiveError), - InvalidRoot, - Other(String), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl From for Error { - fn from(e: BeaconStateError) -> Self { - Error::BeaconState(e) - } -} - -impl From for Error { - fn from(e: ConnectionError) -> Self { - Error::PostgresConnection(e) - } -} - -impl From for Error { - fn from(e: PgError) -> Self { - Error::Database(e) - } -} - -impl From for Error { - fn from(e: PoolError) -> Self { - Error::Pool(e) - } -} - -impl From for Error { - fn from(e: BlsError) -> Self { - Error::InvalidSig(e) - } -} diff --git a/watch/src/database/mod.rs b/watch/src/database/mod.rs deleted file mode 100644 index 7193b0744a..0000000000 --- a/watch/src/database/mod.rs +++ /dev/null @@ -1,786 +0,0 @@ -mod config; -mod error; - -pub mod compat; -pub mod models; -pub mod schema; -pub mod utils; -pub mod watch_types; - -use self::schema::{ - active_config, beacon_blocks, canonical_slots, proposer_info, suboptimal_attestations, - validators, -}; - -use diesel::dsl::max; -use diesel::prelude::*; -use diesel::r2d2::{Builder, ConnectionManager, Pool, PooledConnection}; -use diesel::upsert::excluded; -use log::{debug, info}; -use std::collections::HashMap; -use std::time::Instant; -use types::{EthSpec, SignedBeaconBlock}; - -pub use self::error::Error; -pub use self::models::{WatchBeaconBlock, WatchCanonicalSlot, WatchProposerInfo, WatchValidator}; -pub use self::watch_types::{WatchHash, WatchPK, WatchSlot}; - -// Clippy has false positives on these re-exports from Rust 1.75.0-beta.1. -#[allow(unused_imports)] -pub use crate::block_rewards::{ - get_block_rewards_by_root, get_block_rewards_by_slot, get_highest_block_rewards, - get_lowest_block_rewards, get_unknown_block_rewards, insert_batch_block_rewards, - WatchBlockRewards, -}; - -#[allow(unused_imports)] -pub use crate::block_packing::{ - get_block_packing_by_root, get_block_packing_by_slot, get_highest_block_packing, - get_lowest_block_packing, get_unknown_block_packing, insert_batch_block_packing, - WatchBlockPacking, -}; - -#[allow(unused_imports)] -pub use crate::suboptimal_attestations::{ - get_all_suboptimal_attestations_for_epoch, get_attestation_by_index, get_attestation_by_pubkey, - get_highest_attestation, get_lowest_attestation, insert_batch_suboptimal_attestations, - WatchAttestation, WatchSuboptimalAttestation, -}; - -#[allow(unused_imports)] -pub use crate::blockprint::{ - get_blockprint_by_root, get_blockprint_by_slot, get_highest_blockprint, get_lowest_blockprint, - get_unknown_blockprint, get_validators_clients_at_slot, insert_batch_blockprint, - WatchBlockprint, -}; - -pub use config::Config; - -/// Batch inserts cannot exceed a certain size. -/// See https://github.com/diesel-rs/diesel/issues/2414. -/// For some reason, this seems to translate to 65535 / 5 (13107) records. -pub const MAX_SIZE_BATCH_INSERT: usize = 13107; - -pub type PgPool = Pool>; -pub type PgConn = PooledConnection>; - -/// Connect to a Postgresql database and build a connection pool. -pub fn build_connection_pool(config: &Config) -> Result { - let database_url = config.clone().build_database_url(); - info!("Building connection pool at: {database_url}"); - let pg = ConnectionManager::::new(&database_url); - Builder::new().build(pg).map_err(Error::Pool) -} - -/// Retrieve an idle connection from the pool. -pub fn get_connection(pool: &PgPool) -> Result { - pool.get().map_err(Error::Pool) -} - -/// Insert the active config into the database. This is used to check if the connected beacon node -/// is compatible with the database. These values will not change (except -/// `current_blockprint_checkpoint`). -pub fn insert_active_config( - conn: &mut PgConn, - new_config_name: String, - new_slots_per_epoch: u64, -) -> Result<(), Error> { - use self::active_config::dsl::*; - - diesel::insert_into(active_config) - .values(&vec![( - id.eq(1), - config_name.eq(new_config_name), - slots_per_epoch.eq(new_slots_per_epoch as i32), - )]) - .on_conflict_do_nothing() - .execute(conn)?; - - Ok(()) -} - -/// Get the active config from the database. -pub fn get_active_config(conn: &mut PgConn) -> Result, Error> { - use self::active_config::dsl::*; - Ok(active_config - .select((config_name, slots_per_epoch)) - .filter(id.eq(1)) - .first::<(String, i32)>(conn) - .optional()?) -} - -/* - * INSERT statements - */ - -/// Inserts a single row into the `canonical_slots` table. -/// If `new_slot.beacon_block` is `None`, the value in the row will be `null`. -/// -/// On a conflict, it will do nothing, leaving the old value. -pub fn insert_canonical_slot(conn: &mut PgConn, new_slot: WatchCanonicalSlot) -> Result<(), Error> { - diesel::insert_into(canonical_slots::table) - .values(&new_slot) - .on_conflict_do_nothing() - .execute(conn)?; - - debug!("Canonical slot inserted: {}", new_slot.slot); - Ok(()) -} - -pub fn insert_beacon_block( - conn: &mut PgConn, - block: SignedBeaconBlock, - root: WatchHash, -) -> Result<(), Error> { - use self::canonical_slots::dsl::{beacon_block, slot as canonical_slot}; - - let block_message = block.message(); - - // Pull out relevant values from the block. - let slot = WatchSlot::from_slot(block.slot()); - let parent_root = WatchHash::from_hash(block.parent_root()); - let proposer_index = block_message.proposer_index() as i32; - let graffiti = block_message.body().graffiti().as_utf8_lossy(); - let attestation_count = block_message.body().attestations_len() as i32; - - let full_payload = block_message.execution_payload().ok(); - - let transaction_count: Option = if let Some(bellatrix_payload) = - full_payload.and_then(|payload| payload.execution_payload_bellatrix().ok()) - { - Some(bellatrix_payload.transactions.len() as i32) - } else { - full_payload - .and_then(|payload| payload.execution_payload_capella().ok()) - .map(|payload| payload.transactions.len() as i32) - }; - - let withdrawal_count: Option = full_payload - .and_then(|payload| payload.execution_payload_capella().ok()) - .map(|payload| payload.withdrawals.len() as i32); - - let block_to_add = WatchBeaconBlock { - slot, - root, - parent_root, - attestation_count, - transaction_count, - withdrawal_count, - }; - - let proposer_info_to_add = WatchProposerInfo { - slot, - proposer_index, - graffiti, - }; - - // Update the canonical slots table. - diesel::update(canonical_slots::table) - .set(beacon_block.eq(root)) - .filter(canonical_slot.eq(slot)) - // Do not overwrite the value if it already exists. - .filter(beacon_block.is_null()) - .execute(conn)?; - - diesel::insert_into(beacon_blocks::table) - .values(block_to_add) - .on_conflict_do_nothing() - .execute(conn)?; - - diesel::insert_into(proposer_info::table) - .values(proposer_info_to_add) - .on_conflict_do_nothing() - .execute(conn)?; - - debug!("Beacon block inserted at slot: {slot}, root: {root}, parent: {parent_root}"); - Ok(()) -} - -/// Insert a validator into the `validators` table -/// -/// On a conflict, it will only overwrite `status`, `activation_epoch` and `exit_epoch`. -pub fn insert_validator(conn: &mut PgConn, validator: WatchValidator) -> Result<(), Error> { - use self::validators::dsl::*; - let new_index = validator.index; - let new_public_key = validator.public_key; - - diesel::insert_into(validators) - .values(validator) - .on_conflict(index) - .do_update() - .set(( - status.eq(excluded(status)), - activation_epoch.eq(excluded(activation_epoch)), - exit_epoch.eq(excluded(exit_epoch)), - )) - .execute(conn)?; - - debug!("Validator inserted, index: {new_index}, public_key: {new_public_key}"); - Ok(()) -} - -/// Insert a batch of values into the `validators` table. -/// -/// On a conflict, it will do nothing. -/// -/// Should not be used when updating validators. -/// Validators should be updated through the `insert_validator` function which contains the correct -/// `on_conflict` clauses. -pub fn insert_batch_validators( - conn: &mut PgConn, - all_validators: Vec, -) -> Result<(), Error> { - use self::validators::dsl::*; - - let mut count = 0; - - for chunk in all_validators.chunks(1000) { - count += diesel::insert_into(validators) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - debug!("Validators inserted, count: {count}"); - Ok(()) -} - -/* - * SELECT statements - */ - -/// Selects a single row of the `canonical_slots` table corresponding to a given `slot_query`. -pub fn get_canonical_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: {slot_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `canonical_slots` table corresponding to a given `root_query`. -/// Only returns the non-skipped slot which matches `root`. -pub fn get_canonical_slot_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(root.eq(root_query)) - .filter(skipped.eq(false)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical root requested: {root_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `root` from a single row of the `canonical_slots` table corresponding to a given -/// `slot_query`. -#[allow(dead_code)] -pub fn get_root_at_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .select(root) - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: {slot_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from the row of the `canonical_slots` table corresponding to the minimum value -/// of `slot`. -pub fn get_lowest_canonical_slot(conn: &mut PgConn) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: lowest, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `slot` from the row of the `canonical_slots` table corresponding to the minimum value -/// of `slot` and where `skipped == false`. -pub fn get_lowest_non_skipped_canonical_slot( - conn: &mut PgConn, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(skipped.eq(false)) - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: lowest_non_skipped, time taken: {time_taken:?})"); - Ok(result) -} - -/// Select 'slot' from the row of the `canonical_slots` table corresponding to the maximum value -/// of `slot`. -pub fn get_highest_canonical_slot(conn: &mut PgConn) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: highest, time taken: {time_taken:?}"); - Ok(result) -} - -/// Select 'slot' from the row of the `canonical_slots` table corresponding to the maximum value -/// of `slot` and where `skipped == false`. -pub fn get_highest_non_skipped_canonical_slot( - conn: &mut PgConn, -) -> Result, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(skipped.eq(false)) - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Canonical slot requested: highest_non_skipped, time taken: {time_taken:?}"); - Ok(result) -} - -/// Select all rows of the `canonical_slots` table where `slot >= `start_slot && slot <= -/// `end_slot`. -pub fn get_canonical_slots_by_range( - conn: &mut PgConn, - start_slot: WatchSlot, - end_slot: WatchSlot, -) -> Result>, Error> { - use self::canonical_slots::dsl::*; - let timer = Instant::now(); - - let result = canonical_slots - .filter(slot.ge(start_slot)) - .filter(slot.le(end_slot)) - .load::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!( - "Canonical slots by range requested, start_slot: {}, end_slot: {}, time_taken: {:?}", - start_slot.as_u64(), - end_slot.as_u64(), - time_taken - ); - Ok(result) -} - -/// Selects `root` from all rows of the `canonical_slots` table which have `beacon_block == null` -/// and `skipped == false` -pub fn get_unknown_canonical_blocks(conn: &mut PgConn) -> Result, Error> { - use self::canonical_slots::dsl::*; - - let result = canonical_slots - .select(root) - .filter(beacon_block.is_null()) - .filter(skipped.eq(false)) - .order_by(slot.desc()) - .load::(conn)?; - - Ok(result) -} - -/// Selects the row from the `beacon_blocks` table where `slot` is minimum. -pub fn get_lowest_beacon_block(conn: &mut PgConn) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .order_by(slot.asc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Beacon block requested: lowest, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `beacon_blocks` table where `slot` is maximum. -pub fn get_highest_beacon_block(conn: &mut PgConn) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .order_by(slot.desc()) - .limit(1) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Beacon block requested: highest, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row from the `beacon_blocks` table corresponding to a given `root_query`. -pub fn get_beacon_block_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - let time_taken = timer.elapsed(); - debug!("Beacon block requested: {root_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row from the `beacon_blocks` table corresponding to a given `slot_query`. -pub fn get_beacon_block_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - let time_taken = timer.elapsed(); - debug!("Beacon block requested: {slot_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects the row from the `beacon_blocks` table where `parent_root` equals the given `parent`. -/// This fetches the next block in the database. -/// -/// Will return `Ok(None)` if there are no matching blocks (e.g. the tip of the chain). -pub fn get_beacon_block_with_parent( - conn: &mut PgConn, - parent: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .filter(parent_root.eq(parent)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Next beacon block requested: {parent}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Select all rows of the `beacon_blocks` table where `slot >= `start_slot && slot <= -/// `end_slot`. -pub fn get_beacon_blocks_by_range( - conn: &mut PgConn, - start_slot: WatchSlot, - end_slot: WatchSlot, -) -> Result>, Error> { - use self::beacon_blocks::dsl::*; - let timer = Instant::now(); - - let result = beacon_blocks - .filter(slot.ge(start_slot)) - .filter(slot.le(end_slot)) - .load::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Beacon blocks by range requested, start_slot: {start_slot}, end_slot: {end_slot}, time_taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `proposer_info` table corresponding to a given `root_query`. -pub fn get_proposer_info_by_root( - conn: &mut PgConn, - root_query: WatchHash, -) -> Result, Error> { - use self::beacon_blocks::dsl::{beacon_blocks, root}; - use self::proposer_info::dsl::*; - let timer = Instant::now(); - - let join = beacon_blocks.inner_join(proposer_info); - - let result = join - .select((slot, proposer_index, graffiti)) - .filter(root.eq(root_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Proposer info requested for block: {root_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row of the `proposer_info` table corresponding to a given `slot_query`. -pub fn get_proposer_info_by_slot( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result, Error> { - use self::proposer_info::dsl::*; - let timer = Instant::now(); - - let result = proposer_info - .filter(slot.eq(slot_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Proposer info requested for slot: {slot_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects multiple rows of the `proposer_info` table between `start_slot` and `end_slot`. -/// Selects a single row of the `proposer_info` table corresponding to a given `slot_query`. -#[allow(dead_code)] -pub fn get_proposer_info_by_range( - conn: &mut PgConn, - start_slot: WatchSlot, - end_slot: WatchSlot, -) -> Result>, Error> { - use self::proposer_info::dsl::*; - let timer = Instant::now(); - - let result = proposer_info - .filter(slot.ge(start_slot)) - .filter(slot.le(end_slot)) - .load::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!( - "Proposer info requested for range: {start_slot} to {end_slot}, time taken: {time_taken:?}" - ); - Ok(result) -} - -pub fn get_validators_latest_proposer_info( - conn: &mut PgConn, - indices_query: Vec, -) -> Result, Error> { - use self::proposer_info::dsl::*; - - let proposers = proposer_info - .filter(proposer_index.eq_any(indices_query)) - .load::(conn)?; - - let mut result = HashMap::new(); - for proposer in proposers { - result - .entry(proposer.proposer_index) - .or_insert_with(|| proposer.clone()); - let entry = result - .get_mut(&proposer.proposer_index) - .ok_or_else(|| Error::Other("An internal error occured".to_string()))?; - if proposer.slot > entry.slot { - entry.slot = proposer.slot - } - } - - Ok(result) -} - -/// Selects the max(`slot`) and `proposer_index` of each unique index in the -/// `proposer_info` table and returns them formatted as a `HashMap`. -/// Only returns rows which have `slot <= target_slot`. -/// -/// Ideally, this would return the full row, but I have not found a way to do that without using -/// a much more expensive SQL query. -pub fn get_all_validators_latest_proposer_info_at_slot( - conn: &mut PgConn, - target_slot: WatchSlot, -) -> Result, Error> { - use self::proposer_info::dsl::*; - - let latest_proposals: Vec<(i32, Option)> = proposer_info - .group_by(proposer_index) - .select((proposer_index, max(slot))) - .filter(slot.le(target_slot)) - .load::<(i32, Option)>(conn)?; - - let mut result = HashMap::new(); - - for proposal in latest_proposals { - if let Some(latest_slot) = proposal.1 { - result.insert(latest_slot, proposal.0); - } - } - - Ok(result) -} - -/// Selects a single row from the `validators` table corresponding to a given -/// `validator_index_query`. -pub fn get_validator_by_index( - conn: &mut PgConn, - validator_index_query: i32, -) -> Result, Error> { - use self::validators::dsl::*; - let timer = Instant::now(); - - let result = validators - .filter(index.eq(validator_index_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Validator requested: {validator_index_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row from the `validators` table corresponding to a given -/// `public_key_query`. -pub fn get_validator_by_public_key( - conn: &mut PgConn, - public_key_query: WatchPK, -) -> Result, Error> { - use self::validators::dsl::*; - let timer = Instant::now(); - - let result = validators - .filter(public_key.eq(public_key_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Validator requested: {public_key_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects all rows from the `validators` table which have an `index` contained in -/// the `indices_query`. -#[allow(dead_code)] -pub fn get_validators_by_indices( - conn: &mut PgConn, - indices_query: Vec, -) -> Result, Error> { - use self::validators::dsl::*; - let timer = Instant::now(); - - let query_len = indices_query.len(); - let result = validators - .filter(index.eq_any(indices_query)) - .load::(conn)?; - - let time_taken = timer.elapsed(); - debug!("{query_len} validators requested, time taken: {time_taken:?}"); - Ok(result) -} - -// Selects all rows from the `validators` table. -pub fn get_all_validators(conn: &mut PgConn) -> Result, Error> { - use self::validators::dsl::*; - let timer = Instant::now(); - - let result = validators.load::(conn)?; - - let time_taken = timer.elapsed(); - debug!("All validators requested, time taken: {time_taken:?}"); - Ok(result) -} - -/// Counts the number of rows in the `validators` table. -#[allow(dead_code)] -pub fn count_validators(conn: &mut PgConn) -> Result { - use self::validators::dsl::*; - - validators.count().get_result(conn).map_err(Error::Database) -} - -/// Counts the number of rows in the `validators` table where -/// `activation_epoch <= target_slot.epoch()`. -pub fn count_validators_activated_before_slot( - conn: &mut PgConn, - target_slot: WatchSlot, - slots_per_epoch: u64, -) -> Result { - use self::validators::dsl::*; - - let target_epoch = target_slot.epoch(slots_per_epoch); - - validators - .count() - .filter(activation_epoch.le(target_epoch.as_u64() as i32)) - .get_result(conn) - .map_err(Error::Database) -} - -/* - * DELETE statements. - */ - -/// Deletes all rows of the `canonical_slots` table which have `slot` greater than `slot_query`. -/// -/// Due to the ON DELETE CASCADE clause present in the database migration SQL, deleting rows from -/// `canonical_slots` will delete all corresponding rows in `beacon_blocks, `block_rewards`, -/// `block_packing` and `proposer_info`. -pub fn delete_canonical_slots_above( - conn: &mut PgConn, - slot_query: WatchSlot, -) -> Result { - use self::canonical_slots::dsl::*; - - let result = diesel::delete(canonical_slots) - .filter(slot.gt(slot_query)) - .execute(conn)?; - - debug!("Deleted canonical slots above {slot_query}: {result} rows deleted"); - Ok(result) -} - -/// Deletes all rows of the `suboptimal_attestations` table which have `epoch_start_slot` greater -/// than `epoch_start_slot_query`. -pub fn delete_suboptimal_attestations_above( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result { - use self::suboptimal_attestations::dsl::*; - - let result = diesel::delete(suboptimal_attestations) - .filter(epoch_start_slot.gt(epoch_start_slot_query)) - .execute(conn)?; - - debug!("Deleted attestations above: {epoch_start_slot_query}, rows deleted: {result}"); - Ok(result) -} diff --git a/watch/src/database/models.rs b/watch/src/database/models.rs deleted file mode 100644 index f42444d661..0000000000 --- a/watch/src/database/models.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::database::{ - schema::{beacon_blocks, canonical_slots, proposer_info, validators}, - watch_types::{WatchHash, WatchPK, WatchSlot}, -}; -use diesel::{Insertable, Queryable}; -use serde::{Deserialize, Serialize}; -use std::hash::{Hash, Hasher}; - -pub type WatchEpoch = i32; - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = canonical_slots)] -pub struct WatchCanonicalSlot { - pub slot: WatchSlot, - pub root: WatchHash, - pub skipped: bool, - pub beacon_block: Option, -} - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = beacon_blocks)] -pub struct WatchBeaconBlock { - pub slot: WatchSlot, - pub root: WatchHash, - pub parent_root: WatchHash, - pub attestation_count: i32, - pub transaction_count: Option, - pub withdrawal_count: Option, -} - -#[derive(Clone, Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = validators)] -pub struct WatchValidator { - pub index: i32, - pub public_key: WatchPK, - pub status: String, - pub activation_epoch: Option, - pub exit_epoch: Option, -} - -// Implement a minimal version of `Hash` and `Eq` so that we know if a validator status has changed. -impl Hash for WatchValidator { - fn hash(&self, state: &mut H) { - self.index.hash(state); - self.status.hash(state); - self.activation_epoch.hash(state); - self.exit_epoch.hash(state); - } -} - -impl PartialEq for WatchValidator { - fn eq(&self, other: &Self) -> bool { - self.index == other.index - && self.status == other.status - && self.activation_epoch == other.activation_epoch - && self.exit_epoch == other.exit_epoch - } -} -impl Eq for WatchValidator {} - -#[derive(Clone, Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = proposer_info)] -pub struct WatchProposerInfo { - pub slot: WatchSlot, - pub proposer_index: i32, - pub graffiti: String, -} diff --git a/watch/src/database/schema.rs b/watch/src/database/schema.rs deleted file mode 100644 index 32f22d506d..0000000000 --- a/watch/src/database/schema.rs +++ /dev/null @@ -1,102 +0,0 @@ -// @generated automatically by Diesel CLI. - -diesel::table! { - active_config (id) { - id -> Int4, - config_name -> Text, - slots_per_epoch -> Int4, - } -} - -diesel::table! { - beacon_blocks (slot) { - slot -> Int4, - root -> Bytea, - parent_root -> Bytea, - attestation_count -> Int4, - transaction_count -> Nullable, - withdrawal_count -> Nullable, - } -} - -diesel::table! { - block_packing (slot) { - slot -> Int4, - available -> Int4, - included -> Int4, - prior_skip_slots -> Int4, - } -} - -diesel::table! { - block_rewards (slot) { - slot -> Int4, - total -> Int4, - attestation_reward -> Int4, - sync_committee_reward -> Int4, - } -} - -diesel::table! { - blockprint (slot) { - slot -> Int4, - best_guess -> Text, - } -} - -diesel::table! { - canonical_slots (slot) { - slot -> Int4, - root -> Bytea, - skipped -> Bool, - beacon_block -> Nullable, - } -} - -diesel::table! { - proposer_info (slot) { - slot -> Int4, - proposer_index -> Int4, - graffiti -> Text, - } -} - -diesel::table! { - suboptimal_attestations (epoch_start_slot, index) { - epoch_start_slot -> Int4, - index -> Int4, - source -> Bool, - head -> Bool, - target -> Bool, - } -} - -diesel::table! { - validators (index) { - index -> Int4, - public_key -> Bytea, - status -> Text, - activation_epoch -> Nullable, - exit_epoch -> Nullable, - } -} - -diesel::joinable!(block_packing -> beacon_blocks (slot)); -diesel::joinable!(block_rewards -> beacon_blocks (slot)); -diesel::joinable!(blockprint -> beacon_blocks (slot)); -diesel::joinable!(proposer_info -> beacon_blocks (slot)); -diesel::joinable!(proposer_info -> validators (proposer_index)); -diesel::joinable!(suboptimal_attestations -> canonical_slots (epoch_start_slot)); -diesel::joinable!(suboptimal_attestations -> validators (index)); - -diesel::allow_tables_to_appear_in_same_query!( - active_config, - beacon_blocks, - block_packing, - block_rewards, - blockprint, - canonical_slots, - proposer_info, - suboptimal_attestations, - validators, -); diff --git a/watch/src/database/utils.rs b/watch/src/database/utils.rs deleted file mode 100644 index 9134c3698f..0000000000 --- a/watch/src/database/utils.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![allow(dead_code)] -use crate::database::config::Config; -use diesel::prelude::*; -use diesel_migrations::{FileBasedMigrations, MigrationHarness}; - -/// Sets `config.dbname` to `config.default_dbname` and returns `(new_config, old_dbname)`. -/// -/// This is useful for creating or dropping databases, since these actions must be done by -/// logging into another database. -pub fn get_config_using_default_db(config: &Config) -> (Config, String) { - let mut config = config.clone(); - let new_dbname = std::mem::replace(&mut config.dbname, config.default_dbname.clone()); - (config, new_dbname) -} - -/// Runs the set of migrations as detected in the local directory. -/// Equivalent to `diesel migration run`. -/// -/// Contains `unwrap`s so is only suitable for test code. -/// TODO(mac) refactor to return Result -pub fn run_migrations(config: &Config) -> PgConnection { - let database_url = config.clone().build_database_url(); - let mut conn = PgConnection::establish(&database_url).unwrap(); - let migrations = FileBasedMigrations::find_migrations_directory().unwrap(); - conn.run_pending_migrations(migrations).unwrap(); - conn.begin_test_transaction().unwrap(); - conn -} diff --git a/watch/src/database/watch_types.rs b/watch/src/database/watch_types.rs deleted file mode 100644 index c2b67084c9..0000000000 --- a/watch/src/database/watch_types.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::database::error::Error; -use diesel::{ - sql_types::{Binary, Integer}, - AsExpression, FromSqlRow, -}; -use serde::{Deserialize, Serialize}; -use std::fmt; -use std::str::FromStr; -use types::{Epoch, Hash256, PublicKeyBytes, Slot}; -#[derive( - Clone, - Copy, - Debug, - AsExpression, - FromSqlRow, - Deserialize, - Serialize, - Hash, - PartialEq, - Eq, - PartialOrd, - Ord, -)] -#[diesel(sql_type = Integer)] -pub struct WatchSlot(Slot); - -impl fmt::Display for WatchSlot { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl WatchSlot { - pub fn new(slot: u64) -> Self { - Self(Slot::new(slot)) - } - - pub fn from_slot(slot: Slot) -> Self { - Self(slot) - } - - pub fn as_slot(self) -> Slot { - self.0 - } - - pub fn as_u64(self) -> u64 { - self.0.as_u64() - } - - pub fn epoch(self, slots_per_epoch: u64) -> Epoch { - self.as_slot().epoch(slots_per_epoch) - } -} - -#[derive(Clone, Copy, Debug, AsExpression, FromSqlRow, Deserialize, Serialize)] -#[diesel(sql_type = Binary)] -pub struct WatchHash(Hash256); - -impl fmt::Display for WatchHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.0) - } -} - -impl WatchHash { - pub fn as_hash(&self) -> Hash256 { - self.0 - } - - pub fn from_hash(hash: Hash256) -> Self { - WatchHash(hash) - } - - pub fn as_bytes(&self) -> &[u8] { - self.0.as_slice() - } - - pub fn from_bytes(src: &[u8]) -> Result { - if src.len() == 32 { - Ok(WatchHash(Hash256::from_slice(src))) - } else { - Err(Error::InvalidRoot) - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, AsExpression, FromSqlRow, Serialize, Deserialize)] -#[diesel(sql_type = Binary)] -pub struct WatchPK(PublicKeyBytes); - -impl fmt::Display for WatchPK { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.0) - } -} - -impl WatchPK { - pub fn as_bytes(&self) -> &[u8] { - self.0.as_serialized() - } - - pub fn from_bytes(src: &[u8]) -> Result { - Ok(WatchPK(PublicKeyBytes::deserialize(src)?)) - } - - pub fn from_pubkey(key: PublicKeyBytes) -> Self { - WatchPK(key) - } -} - -impl FromStr for WatchPK { - type Err = String; - - fn from_str(s: &str) -> Result { - Ok(WatchPK( - PublicKeyBytes::from_str(s).map_err(|e| format!("Cannot be parsed: {}", e))?, - )) - } -} diff --git a/watch/src/lib.rs b/watch/src/lib.rs deleted file mode 100644 index 664c945165..0000000000 --- a/watch/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![cfg(unix)] -pub mod block_packing; -pub mod block_rewards; -pub mod blockprint; -pub mod cli; -pub mod client; -pub mod config; -pub mod database; -pub mod logger; -pub mod server; -pub mod suboptimal_attestations; -pub mod updater; diff --git a/watch/src/logger.rs b/watch/src/logger.rs deleted file mode 100644 index 49310b42aa..0000000000 --- a/watch/src/logger.rs +++ /dev/null @@ -1,24 +0,0 @@ -use env_logger::Builder; -use log::{info, LevelFilter}; -use std::process; - -pub fn init_logger(log_level: &str) { - let log_level = match log_level.to_lowercase().as_str() { - "trace" => LevelFilter::Trace, - "debug" => LevelFilter::Debug, - "info" => LevelFilter::Info, - "warn" => LevelFilter::Warn, - "error" => LevelFilter::Error, - _ => { - eprintln!("Unsupported log level"); - process::exit(1) - } - }; - - let mut builder = Builder::new(); - builder.filter(Some("watch"), log_level); - - builder.init(); - - info!("Logger initialized with log-level: {log_level}"); -} diff --git a/watch/src/main.rs b/watch/src/main.rs deleted file mode 100644 index f971747da4..0000000000 --- a/watch/src/main.rs +++ /dev/null @@ -1,41 +0,0 @@ -#[cfg(unix)] -use std::process; - -#[cfg(unix)] -mod block_packing; -#[cfg(unix)] -mod block_rewards; -#[cfg(unix)] -mod blockprint; -#[cfg(unix)] -mod cli; -#[cfg(unix)] -mod config; -#[cfg(unix)] -mod database; -#[cfg(unix)] -mod logger; -#[cfg(unix)] -mod server; -#[cfg(unix)] -mod suboptimal_attestations; -#[cfg(unix)] -mod updater; - -#[cfg(unix)] -#[tokio::main] -async fn main() { - match cli::run().await { - Ok(()) => process::exit(0), - Err(e) => { - eprintln!("Command failed with: {}", e); - drop(e); - process::exit(1) - } - } -} - -#[cfg(windows)] -fn main() { - eprintln!("Windows is not supported. Exiting."); -} diff --git a/watch/src/server/config.rs b/watch/src/server/config.rs deleted file mode 100644 index a7d38e706f..0000000000 --- a/watch/src/server/config.rs +++ /dev/null @@ -1,28 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::net::IpAddr; - -pub const LISTEN_ADDR: &str = "127.0.0.1"; - -pub const fn listen_port() -> u16 { - 5059 -} -fn listen_addr() -> IpAddr { - LISTEN_ADDR.parse().expect("Server address is not valid") -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - #[serde(default = "listen_addr")] - pub listen_addr: IpAddr, - #[serde(default = "listen_port")] - pub listen_port: u16, -} - -impl Default for Config { - fn default() -> Self { - Self { - listen_addr: listen_addr(), - listen_port: listen_port(), - } - } -} diff --git a/watch/src/server/error.rs b/watch/src/server/error.rs deleted file mode 100644 index e2c8f0f42a..0000000000 --- a/watch/src/server/error.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::database::Error as DbError; -use axum::Error as AxumError; -use axum::{http::StatusCode, response::IntoResponse, Json}; -use hyper::Error as HyperError; -use serde_json::json; -use std::io::Error as IoError; - -#[derive(Debug)] -#[allow(dead_code)] -pub enum Error { - Axum(AxumError), - Hyper(HyperError), - Database(DbError), - IoError(IoError), - BadRequest, - NotFound, - Other(String), -} - -impl IntoResponse for Error { - fn into_response(self) -> axum::response::Response { - let (status, error_message) = match self { - Self::BadRequest => (StatusCode::BAD_REQUEST, "Bad Request"), - Self::NotFound => (StatusCode::NOT_FOUND, "Not Found"), - _ => (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error"), - }; - (status, Json(json!({ "error": error_message }))).into_response() - } -} - -impl From for Error { - fn from(e: HyperError) -> Self { - Error::Hyper(e) - } -} - -impl From for Error { - fn from(e: AxumError) -> Self { - Error::Axum(e) - } -} - -impl From for Error { - fn from(e: DbError) -> Self { - Error::Database(e) - } -} - -impl From for Error { - fn from(e: IoError) -> Self { - Error::IoError(e) - } -} - -impl From for Error { - fn from(e: String) -> Self { - Error::Other(e) - } -} diff --git a/watch/src/server/handler.rs b/watch/src/server/handler.rs deleted file mode 100644 index 6777026867..0000000000 --- a/watch/src/server/handler.rs +++ /dev/null @@ -1,266 +0,0 @@ -use crate::database::{ - self, Error as DbError, PgPool, WatchBeaconBlock, WatchCanonicalSlot, WatchHash, WatchPK, - WatchProposerInfo, WatchSlot, WatchValidator, -}; -use crate::server::Error; -use axum::{ - extract::{Path, Query}, - Extension, Json, -}; -use eth2::types::BlockId; -use std::collections::HashMap; -use std::str::FromStr; - -pub async fn get_slot( - Path(slot): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_canonical_slot( - &mut conn, - WatchSlot::new(slot), - )?)) -} - -pub async fn get_slot_lowest( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_lowest_canonical_slot(&mut conn)?)) -} - -pub async fn get_slot_highest( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_highest_canonical_slot(&mut conn)?)) -} - -pub async fn get_slots_by_range( - Query(query): Query>, - Extension(pool): Extension, -) -> Result>>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - if let Some(start_slot) = query.get("start_slot") { - if let Some(end_slot) = query.get("end_slot") { - if start_slot > end_slot { - Err(Error::BadRequest) - } else { - Ok(Json(database::get_canonical_slots_by_range( - &mut conn, - WatchSlot::new(*start_slot), - WatchSlot::new(*end_slot), - )?)) - } - } else { - Err(Error::BadRequest) - } - } else { - Err(Error::BadRequest) - } -} - -pub async fn get_block( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - let block_id: BlockId = BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)?; - match block_id { - BlockId::Slot(slot) => Ok(Json(database::get_beacon_block_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - BlockId::Root(root) => Ok(Json(database::get_beacon_block_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_block_lowest( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_lowest_beacon_block(&mut conn)?)) -} - -pub async fn get_block_highest( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_highest_beacon_block(&mut conn)?)) -} - -pub async fn get_block_previous( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => { - if let Some(block) = - database::get_beacon_block_by_root(&mut conn, WatchHash::from_hash(root))? - .map(|block| block.parent_root) - { - Ok(Json(database::get_beacon_block_by_root(&mut conn, block)?)) - } else { - Err(Error::NotFound) - } - } - BlockId::Slot(slot) => Ok(Json(database::get_beacon_block_by_slot( - &mut conn, - WatchSlot::new(slot.as_u64().checked_sub(1_u64).ok_or(Error::NotFound)?), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_block_next( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(database::get_beacon_block_with_parent( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(database::get_beacon_block_by_slot( - &mut conn, - WatchSlot::from_slot(slot + 1_u64), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_blocks_by_range( - Query(query): Query>, - Extension(pool): Extension, -) -> Result>>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - if let Some(start_slot) = query.get("start_slot") { - if let Some(end_slot) = query.get("end_slot") { - if start_slot > end_slot { - Err(Error::BadRequest) - } else { - Ok(Json(database::get_beacon_blocks_by_range( - &mut conn, - WatchSlot::new(*start_slot), - WatchSlot::new(*end_slot), - )?)) - } - } else { - Err(Error::BadRequest) - } - } else { - Err(Error::BadRequest) - } -} - -pub async fn get_block_proposer( - Path(block_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - match BlockId::from_str(&block_query).map_err(|_| Error::BadRequest)? { - BlockId::Root(root) => Ok(Json(database::get_proposer_info_by_root( - &mut conn, - WatchHash::from_hash(root), - )?)), - BlockId::Slot(slot) => Ok(Json(database::get_proposer_info_by_slot( - &mut conn, - WatchSlot::from_slot(slot), - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_validator( - Path(validator_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - if validator_query.starts_with("0x") { - let pubkey = WatchPK::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - Ok(Json(database::get_validator_by_public_key( - &mut conn, pubkey, - )?)) - } else { - let index = i32::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - Ok(Json(database::get_validator_by_index(&mut conn, index)?)) - } -} - -pub async fn get_all_validators( - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - Ok(Json(database::get_all_validators(&mut conn)?)) -} - -pub async fn get_validator_latest_proposal( - Path(validator_query): Path, - Extension(pool): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - if validator_query.starts_with("0x") { - let pubkey = WatchPK::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - let validator = - database::get_validator_by_public_key(&mut conn, pubkey)?.ok_or(Error::NotFound)?; - Ok(Json(database::get_validators_latest_proposer_info( - &mut conn, - vec![validator.index], - )?)) - } else { - let index = i32::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - Ok(Json(database::get_validators_latest_proposer_info( - &mut conn, - vec![index], - )?)) - } -} - -pub async fn get_client_breakdown( - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - - if let Some(target_slot) = database::get_highest_canonical_slot(&mut conn)? { - Ok(Json(database::get_validators_clients_at_slot( - &mut conn, - target_slot.slot, - slots_per_epoch, - )?)) - } else { - Err(Error::Database(DbError::Other( - "No slots found in database.".to_string(), - ))) - } -} - -pub async fn get_client_breakdown_percentages( - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = database::get_connection(&pool).map_err(Error::Database)?; - - let mut result = HashMap::new(); - if let Some(target_slot) = database::get_highest_canonical_slot(&mut conn)? { - let total = database::count_validators_activated_before_slot( - &mut conn, - target_slot.slot, - slots_per_epoch, - )?; - let clients = - database::get_validators_clients_at_slot(&mut conn, target_slot.slot, slots_per_epoch)?; - for (client, number) in clients.iter() { - let percentage: f64 = *number as f64 / total as f64 * 100.0; - result.insert(client.to_string(), percentage); - } - } - - Ok(Json(result)) -} diff --git a/watch/src/server/mod.rs b/watch/src/server/mod.rs deleted file mode 100644 index 08036db951..0000000000 --- a/watch/src/server/mod.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::block_packing::block_packing_routes; -use crate::block_rewards::block_rewards_routes; -use crate::blockprint::blockprint_routes; -use crate::config::Config as FullConfig; -use crate::database::{self, PgPool}; -use crate::suboptimal_attestations::{attestation_routes, blockprint_attestation_routes}; -use axum::{ - http::{StatusCode, Uri}, - routing::get, - Extension, Json, Router, -}; -use eth2::types::ErrorMessage; -use log::info; -use std::future::{Future, IntoFuture}; -use std::net::{SocketAddr, TcpListener}; - -pub use config::Config; -pub use error::Error; - -mod config; -mod error; -mod handler; - -pub async fn serve(config: FullConfig) -> Result<(), Error> { - let db = database::build_connection_pool(&config.database)?; - let (_, slots_per_epoch) = database::get_active_config(&mut database::get_connection(&db)?)? - .ok_or_else(|| { - Error::Other( - "Database not found. Please run the updater prior to starting the server" - .to_string(), - ) - })?; - - let (_addr, server) = start_server(&config, slots_per_epoch as u64, db)?; - - server.await?; - - Ok(()) -} - -/// Creates a server that will serve requests using information from `config`. -/// -/// The server will create its own connection pool to serve connections to the database. -/// This is separate to the connection pool that is used for the `updater`. -/// -/// The server will shut down gracefully when the `shutdown` future resolves. -/// -/// ## Returns -/// -/// This function will bind the server to the address specified in the config and then return a -/// Future representing the actual server that will need to be awaited. -/// -/// ## Errors -/// -/// Returns an error if the server is unable to bind or there is another error during -/// configuration. -pub fn start_server( - config: &FullConfig, - slots_per_epoch: u64, - pool: PgPool, -) -> Result< - ( - SocketAddr, - impl Future> + 'static, - ), - Error, -> { - let mut routes = Router::new() - .route("/v1/slots", get(handler::get_slots_by_range)) - .route("/v1/slots/:slot", get(handler::get_slot)) - .route("/v1/slots/lowest", get(handler::get_slot_lowest)) - .route("/v1/slots/highest", get(handler::get_slot_highest)) - .route("/v1/slots/:slot/block", get(handler::get_block)) - .route("/v1/blocks", get(handler::get_blocks_by_range)) - .route("/v1/blocks/:block", get(handler::get_block)) - .route("/v1/blocks/lowest", get(handler::get_block_lowest)) - .route("/v1/blocks/highest", get(handler::get_block_highest)) - .route( - "/v1/blocks/:block/previous", - get(handler::get_block_previous), - ) - .route("/v1/blocks/:block/next", get(handler::get_block_next)) - .route( - "/v1/blocks/:block/proposer", - get(handler::get_block_proposer), - ) - .route("/v1/validators/:validator", get(handler::get_validator)) - .route("/v1/validators/all", get(handler::get_all_validators)) - .route( - "/v1/validators/:validator/latest_proposal", - get(handler::get_validator_latest_proposal), - ) - .route("/v1/clients", get(handler::get_client_breakdown)) - .route( - "/v1/clients/percentages", - get(handler::get_client_breakdown_percentages), - ) - .merge(attestation_routes()) - .merge(blockprint_routes()) - .merge(block_packing_routes()) - .merge(block_rewards_routes()); - - if config.blockprint.enabled && config.updater.attestations { - routes = routes.merge(blockprint_attestation_routes()) - } - - let app = routes - .fallback(route_not_found) - .layer(Extension(pool)) - .layer(Extension(slots_per_epoch)); - - let addr = SocketAddr::new(config.server.listen_addr, config.server.listen_port); - let listener = TcpListener::bind(addr)?; - listener.set_nonblocking(true)?; - - // Read the socket address (it may be different from `addr` if listening on port 0). - let socket_addr = listener.local_addr()?; - - let serve = axum::serve(tokio::net::TcpListener::from_std(listener)?, app); - - info!("HTTP server listening on {}", addr); - - Ok((socket_addr, serve.into_future())) -} - -// The default route indicating that no available routes matched the request. -async fn route_not_found(uri: Uri) -> (StatusCode, Json) { - ( - StatusCode::METHOD_NOT_ALLOWED, - Json(ErrorMessage { - code: StatusCode::METHOD_NOT_ALLOWED.as_u16(), - message: format!("No route for {uri}"), - stacktraces: vec![], - }), - ) -} diff --git a/watch/src/suboptimal_attestations/database.rs b/watch/src/suboptimal_attestations/database.rs deleted file mode 100644 index cb947d250a..0000000000 --- a/watch/src/suboptimal_attestations/database.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::database::{ - schema::{suboptimal_attestations, validators}, - watch_types::{WatchPK, WatchSlot}, - Error, PgConn, MAX_SIZE_BATCH_INSERT, -}; - -use diesel::prelude::*; -use diesel::{Insertable, Queryable}; -use log::debug; -use serde::{Deserialize, Serialize}; -use std::time::Instant; - -use types::Epoch; - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct WatchAttestation { - pub index: i32, - pub epoch: Epoch, - pub source: bool, - pub head: bool, - pub target: bool, -} - -impl WatchAttestation { - pub fn optimal(index: i32, epoch: Epoch) -> WatchAttestation { - WatchAttestation { - index, - epoch, - source: true, - head: true, - target: true, - } - } -} - -#[derive(Debug, Queryable, Insertable, Serialize, Deserialize)] -#[diesel(table_name = suboptimal_attestations)] -pub struct WatchSuboptimalAttestation { - pub epoch_start_slot: WatchSlot, - pub index: i32, - pub source: bool, - pub head: bool, - pub target: bool, -} - -impl WatchSuboptimalAttestation { - pub fn to_attestation(&self, slots_per_epoch: u64) -> WatchAttestation { - WatchAttestation { - index: self.index, - epoch: self.epoch_start_slot.epoch(slots_per_epoch), - source: self.source, - head: self.head, - target: self.target, - } - } -} - -/// Insert a batch of values into the `suboptimal_attestations` table -/// -/// Since attestations technically occur per-slot but we only store them per-epoch (via its -/// `start_slot`) so if any slot in the epoch changes, we need to resync the whole epoch as a -/// 'suboptimal' attestation could now be 'optimal'. -/// -/// This is handled in the update code, where in the case of a re-org, the affected epoch is -/// deleted completely. -/// -/// On a conflict, it will do nothing. -pub fn insert_batch_suboptimal_attestations( - conn: &mut PgConn, - attestations: Vec, -) -> Result<(), Error> { - use self::suboptimal_attestations::dsl::*; - - let mut count = 0; - let timer = Instant::now(); - - for chunk in attestations.chunks(MAX_SIZE_BATCH_INSERT) { - count += diesel::insert_into(suboptimal_attestations) - .values(chunk) - .on_conflict_do_nothing() - .execute(conn)?; - } - - let time_taken = timer.elapsed(); - debug!("Attestations inserted, count: {count}, time taken: {time_taken:?}"); - Ok(()) -} - -/// Selects the row from the `suboptimal_attestations` table where `epoch_start_slot` is minimum. -pub fn get_lowest_attestation( - conn: &mut PgConn, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .order_by(epoch_start_slot.asc()) - .limit(1) - .first::(conn) - .optional()?) -} - -/// Selects the row from the `suboptimal_attestations` table where `epoch_start_slot` is maximum. -pub fn get_highest_attestation( - conn: &mut PgConn, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .order_by(epoch_start_slot.desc()) - .limit(1) - .first::(conn) - .optional()?) -} - -/// Selects a single row from the `suboptimal_attestations` table corresponding to a given -/// `index_query` and `epoch_query`. -pub fn get_attestation_by_index( - conn: &mut PgConn, - index_query: i32, - epoch_query: Epoch, - slots_per_epoch: u64, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - let timer = Instant::now(); - - let result = suboptimal_attestations - .filter(epoch_start_slot.eq(WatchSlot::from_slot( - epoch_query.start_slot(slots_per_epoch), - ))) - .filter(index.eq(index_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Attestation requested for validator: {index_query}, epoch: {epoch_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects a single row from the `suboptimal_attestations` table corresponding -/// to a given `pubkey_query` and `epoch_query`. -#[allow(dead_code)] -pub fn get_attestation_by_pubkey( - conn: &mut PgConn, - pubkey_query: WatchPK, - epoch_query: Epoch, - slots_per_epoch: u64, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - use self::validators::dsl::{public_key, validators}; - let timer = Instant::now(); - - let join = validators.inner_join(suboptimal_attestations); - - let result = join - .select((epoch_start_slot, index, source, head, target)) - .filter(epoch_start_slot.eq(WatchSlot::from_slot( - epoch_query.start_slot(slots_per_epoch), - ))) - .filter(public_key.eq(pubkey_query)) - .first::(conn) - .optional()?; - - let time_taken = timer.elapsed(); - debug!("Attestation requested for validator: {pubkey_query}, epoch: {epoch_query}, time taken: {time_taken:?}"); - Ok(result) -} - -/// Selects `index` for all validators in the suboptimal_attestations table -/// that have `source == false` for the corresponding `epoch_start_slot_query`. -pub fn get_validators_missed_source( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .select(index) - .filter(epoch_start_slot.eq(epoch_start_slot_query)) - .filter(source.eq(false)) - .load::(conn)?) -} - -/// Selects `index` for all validators in the suboptimal_attestations table -/// that have `head == false` for the corresponding `epoch_start_slot_query`. -pub fn get_validators_missed_head( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .select(index) - .filter(epoch_start_slot.eq(epoch_start_slot_query)) - .filter(head.eq(false)) - .load::(conn)?) -} - -/// Selects `index` for all validators in the suboptimal_attestations table -/// that have `target == false` for the corresponding `epoch_start_slot_query`. -pub fn get_validators_missed_target( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .select(index) - .filter(epoch_start_slot.eq(epoch_start_slot_query)) - .filter(target.eq(false)) - .load::(conn)?) -} - -/// Selects all rows from the `suboptimal_attestations` table for the given -/// `epoch_start_slot_query`. -pub fn get_all_suboptimal_attestations_for_epoch( - conn: &mut PgConn, - epoch_start_slot_query: WatchSlot, -) -> Result, Error> { - use self::suboptimal_attestations::dsl::*; - - Ok(suboptimal_attestations - .filter(epoch_start_slot.eq(epoch_start_slot_query)) - .load::(conn)?) -} diff --git a/watch/src/suboptimal_attestations/mod.rs b/watch/src/suboptimal_attestations/mod.rs deleted file mode 100644 index a94532e8ab..0000000000 --- a/watch/src/suboptimal_attestations/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -pub mod database; -pub mod server; -pub mod updater; - -use crate::database::watch_types::WatchSlot; -use crate::updater::error::Error; - -pub use database::{ - get_all_suboptimal_attestations_for_epoch, get_attestation_by_index, get_attestation_by_pubkey, - get_highest_attestation, get_lowest_attestation, insert_batch_suboptimal_attestations, - WatchAttestation, WatchSuboptimalAttestation, -}; - -pub use server::{attestation_routes, blockprint_attestation_routes}; - -use eth2::BeaconNodeHttpClient; -use types::Epoch; - -/// Sends a request to `lighthouse/analysis/attestation_performance`. -/// Formats the response into a vector of `WatchSuboptimalAttestation`. -/// -/// Any attestations with `source == true && head == true && target == true` are ignored. -pub async fn get_attestation_performances( - bn: &BeaconNodeHttpClient, - start_epoch: Epoch, - end_epoch: Epoch, - slots_per_epoch: u64, -) -> Result, Error> { - let mut output = Vec::new(); - let result = bn - .get_lighthouse_analysis_attestation_performance( - start_epoch, - end_epoch, - "global".to_string(), - ) - .await?; - for index in result { - for epoch in index.epochs { - if epoch.1.active { - // Check if the attestation is suboptimal. - if !epoch.1.source || !epoch.1.head || !epoch.1.target { - output.push(WatchSuboptimalAttestation { - epoch_start_slot: WatchSlot::from_slot( - Epoch::new(epoch.0).start_slot(slots_per_epoch), - ), - index: index.index as i32, - source: epoch.1.source, - head: epoch.1.head, - target: epoch.1.target, - }) - } - } - } - } - Ok(output) -} diff --git a/watch/src/suboptimal_attestations/server.rs b/watch/src/suboptimal_attestations/server.rs deleted file mode 100644 index 391db9a41b..0000000000 --- a/watch/src/suboptimal_attestations/server.rs +++ /dev/null @@ -1,299 +0,0 @@ -use crate::database::{ - get_canonical_slot, get_connection, get_validator_by_index, get_validator_by_public_key, - get_validators_clients_at_slot, get_validators_latest_proposer_info, PgPool, WatchPK, - WatchSlot, -}; - -use crate::blockprint::database::construct_validator_blockprints_at_slot; -use crate::server::Error; -use crate::suboptimal_attestations::database::{ - get_all_suboptimal_attestations_for_epoch, get_attestation_by_index, - get_validators_missed_head, get_validators_missed_source, get_validators_missed_target, - WatchAttestation, WatchSuboptimalAttestation, -}; - -use axum::{extract::Path, routing::get, Extension, Json, Router}; -use std::collections::{HashMap, HashSet}; -use std::str::FromStr; -use types::Epoch; - -// Will return Ok(None) if the epoch is not synced or if the validator does not exist. -// In the future it might be worth differentiating these events. -pub async fn get_validator_attestation( - Path((validator_query, epoch_query)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - let epoch = Epoch::new(epoch_query); - - // Ensure the database has synced the target epoch. - if get_canonical_slot( - &mut conn, - WatchSlot::from_slot(epoch.end_slot(slots_per_epoch)), - )? - .is_none() - { - // Epoch is not fully synced. - return Ok(Json(None)); - } - - let index = if validator_query.starts_with("0x") { - let pubkey = WatchPK::from_str(&validator_query).map_err(|_| Error::BadRequest)?; - get_validator_by_public_key(&mut conn, pubkey)? - .ok_or(Error::NotFound)? - .index - } else { - i32::from_str(&validator_query).map_err(|_| Error::BadRequest)? - }; - let attestation = if let Some(suboptimal_attestation) = - get_attestation_by_index(&mut conn, index, epoch, slots_per_epoch)? - { - Some(suboptimal_attestation.to_attestation(slots_per_epoch)) - } else { - // Attestation was not in database. Check if the validator was active. - match get_validator_by_index(&mut conn, index)? { - Some(validator) => { - if let Some(activation_epoch) = validator.activation_epoch { - if activation_epoch <= epoch.as_u64() as i32 { - if let Some(exit_epoch) = validator.exit_epoch { - if exit_epoch > epoch.as_u64() as i32 { - // Validator is active and has not yet exited. - Some(WatchAttestation::optimal(index, epoch)) - } else { - // Validator has exited. - None - } - } else { - // Validator is active and has not yet exited. - Some(WatchAttestation::optimal(index, epoch)) - } - } else { - // Validator is not yet active. - None - } - } else { - // Validator is not yet active. - None - } - } - None => return Err(Error::Other("Validator index does not exist".to_string())), - } - }; - Ok(Json(attestation)) -} - -pub async fn get_all_validators_attestations( - Path(epoch): Path, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - - let epoch_start_slot = WatchSlot::from_slot(Epoch::new(epoch).start_slot(slots_per_epoch)); - - Ok(Json(get_all_suboptimal_attestations_for_epoch( - &mut conn, - epoch_start_slot, - )?)) -} - -pub async fn get_validators_missed_vote( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - - let epoch_start_slot = WatchSlot::from_slot(Epoch::new(epoch).start_slot(slots_per_epoch)); - match vote.to_lowercase().as_str() { - "source" => Ok(Json(get_validators_missed_source( - &mut conn, - epoch_start_slot, - )?)), - "head" => Ok(Json(get_validators_missed_head( - &mut conn, - epoch_start_slot, - )?)), - "target" => Ok(Json(get_validators_missed_target( - &mut conn, - epoch_start_slot, - )?)), - _ => Err(Error::BadRequest), - } -} - -pub async fn get_validators_missed_vote_graffiti( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - - let Json(indices) = get_validators_missed_vote( - Path((vote, epoch)), - Extension(pool), - Extension(slots_per_epoch), - ) - .await?; - - let graffitis = get_validators_latest_proposer_info(&mut conn, indices)? - .values() - .map(|info| info.graffiti.clone()) - .collect::>(); - - let mut result = HashMap::new(); - for graffiti in graffitis { - if !result.contains_key(&graffiti) { - result.insert(graffiti.clone(), 0); - } - *result - .get_mut(&graffiti) - .ok_or_else(|| Error::Other("An unexpected error occurred".to_string()))? += 1; - } - - Ok(Json(result)) -} - -pub fn attestation_routes() -> Router { - Router::new() - .route( - "/v1/validators/:validator/attestation/:epoch", - get(get_validator_attestation), - ) - .route( - "/v1/validators/all/attestation/:epoch", - get(get_all_validators_attestations), - ) - .route( - "/v1/validators/missed/:vote/:epoch", - get(get_validators_missed_vote), - ) - .route( - "/v1/validators/missed/:vote/:epoch/graffiti", - get(get_validators_missed_vote_graffiti), - ) -} - -/// The functions below are dependent on Blockprint and if it is disabled, the endpoints will be -/// disabled. -pub async fn get_clients_missed_vote( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let mut conn = get_connection(&pool).map_err(Error::Database)?; - - let Json(indices) = get_validators_missed_vote( - Path((vote, epoch)), - Extension(pool), - Extension(slots_per_epoch), - ) - .await?; - - // All validators which missed the vote. - let indices_map = indices.into_iter().collect::>(); - - let target_slot = WatchSlot::from_slot(Epoch::new(epoch).start_slot(slots_per_epoch)); - - // All validators. - let client_map = - construct_validator_blockprints_at_slot(&mut conn, target_slot, slots_per_epoch)?; - - let mut result = HashMap::new(); - - for index in indices_map { - if let Some(print) = client_map.get(&index) { - if !result.contains_key(print) { - result.insert(print.clone(), 0); - } - *result - .get_mut(print) - .ok_or_else(|| Error::Other("An unexpected error occurred".to_string()))? += 1; - } - } - - Ok(Json(result)) -} - -pub async fn get_clients_missed_vote_percentages( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let Json(clients_counts) = get_clients_missed_vote( - Path((vote, epoch)), - Extension(pool.clone()), - Extension(slots_per_epoch), - ) - .await?; - - let target_slot = WatchSlot::from_slot(Epoch::new(epoch).start_slot(slots_per_epoch)); - - let mut conn = get_connection(&pool)?; - let totals = get_validators_clients_at_slot(&mut conn, target_slot, slots_per_epoch)?; - - let mut result = HashMap::new(); - for (client, count) in clients_counts.iter() { - let client_total: f64 = *totals - .get(client) - .ok_or_else(|| Error::Other("Client type mismatch".to_string()))? - as f64; - // `client_total` should never be `0`, but if it is, return `0` instead of `inf`. - if client_total == 0.0 { - result.insert(client.to_string(), 0.0); - } else { - let percentage: f64 = *count as f64 / client_total * 100.0; - result.insert(client.to_string(), percentage); - } - } - - Ok(Json(result)) -} - -pub async fn get_clients_missed_vote_percentages_relative( - Path((vote, epoch)): Path<(String, u64)>, - Extension(pool): Extension, - Extension(slots_per_epoch): Extension, -) -> Result>, Error> { - let Json(clients_counts) = get_clients_missed_vote( - Path((vote, epoch)), - Extension(pool), - Extension(slots_per_epoch), - ) - .await?; - - let mut total: u64 = 0; - for (_, count) in clients_counts.iter() { - total += *count - } - - let mut result = HashMap::new(); - for (client, count) in clients_counts.iter() { - // `total` should never be 0, but if it is, return `-` instead of `inf`. - if total == 0 { - result.insert(client.to_string(), 0.0); - } else { - let percentage: f64 = *count as f64 / total as f64 * 100.0; - result.insert(client.to_string(), percentage); - } - } - - Ok(Json(result)) -} - -pub fn blockprint_attestation_routes() -> Router { - Router::new() - .route( - "/v1/clients/missed/:vote/:epoch", - get(get_clients_missed_vote), - ) - .route( - "/v1/clients/missed/:vote/:epoch/percentages", - get(get_clients_missed_vote_percentages), - ) - .route( - "/v1/clients/missed/:vote/:epoch/percentages/relative", - get(get_clients_missed_vote_percentages_relative), - ) -} diff --git a/watch/src/suboptimal_attestations/updater.rs b/watch/src/suboptimal_attestations/updater.rs deleted file mode 100644 index d8f6ec57d5..0000000000 --- a/watch/src/suboptimal_attestations/updater.rs +++ /dev/null @@ -1,236 +0,0 @@ -use crate::database::{self, Error as DbError}; -use crate::updater::{Error, UpdateHandler}; - -use crate::suboptimal_attestations::get_attestation_performances; - -use eth2::types::EthSpec; -use log::{debug, error, warn}; - -const MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS: u64 = 50; - -impl UpdateHandler { - /// Forward fills the `suboptimal_attestations` table starting from the entry with the highest - /// slot. - /// - /// It construts a request to the `attestation_performance` API endpoint with: - /// `start_epoch` -> highest completely filled epoch + 1 (or epoch of lowest canonical slot) - /// `end_epoch` -> epoch of highest canonical slot - /// - /// It will resync the latest epoch if it is not fully filled but will not overwrite existing - /// values unless there is a re-org. - /// That is, `if highest_filled_slot % slots_per_epoch != 31`. - /// - /// In the event the most recent epoch has no suboptimal attestations, it will attempt to - /// resync that epoch. The odds of this occuring on mainnet are vanishingly small so it is not - /// accounted for. - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS`. - pub async fn fill_suboptimal_attestations(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - let highest_filled_slot_opt = if self.config.attestations { - database::get_highest_attestation(&mut conn)? - .map(|attestation| attestation.epoch_start_slot.as_slot()) - } else { - return Err(Error::NotEnabled("attestations".to_string())); - }; - - let start_epoch = if let Some(highest_filled_slot) = highest_filled_slot_opt { - if highest_filled_slot % self.slots_per_epoch == self.slots_per_epoch.saturating_sub(1) - { - // The whole epoch is filled so we can begin syncing the next one. - highest_filled_slot.epoch(self.slots_per_epoch) + 1 - } else { - // The epoch is only partially synced. Try to sync it fully. - highest_filled_slot.epoch(self.slots_per_epoch) - } - } else { - // No rows present in the `suboptimal_attestations` table. Use `canonical_slots` - // instead. - if let Some(lowest_canonical_slot) = database::get_lowest_canonical_slot(&mut conn)? { - lowest_canonical_slot - .slot - .as_slot() - .epoch(self.slots_per_epoch) - } else { - // There are no slots in the database, do not fill the `suboptimal_attestations` - // table. - warn!("Refusing to fill the `suboptimal_attestations` table as there are no slots in the database"); - return Ok(()); - } - }; - - if let Some(highest_canonical_slot) = - database::get_highest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - let mut end_epoch = highest_canonical_slot.epoch(self.slots_per_epoch); - - // The `lighthouse/analysis/attestation_performance` endpoint can only retrieve attestations - // which are more than 1 epoch old. - // We assume that `highest_canonical_slot` is near the head of the chain. - end_epoch = end_epoch.saturating_sub(2_u64); - - // If end_epoch == 0 then the chain just started so we need to wait until - // `current_epoch >= 2`. - if end_epoch == 0 { - debug!("Chain just begun, refusing to sync attestations"); - return Ok(()); - } - - if start_epoch > end_epoch { - debug!("Attestations are up to date with the head of the database"); - return Ok(()); - } - - // Ensure the size of the request does not exceed the maximum allowed value. - if start_epoch < end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS) { - end_epoch = start_epoch + MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS - } - - if let Some(lowest_canonical_slot) = - database::get_lowest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - let mut attestations = get_attestation_performances( - &self.bn, - start_epoch, - end_epoch, - self.slots_per_epoch, - ) - .await?; - - // Only insert attestations with corresponding `canonical_slot`s. - attestations.retain(|attestation| { - attestation.epoch_start_slot.as_slot() >= lowest_canonical_slot - && attestation.epoch_start_slot.as_slot() <= highest_canonical_slot - }); - database::insert_batch_suboptimal_attestations(&mut conn, attestations)?; - } else { - return Err(Error::Database(DbError::Other( - "Database did not return a lowest canonical slot when one exists".to_string(), - ))); - } - } else { - // There are no slots in the `canonical_slots` table, but there are entries in the - // `suboptimal_attestations` table. This is a critical failure. It usually means - // someone has manually tampered with the database tables and should not occur during - // normal operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } - - /// Backfill the `suboptimal_attestations` table starting from the entry with the lowest slot. - /// - /// It constructs a request to the `attestation_performance` API endpoint with: - /// `start_epoch` -> epoch of the lowest `canonical_slot`. - /// `end_epoch` -> epoch of the lowest filled `suboptimal_attestation` - 1 (or epoch of highest - /// canonical slot) - /// - /// It will resync the lowest epoch if it is not fully filled. - /// That is, `if lowest_filled_slot % slots_per_epoch != 0` - /// - /// In the event there are no suboptimal attestations present in the lowest epoch, it will attempt to - /// resync the epoch. The odds of this occuring on mainnet are vanishingly small so it is not - /// accounted for. - /// - /// Request range will not exceed `MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS`. - pub async fn backfill_suboptimal_attestations(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let max_attestation_backfill = self.config.max_backfill_size_epochs; - - // Get the slot of the lowest entry in the `suboptimal_attestations` table. - let lowest_filled_slot_opt = if self.config.attestations { - database::get_lowest_attestation(&mut conn)? - .map(|attestation| attestation.epoch_start_slot.as_slot()) - } else { - return Err(Error::NotEnabled("attestations".to_string())); - }; - - let end_epoch = if let Some(lowest_filled_slot) = lowest_filled_slot_opt { - if lowest_filled_slot % self.slots_per_epoch == 0 { - lowest_filled_slot - .epoch(self.slots_per_epoch) - .saturating_sub(1_u64) - } else { - // The epoch is only partially synced. Try to sync it fully. - lowest_filled_slot.epoch(self.slots_per_epoch) - } - } else { - // No entries in the `suboptimal_attestations` table. Use `canonical_slots` instead. - if let Some(highest_canonical_slot) = - database::get_highest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - // Subtract 2 since `end_epoch` must be less than the current epoch - 1. - // We assume that `highest_canonical_slot` is near the head of the chain. - highest_canonical_slot - .epoch(self.slots_per_epoch) - .saturating_sub(2_u64) - } else { - // There are no slots in the database, do not backfill the - // `suboptimal_attestations` table. - warn!("Refusing to backfill attestations as there are no slots in the database"); - return Ok(()); - } - }; - - if end_epoch == 0 { - debug!("Attestations backfill is complete"); - return Ok(()); - } - - if let Some(lowest_canonical_slot) = - database::get_lowest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - let mut start_epoch = lowest_canonical_slot.epoch(self.slots_per_epoch); - - if start_epoch > end_epoch { - debug!("Attestations are up to date with the base of the database"); - return Ok(()); - } - - // Ensure the request range does not exceed `max_attestation_backfill` or - // `MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS`. - if start_epoch < end_epoch.saturating_sub(max_attestation_backfill) { - start_epoch = end_epoch.saturating_sub(max_attestation_backfill) - } - if start_epoch < end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS) { - start_epoch = end_epoch.saturating_sub(MAX_SIZE_SINGLE_REQUEST_ATTESTATIONS) - } - - if let Some(highest_canonical_slot) = - database::get_highest_canonical_slot(&mut conn)?.map(|slot| slot.slot.as_slot()) - { - let mut attestations = get_attestation_performances( - &self.bn, - start_epoch, - end_epoch, - self.slots_per_epoch, - ) - .await?; - - // Only insert `suboptimal_attestations` with corresponding `canonical_slots`. - attestations.retain(|attestation| { - attestation.epoch_start_slot.as_slot() >= lowest_canonical_slot - && attestation.epoch_start_slot.as_slot() <= highest_canonical_slot - }); - - database::insert_batch_suboptimal_attestations(&mut conn, attestations)?; - } else { - return Err(Error::Database(DbError::Other( - "Database did not return a lowest slot when one exists".to_string(), - ))); - } - } else { - // There are no slots in the `canonical_slot` table, but there are entries in the - // `suboptimal_attestations` table. This is a critical failure. It usually means - // someone has manually tampered with the database tables and should not occur during - // normal operation. - error!("Database is corrupted. Please re-sync the database"); - return Err(Error::Database(DbError::DatabaseCorrupted)); - } - - Ok(()) - } -} diff --git a/watch/src/updater/config.rs b/watch/src/updater/config.rs deleted file mode 100644 index 0179be73db..0000000000 --- a/watch/src/updater/config.rs +++ /dev/null @@ -1,65 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub const BEACON_NODE_URL: &str = "http://127.0.0.1:5052"; - -pub const fn max_backfill_size_epochs() -> u64 { - 2 -} -pub const fn backfill_stop_epoch() -> u64 { - 0 -} -pub const fn attestations() -> bool { - true -} -pub const fn proposer_info() -> bool { - true -} -pub const fn block_rewards() -> bool { - true -} -pub const fn block_packing() -> bool { - true -} - -fn beacon_node_url() -> String { - BEACON_NODE_URL.to_string() -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Config { - /// The URL of the beacon you wish to sync from. - #[serde(default = "beacon_node_url")] - pub beacon_node_url: String, - /// The maximum size each backfill iteration will allow per request (in epochs). - #[serde(default = "max_backfill_size_epochs")] - pub max_backfill_size_epochs: u64, - /// The epoch at which to never backfill past. - #[serde(default = "backfill_stop_epoch")] - pub backfill_stop_epoch: u64, - /// Whether to sync the suboptimal_attestations table. - #[serde(default = "attestations")] - pub attestations: bool, - /// Whether to sync the proposer_info table. - #[serde(default = "proposer_info")] - pub proposer_info: bool, - /// Whether to sync the block_rewards table. - #[serde(default = "block_rewards")] - pub block_rewards: bool, - /// Whether to sync the block_packing table. - #[serde(default = "block_packing")] - pub block_packing: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { - beacon_node_url: beacon_node_url(), - max_backfill_size_epochs: max_backfill_size_epochs(), - backfill_stop_epoch: backfill_stop_epoch(), - attestations: attestations(), - proposer_info: proposer_info(), - block_rewards: block_rewards(), - block_packing: block_packing(), - } - } -} diff --git a/watch/src/updater/error.rs b/watch/src/updater/error.rs deleted file mode 100644 index 13c83bcf01..0000000000 --- a/watch/src/updater/error.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::blockprint::Error as BlockprintError; -use crate::database::Error as DbError; -use beacon_node::beacon_chain::BeaconChainError; -use eth2::{Error as Eth2Error, SensitiveError}; -use std::fmt; - -#[derive(Debug)] -#[allow(dead_code)] -pub enum Error { - BeaconChain(BeaconChainError), - Eth2(Eth2Error), - SensitiveUrl(SensitiveError), - Database(DbError), - Blockprint(BlockprintError), - UnableToGetRemoteHead, - BeaconNodeSyncing, - NotEnabled(String), - NoValidatorsFound, - BeaconNodeNotCompatible(String), - InvalidConfig(String), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl From for Error { - fn from(e: BeaconChainError) -> Self { - Error::BeaconChain(e) - } -} - -impl From for Error { - fn from(e: Eth2Error) -> Self { - Error::Eth2(e) - } -} - -impl From for Error { - fn from(e: SensitiveError) -> Self { - Error::SensitiveUrl(e) - } -} - -impl From for Error { - fn from(e: DbError) -> Self { - Error::Database(e) - } -} - -impl From for Error { - fn from(e: BlockprintError) -> Self { - Error::Blockprint(e) - } -} diff --git a/watch/src/updater/handler.rs b/watch/src/updater/handler.rs deleted file mode 100644 index 8f5e3f8e4a..0000000000 --- a/watch/src/updater/handler.rs +++ /dev/null @@ -1,471 +0,0 @@ -use crate::blockprint::WatchBlockprintClient; -use crate::config::Config as FullConfig; -use crate::database::{self, PgPool, WatchCanonicalSlot, WatchHash, WatchSlot}; -use crate::updater::{Config, Error, WatchSpec}; -use beacon_node::beacon_chain::BeaconChainError; -use eth2::{ - types::{BlockId, SyncingData}, - BeaconNodeHttpClient, SensitiveUrl, -}; -use log::{debug, error, info, warn}; -use std::collections::HashSet; -use std::marker::PhantomData; -use types::{BeaconBlockHeader, EthSpec, Hash256, SignedBeaconBlock, Slot}; - -use crate::updater::{get_beacon_block, get_header, get_validators}; - -const MAX_EXPECTED_REORG_LENGTH: u64 = 32; - -/// Ensure the existing database is valid for this run. -pub async fn ensure_valid_database( - spec: &WatchSpec, - pool: &mut PgPool, -) -> Result<(), Error> { - let mut conn = database::get_connection(pool)?; - - let bn_slots_per_epoch = spec.slots_per_epoch(); - let bn_config_name = spec.network.clone(); - - if let Some((db_config_name, db_slots_per_epoch)) = database::get_active_config(&mut conn)? { - if db_config_name != bn_config_name || db_slots_per_epoch != bn_slots_per_epoch as i32 { - Err(Error::InvalidConfig( - "The config stored in the database does not match the beacon node.".to_string(), - )) - } else { - // Configs match. - Ok(()) - } - } else { - // No config exists in the DB. - database::insert_active_config(&mut conn, bn_config_name, bn_slots_per_epoch)?; - Ok(()) - } -} - -pub struct UpdateHandler { - pub pool: PgPool, - pub bn: BeaconNodeHttpClient, - pub blockprint: Option, - pub config: Config, - pub slots_per_epoch: u64, - pub _phantom: PhantomData, -} - -impl UpdateHandler { - pub async fn new( - bn: BeaconNodeHttpClient, - spec: WatchSpec, - config: FullConfig, - ) -> Result, Error> { - let blockprint = if config.blockprint.enabled { - if let Some(server) = config.blockprint.url { - let blockprint_url = SensitiveUrl::parse(&server).map_err(Error::SensitiveUrl)?; - Some(WatchBlockprintClient { - client: reqwest::Client::new(), - server: blockprint_url, - username: config.blockprint.username, - password: config.blockprint.password, - }) - } else { - return Err(Error::NotEnabled( - "blockprint was enabled but url was not set".to_string(), - )); - } - } else { - None - }; - - let mut pool = database::build_connection_pool(&config.database)?; - - ensure_valid_database(&spec, &mut pool).await?; - - Ok(Self { - pool, - bn, - blockprint, - config: config.updater, - slots_per_epoch: spec.slots_per_epoch(), - _phantom: PhantomData, - }) - } - - /// Gets the syncing status of the connected beacon node. - pub async fn get_bn_syncing_status(&mut self) -> Result { - Ok(self.bn.get_node_syncing().await?.data) - } - - /// Gets a list of block roots from the database which do not yet contain a corresponding - /// entry in the `beacon_blocks` table and inserts them. - pub async fn update_unknown_blocks(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let roots = database::get_unknown_canonical_blocks(&mut conn)?; - for root in roots { - let block_opt: Option> = - get_beacon_block(&self.bn, BlockId::Root(root.as_hash())).await?; - if let Some(block) = block_opt { - database::insert_beacon_block(&mut conn, block, root)?; - } - } - - Ok(()) - } - - /// Performs a head update with the following steps: - /// 1. Pull the latest header from the beacon node and the latest canonical slot from the - /// database. - /// 2. Loop back through the beacon node and database to find the first matching slot -> root - /// pair. - /// 3. Go back `MAX_EXPECTED_REORG_LENGTH` slots through the database ensuring it is - /// consistent with the beacon node. If a re-org occurs beyond this range, we cannot recover. - /// 4. Remove any invalid slots from the database. - /// 5. Sync all blocks between the first valid block of the database and the head of the beacon - /// chain. - /// - /// In the event there are no slots present in the database, it will sync from the head block - /// block back to the first slot of the epoch. - /// This will ensure backfills are always done in full epochs (which helps keep certain syncing - /// tasks efficient). - pub async fn perform_head_update(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - // Load the head from the beacon node. - let bn_header = get_header(&self.bn, BlockId::Head) - .await? - .ok_or(Error::UnableToGetRemoteHead)?; - let header_root = bn_header.canonical_root(); - - if let Some(latest_matching_canonical_slot) = - self.get_first_matching_block(bn_header.clone()).await? - { - // Check for reorgs. - let latest_db_slot = self.check_for_reorg(latest_matching_canonical_slot).await?; - - // Remove all slots above `latest_db_slot` from the database. - let result = database::delete_canonical_slots_above( - &mut conn, - WatchSlot::from_slot(latest_db_slot), - )?; - info!("{result} old records removed during head update"); - - if result > 0 { - // If slots were removed, we need to resync the suboptimal_attestations table for - // the epoch since they will have changed and cannot be fixed by a simple update. - let epoch = latest_db_slot - .epoch(self.slots_per_epoch) - .saturating_sub(1_u64); - debug!("Preparing to resync attestations above epoch {epoch}"); - database::delete_suboptimal_attestations_above( - &mut conn, - WatchSlot::from_slot(epoch.start_slot(self.slots_per_epoch)), - )?; - } - - // Since we are syncing backwards, `start_slot > `end_slot`. - let start_slot = bn_header.slot; - let end_slot = latest_db_slot + 1; - self.reverse_fill_canonical_slots(bn_header, header_root, false, start_slot, end_slot) - .await?; - info!("Reverse sync begun at slot {start_slot} and stopped at slot {end_slot}"); - - // Attempt to sync new blocks with blockprint. - //self.sync_blockprint_until(start_slot).await?; - } else { - // There are no matching parent blocks. Sync from the head block back until the first - // block of the epoch. - let start_slot = bn_header.slot; - let end_slot = start_slot.saturating_sub(start_slot % self.slots_per_epoch); - self.reverse_fill_canonical_slots(bn_header, header_root, false, start_slot, end_slot) - .await?; - info!("Reverse sync begun at slot {start_slot} and stopped at slot {end_slot}"); - } - - Ok(()) - } - - /// Attempt to find a row in the `canonical_slots` table which matches the `canonical_root` of - /// the block header as reported by the beacon node. - /// - /// Any blocks above this value are not canonical according to the beacon node. - /// - /// Note: In the event that there are skip slots above the slot returned by the function, - /// they will not be returned, so may be pruned or re-synced by other code despite being - /// canonical. - pub async fn get_first_matching_block( - &mut self, - mut bn_header: BeaconBlockHeader, - ) -> Result, Error> { - let mut conn = database::get_connection(&self.pool)?; - - // Load latest non-skipped canonical slot from database. - if let Some(db_canonical_slot) = - database::get_highest_non_skipped_canonical_slot(&mut conn)? - { - // Check if the header or parent root matches the entry in the database. - if bn_header.parent_root == db_canonical_slot.root.as_hash() - || bn_header.canonical_root() == db_canonical_slot.root.as_hash() - { - Ok(Some(db_canonical_slot)) - } else { - // Header is not the child of the highest entry in the database. - // From here we need to iterate backwards through the database until we find - // a slot -> root pair that matches the beacon node. - loop { - // Store working `parent_root`. - let parent_root = bn_header.parent_root; - - // Try the next header. - let next_header = get_header(&self.bn, BlockId::Root(parent_root)).await?; - if let Some(header) = next_header { - bn_header = header.clone(); - if let Some(db_canonical_slot) = database::get_canonical_slot_by_root( - &mut conn, - WatchHash::from_hash(header.parent_root), - )? { - // Check if the entry in the database matches the parent of - // the header. - if header.parent_root == db_canonical_slot.root.as_hash() { - return Ok(Some(db_canonical_slot)); - } else { - // Move on to the next header. - continue; - } - } else { - // Database does not have the referenced root. Try the next header. - continue; - } - } else { - // If we get this error it means that the `parent_root` of the header - // did not reference a canonical block. - return Err(Error::BeaconChain(BeaconChainError::MissingBeaconBlock( - parent_root, - ))); - } - } - } - } else { - // There are no non-skipped blocks present in the database. - Ok(None) - } - } - - /// Given the latest slot in the database which matches a root in the beacon node, - /// traverse back through the database for `MAX_EXPECTED_REORG_LENGTH` slots to ensure the tip - /// of the database is consistent with the beacon node (in the case that reorgs have occured). - /// - /// Returns the slot before the oldest canonical_slot which has an invalid child. - pub async fn check_for_reorg( - &mut self, - latest_canonical_slot: WatchCanonicalSlot, - ) -> Result { - let mut conn = database::get_connection(&self.pool)?; - - let end_slot = latest_canonical_slot.slot.as_u64(); - let start_slot = end_slot.saturating_sub(MAX_EXPECTED_REORG_LENGTH); - - for i in start_slot..end_slot { - let slot = Slot::new(i); - let db_canonical_slot_opt = - database::get_canonical_slot(&mut conn, WatchSlot::from_slot(slot))?; - if let Some(db_canonical_slot) = db_canonical_slot_opt { - let header_opt = get_header(&self.bn, BlockId::Slot(slot)).await?; - if let Some(header) = header_opt { - if header.canonical_root() == db_canonical_slot.root.as_hash() { - // The roots match (or are both skip slots). - continue; - } else { - // The block roots do not match. We need to re-sync from here. - warn!("Block {slot} does not match the beacon node. Resyncing"); - return Ok(slot.saturating_sub(1_u64)); - } - } else if !db_canonical_slot.skipped { - // The block exists in the database, but does not exist on the beacon node. - // We need to re-sync from here. - warn!("Block {slot} does not exist on the beacon node. Resyncing"); - return Ok(slot.saturating_sub(1_u64)); - } - } else { - // This slot does not exist in the database. - let lowest_slot = database::get_lowest_canonical_slot(&mut conn)? - .map(|canonical_slot| canonical_slot.slot.as_slot()); - if lowest_slot > Some(slot) { - // The database has not back-filled this slot yet, so skip it. - continue; - } else { - // The database does not contain this block, but has back-filled past it. - // We need to resync from here. - warn!("Slot {slot} missing from database. Resyncing"); - return Ok(slot.saturating_sub(1_u64)); - } - } - } - - // The database is consistent with the beacon node, so return the head of the database. - Ok(latest_canonical_slot.slot.as_slot()) - } - - /// Fills the canonical slots table beginning from `start_slot` and ending at `end_slot`. - /// It fills in reverse order, that is, `start_slot` is higher than `end_slot`. - /// - /// Skip slots set `root` to the root of the previous non-skipped slot and also sets - /// `skipped == true`. - /// - /// Since it uses `insert_canonical_slot` to interact with the database, it WILL NOT overwrite - /// existing rows. This means that any part of the chain within `end_slot..=start_slot` that - /// needs to be resynced, must first be deleted from the database. - pub async fn reverse_fill_canonical_slots( - &mut self, - mut header: BeaconBlockHeader, - mut header_root: Hash256, - mut skipped: bool, - start_slot: Slot, - end_slot: Slot, - ) -> Result { - let mut count = 0; - - let mut conn = database::get_connection(&self.pool)?; - - // Iterate, descending from `start_slot` (higher) to `end_slot` (lower). - for slot in (end_slot.as_u64()..=start_slot.as_u64()).rev() { - // Insert header. - database::insert_canonical_slot( - &mut conn, - WatchCanonicalSlot { - slot: WatchSlot::new(slot), - root: WatchHash::from_hash(header_root), - skipped, - beacon_block: None, - }, - )?; - count += 1; - - // Load the next header: - // We must use BlockId::Slot since we want to include skip slots. - header = if let Some(new_header) = get_header( - &self.bn, - BlockId::Slot(Slot::new(slot.saturating_sub(1_u64))), - ) - .await? - { - header_root = new_header.canonical_root(); - skipped = false; - new_header - } else { - if header.slot == 0 { - info!("Reverse fill exhausted at slot 0"); - break; - } - // Slot was skipped, so use the parent_root (most recent non-skipped block). - skipped = true; - header_root = header.parent_root; - header - }; - } - - Ok(count) - } - - /// Backfills the `canonical_slots` table starting from the lowest non-skipped slot and - /// stopping after `max_backfill_size_epochs` epochs. - pub async fn backfill_canonical_slots(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - let backfill_stop_slot = self.config.backfill_stop_epoch * self.slots_per_epoch; - // Check to see if we have finished backfilling. - if let Some(lowest_slot) = database::get_lowest_canonical_slot(&mut conn)? { - if lowest_slot.slot.as_slot() == backfill_stop_slot { - debug!("Backfill sync complete, all slots filled"); - return Ok(()); - } - } - - let backfill_slot_count = self.config.max_backfill_size_epochs * self.slots_per_epoch; - - if let Some(lowest_non_skipped_canonical_slot) = - database::get_lowest_non_skipped_canonical_slot(&mut conn)? - { - // Set `start_slot` equal to the lowest non-skipped slot in the database. - // While this will attempt to resync some parts of the bottom of the chain, it reduces - // complexity when dealing with skip slots. - let start_slot = lowest_non_skipped_canonical_slot.slot.as_slot(); - let mut end_slot = lowest_non_skipped_canonical_slot - .slot - .as_slot() - .saturating_sub(backfill_slot_count); - - // Ensure end_slot doesn't go below `backfill_stop_epoch` - if end_slot <= backfill_stop_slot { - end_slot = Slot::new(backfill_stop_slot); - } - - let header_opt = get_header(&self.bn, BlockId::Slot(start_slot)).await?; - - if let Some(header) = header_opt { - let header_root = header.canonical_root(); - let count = self - .reverse_fill_canonical_slots(header, header_root, false, start_slot, end_slot) - .await?; - - info!("Backfill completed to slot: {end_slot}, records added: {count}"); - } else { - // The lowest slot of the database is inconsistent with the beacon node. - // Currently we have no way to recover from this. The entire database will need to - // be re-synced. - error!( - "Database is inconsistent with the beacon node. \ - Please ensure your beacon node is set to the right network, \ - otherwise you may need to resync" - ); - } - } else { - // There are no blocks in the database. Forward sync needs to happen first. - info!("Backfill was not performed since there are no blocks in the database"); - return Ok(()); - }; - - Ok(()) - } - - // Attempt to update the validator set. - // This downloads the latest validator set from the beacon node, and pulls the known validator - // set from the database. - // We then take any new or updated validators and insert them into the database (overwriting - // exiting validators). - // - // In the event there are no validators in the database, it will initialize the validator set. - pub async fn update_validator_set(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - let current_validators = database::get_all_validators(&mut conn)?; - - if !current_validators.is_empty() { - let old_validators = HashSet::from_iter(current_validators); - - // Pull the new validator set from the beacon node. - let new_validators = get_validators(&self.bn).await?; - - // The difference should only contain validators that contain either a new `exit_epoch` (implying an - // exit) or a new `index` (implying a validator activation). - let val_diff = new_validators.difference(&old_validators); - - for diff in val_diff { - database::insert_validator(&mut conn, diff.clone())?; - } - } else { - info!("No validators present in database. Initializing the validator set"); - self.initialize_validator_set().await?; - } - - Ok(()) - } - - // Initialize the validator set by downloading it from the beacon node, inserting blockprint - // data (if required) and writing it to the database. - pub async fn initialize_validator_set(&mut self) -> Result<(), Error> { - let mut conn = database::get_connection(&self.pool)?; - - // Pull all validators from the beacon node. - let validators = Vec::from_iter(get_validators(&self.bn).await?); - - database::insert_batch_validators(&mut conn, validators)?; - - Ok(()) - } -} diff --git a/watch/src/updater/mod.rs b/watch/src/updater/mod.rs deleted file mode 100644 index 65e0a90a2b..0000000000 --- a/watch/src/updater/mod.rs +++ /dev/null @@ -1,234 +0,0 @@ -use crate::config::Config as FullConfig; -use crate::database::{WatchPK, WatchValidator}; -use eth2::{ - types::{BlockId, StateId}, - BeaconNodeHttpClient, SensitiveUrl, Timeouts, -}; -use log::{debug, error, info}; -use std::collections::{HashMap, HashSet}; -use std::marker::PhantomData; -use std::time::{Duration, Instant}; -use types::{BeaconBlockHeader, EthSpec, GnosisEthSpec, MainnetEthSpec, SignedBeaconBlock}; - -pub use config::Config; -pub use error::Error; -pub use handler::UpdateHandler; - -mod config; -pub mod error; -pub mod handler; - -const FAR_FUTURE_EPOCH: u64 = u64::MAX; -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5); - -const MAINNET: &str = "mainnet"; -const GNOSIS: &str = "gnosis"; - -pub struct WatchSpec { - network: String, - spec: PhantomData, -} - -impl WatchSpec { - fn slots_per_epoch(&self) -> u64 { - E::slots_per_epoch() - } -} - -impl WatchSpec { - pub fn mainnet(network: String) -> Self { - Self { - network, - spec: PhantomData, - } - } -} - -impl WatchSpec { - fn gnosis(network: String) -> Self { - Self { - network, - spec: PhantomData, - } - } -} - -pub async fn run_updater(config: FullConfig) -> Result<(), Error> { - let beacon_node_url = - SensitiveUrl::parse(&config.updater.beacon_node_url).map_err(Error::SensitiveUrl)?; - let bn = BeaconNodeHttpClient::new(beacon_node_url, Timeouts::set_all(DEFAULT_TIMEOUT)); - - let config_map = bn.get_config_spec::>().await?.data; - - let config_name = config_map - .get("CONFIG_NAME") - .ok_or_else(|| { - Error::BeaconNodeNotCompatible("No field CONFIG_NAME on beacon node spec".to_string()) - })? - .clone(); - - match config_map - .get("PRESET_BASE") - .ok_or_else(|| { - Error::BeaconNodeNotCompatible("No field PRESET_BASE on beacon node spec".to_string()) - })? - .to_lowercase() - .as_str() - { - MAINNET => { - let spec = WatchSpec::mainnet(config_name); - run_once(bn, spec, config).await - } - GNOSIS => { - let spec = WatchSpec::gnosis(config_name); - run_once(bn, spec, config).await - } - _ => unimplemented!("unsupported PRESET_BASE"), - } -} - -pub async fn run_once( - bn: BeaconNodeHttpClient, - spec: WatchSpec, - config: FullConfig, -) -> Result<(), Error> { - let mut watch = UpdateHandler::new(bn, spec, config.clone()).await?; - - let sync_data = watch.get_bn_syncing_status().await?; - if sync_data.is_syncing { - error!( - "Connected beacon node is still syncing: head_slot => {:?}, distance => {}", - sync_data.head_slot, sync_data.sync_distance - ); - return Err(Error::BeaconNodeSyncing); - } - - info!("Performing head update"); - let head_timer = Instant::now(); - watch.perform_head_update().await?; - let head_timer_elapsed = head_timer.elapsed(); - debug!("Head update complete, time taken: {head_timer_elapsed:?}"); - - info!("Performing block backfill"); - let block_backfill_timer = Instant::now(); - watch.backfill_canonical_slots().await?; - let block_backfill_timer_elapsed = block_backfill_timer.elapsed(); - debug!("Block backfill complete, time taken: {block_backfill_timer_elapsed:?}"); - - info!("Updating validator set"); - let validator_timer = Instant::now(); - watch.update_validator_set().await?; - let validator_timer_elapsed = validator_timer.elapsed(); - debug!("Validator update complete, time taken: {validator_timer_elapsed:?}"); - - // Update blocks after updating the validator set since the `proposer_index` must exist in the - // `validators` table. - info!("Updating unknown blocks"); - let unknown_block_timer = Instant::now(); - watch.update_unknown_blocks().await?; - let unknown_block_timer_elapsed = unknown_block_timer.elapsed(); - debug!("Unknown block update complete, time taken: {unknown_block_timer_elapsed:?}"); - - // Run additional modules - if config.updater.attestations { - info!("Updating suboptimal attestations"); - let attestation_timer = Instant::now(); - watch.fill_suboptimal_attestations().await?; - watch.backfill_suboptimal_attestations().await?; - let attestation_timer_elapsed = attestation_timer.elapsed(); - debug!("Attestation update complete, time taken: {attestation_timer_elapsed:?}"); - } - - if config.updater.block_rewards { - info!("Updating block rewards"); - let rewards_timer = Instant::now(); - watch.fill_block_rewards().await?; - watch.backfill_block_rewards().await?; - let rewards_timer_elapsed = rewards_timer.elapsed(); - debug!("Block Rewards update complete, time taken: {rewards_timer_elapsed:?}"); - } - - if config.updater.block_packing { - info!("Updating block packing statistics"); - let packing_timer = Instant::now(); - watch.fill_block_packing().await?; - watch.backfill_block_packing().await?; - let packing_timer_elapsed = packing_timer.elapsed(); - debug!("Block packing update complete, time taken: {packing_timer_elapsed:?}"); - } - - if config.blockprint.enabled { - info!("Updating blockprint"); - let blockprint_timer = Instant::now(); - watch.fill_blockprint().await?; - watch.backfill_blockprint().await?; - let blockprint_timer_elapsed = blockprint_timer.elapsed(); - debug!("Blockprint update complete, time taken: {blockprint_timer_elapsed:?}"); - } - - Ok(()) -} - -/// Queries the beacon node for a given `BlockId` and returns the `BeaconBlockHeader` if it exists. -pub async fn get_header( - bn: &BeaconNodeHttpClient, - block_id: BlockId, -) -> Result, Error> { - let resp = bn - .get_beacon_headers_block_id(block_id) - .await? - .map(|resp| (resp.data.root, resp.data.header.message)); - // When quering with root == 0x000... , slot 0 will be returned with parent_root == 0x0000... - // This check escapes the loop. - if let Some((root, header)) = resp { - if root == header.parent_root { - return Ok(None); - } else { - return Ok(Some(header)); - } - } - Ok(None) -} - -pub async fn get_beacon_block( - bn: &BeaconNodeHttpClient, - block_id: BlockId, -) -> Result>, Error> { - let block = bn.get_beacon_blocks(block_id).await?.map(|resp| resp.data); - - Ok(block) -} - -/// Queries the beacon node for the current validator set. -pub async fn get_validators(bn: &BeaconNodeHttpClient) -> Result, Error> { - let mut validator_map = HashSet::new(); - - let validators = bn - .get_beacon_states_validators(StateId::Head, None, None) - .await? - .ok_or(Error::NoValidatorsFound)? - .data; - - for val in validators { - // Only store `activation_epoch` if it not the `FAR_FUTURE_EPOCH`. - let activation_epoch = if val.validator.activation_epoch.as_u64() == FAR_FUTURE_EPOCH { - None - } else { - Some(val.validator.activation_epoch.as_u64() as i32) - }; - // Only store `exit_epoch` if it is not the `FAR_FUTURE_EPOCH`. - let exit_epoch = if val.validator.exit_epoch.as_u64() == FAR_FUTURE_EPOCH { - None - } else { - Some(val.validator.exit_epoch.as_u64() as i32) - }; - validator_map.insert(WatchValidator { - index: val.index as i32, - public_key: WatchPK::from_pubkey(val.validator.pubkey), - status: val.status.to_string(), - activation_epoch, - exit_epoch, - }); - } - Ok(validator_map) -} diff --git a/watch/tests/tests.rs b/watch/tests/tests.rs deleted file mode 100644 index e21cf151b1..0000000000 --- a/watch/tests/tests.rs +++ /dev/null @@ -1,1294 +0,0 @@ -#![recursion_limit = "256"] -#![cfg(unix)] - -use beacon_chain::{ - test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType}, - ChainConfig, -}; -use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; -use http_api::test_utils::{create_api_server, ApiServer}; -use log::error; -use logging::test_logger; -use network::NetworkReceivers; -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; -use std::collections::HashMap; -use std::env; -use std::time::Duration; -use testcontainers::{clients::Cli, core::WaitFor, Image, RunnableImage}; -use tokio::{runtime, task::JoinHandle}; -use tokio_postgres::{config::Config as PostgresConfig, Client, NoTls}; -use types::{Hash256, MainnetEthSpec, Slot}; -use unused_port::unused_tcp4_port; -use url::Url; -use watch::{ - client::WatchHttpClient, - config::Config, - database::{self, Config as DatabaseConfig, PgPool, WatchSlot}, - server::{start_server, Config as ServerConfig}, - updater::{handler::*, run_updater, Config as UpdaterConfig, WatchSpec}, -}; - -#[derive(Debug)] -pub struct Postgres(HashMap); - -impl Default for Postgres { - fn default() -> Self { - let mut env_vars = HashMap::new(); - env_vars.insert("POSTGRES_DB".to_owned(), "postgres".to_owned()); - env_vars.insert("POSTGRES_HOST_AUTH_METHOD".into(), "trust".into()); - - Self(env_vars) - } -} - -impl Image for Postgres { - type Args = (); - - fn name(&self) -> String { - "postgres".to_owned() - } - - fn tag(&self) -> String { - "11-alpine".to_owned() - } - - fn ready_conditions(&self) -> Vec { - vec![WaitFor::message_on_stderr( - "database system is ready to accept connections", - )] - } - - fn env_vars(&self) -> Box + '_> { - Box::new(self.0.iter()) - } -} - -type E = MainnetEthSpec; - -const VALIDATOR_COUNT: usize = 32; -const SLOTS_PER_EPOCH: u64 = 32; -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5); - -/// Set this environment variable to use a different hostname for connecting to -/// the database. Can be set to `host.docker.internal` for docker-in-docker -/// setups. -const WATCH_HOST_ENV_VARIABLE: &str = "WATCH_HOST"; - -fn build_test_config(config: &DatabaseConfig) -> PostgresConfig { - let mut postgres_config = PostgresConfig::new(); - postgres_config - .user(&config.user) - .password(&config.password) - .dbname(&config.default_dbname) - .host(&config.host) - .port(config.port) - .connect_timeout(Duration::from_millis(config.connect_timeout_millis)); - postgres_config -} - -async fn connect(config: &DatabaseConfig) -> (Client, JoinHandle<()>) { - let db_config = build_test_config(config); - let (client, conn) = db_config - .connect(NoTls) - .await - .expect("Could not connect to db"); - let connection = runtime::Handle::current().spawn(async move { - if let Err(e) = conn.await { - error!("Connection error {:?}", e); - } - }); - - (client, connection) -} - -pub async fn create_test_database(config: &DatabaseConfig) { - let (db, _) = connect(config).await; - - db.execute(&format!("CREATE DATABASE {};", config.dbname), &[]) - .await - .expect("Database creation failed"); -} - -pub fn get_host_from_env() -> String { - env::var(WATCH_HOST_ENV_VARIABLE).unwrap_or_else(|_| "localhost".to_string()) -} - -struct TesterBuilder { - pub harness: BeaconChainHarness>, - pub config: Config, - _bn_network_rx: NetworkReceivers, -} - -impl TesterBuilder { - pub async fn new() -> TesterBuilder { - let harness = BeaconChainHarness::builder(E::default()) - .default_spec() - .chain_config(ChainConfig { - reconstruct_historic_states: true, - ..ChainConfig::default() - }) - .logger(test_logger()) - .deterministic_keypairs(VALIDATOR_COUNT) - .fresh_ephemeral_store() - .build(); - - /* - * Spawn a Beacon Node HTTP API. - */ - let ApiServer { - server, - listening_socket: bn_api_listening_socket, - network_rx: _bn_network_rx, - .. - } = create_api_server( - harness.chain.clone(), - &harness.runtime, - harness.logger().clone(), - ) - .await; - tokio::spawn(server); - - /* - * Create a watch configuration - */ - let database_port = unused_tcp4_port().expect("Unable to find unused port."); - let server_port = 0; - let config = Config { - database: DatabaseConfig { - dbname: random_dbname(), - port: database_port, - host: get_host_from_env(), - ..Default::default() - }, - server: ServerConfig { - listen_port: server_port, - ..Default::default() - }, - updater: UpdaterConfig { - beacon_node_url: format!( - "http://{}:{}", - bn_api_listening_socket.ip(), - bn_api_listening_socket.port() - ), - ..Default::default() - }, - ..Default::default() - }; - - Self { - harness, - config, - _bn_network_rx, - } - } - pub async fn build(self, pool: PgPool) -> Tester { - /* - * Spawn a Watch HTTP API. - */ - let (addr, watch_server) = start_server(&self.config, SLOTS_PER_EPOCH, pool).unwrap(); - tokio::spawn(watch_server); - - /* - * Create a HTTP client to talk to the watch HTTP API. - */ - let client = WatchHttpClient { - client: reqwest::Client::new(), - server: Url::parse(&format!("http://{}:{}", addr.ip(), addr.port())).unwrap(), - }; - - /* - * Create a HTTP client to talk to the Beacon Node API. - */ - let beacon_node_url = SensitiveUrl::parse(&self.config.updater.beacon_node_url).unwrap(); - let bn = BeaconNodeHttpClient::new(beacon_node_url, Timeouts::set_all(DEFAULT_TIMEOUT)); - let spec = WatchSpec::mainnet("mainnet".to_string()); - - /* - * Build update service - */ - let updater = UpdateHandler::new(bn, spec, self.config.clone()) - .await - .unwrap(); - - Tester { - harness: self.harness, - client, - config: self.config, - updater, - _bn_network_rx: self._bn_network_rx, - } - } - async fn initialize_database(&self) -> PgPool { - create_test_database(&self.config.database).await; - database::utils::run_migrations(&self.config.database); - database::build_connection_pool(&self.config.database) - .expect("Could not build connection pool") - } -} - -struct Tester { - pub harness: BeaconChainHarness>, - pub client: WatchHttpClient, - pub config: Config, - pub updater: UpdateHandler, - _bn_network_rx: NetworkReceivers, -} - -impl Tester { - /// Extend the chain on the beacon chain harness. Do not update the beacon watch database. - pub async fn extend_chain(&mut self, num_blocks: u64) -> &mut Self { - self.harness.advance_slot(); - self.harness - .extend_chain( - num_blocks as usize, - BlockStrategy::OnCanonicalHead, - AttestationStrategy::AllValidators, - ) - .await; - self - } - - // Advance the slot clock without a block. This results in a skipped slot. - pub fn skip_slot(&mut self) -> &mut Self { - self.harness.advance_slot(); - self - } - - // Perform a single slot re-org. - pub async fn reorg_chain(&mut self) -> &mut Self { - let previous_slot = self.harness.get_current_slot(); - self.harness.advance_slot(); - let first_slot = self.harness.get_current_slot(); - self.harness - .extend_chain( - 1, - BlockStrategy::ForkCanonicalChainAt { - previous_slot, - first_slot, - }, - AttestationStrategy::AllValidators, - ) - .await; - self - } - - /// Run the watch updater service. - pub async fn run_update_service(&mut self, num_runs: usize) -> &mut Self { - for _ in 0..num_runs { - run_updater(self.config.clone()).await.unwrap(); - } - self - } - - pub async fn perform_head_update(&mut self) -> &mut Self { - self.updater.perform_head_update().await.unwrap(); - self - } - - pub async fn perform_backfill(&mut self) -> &mut Self { - self.updater.backfill_canonical_slots().await.unwrap(); - self - } - - pub async fn update_unknown_blocks(&mut self) -> &mut Self { - self.updater.update_unknown_blocks().await.unwrap(); - self - } - - pub async fn update_validator_set(&mut self) -> &mut Self { - self.updater.update_validator_set().await.unwrap(); - self - } - - pub async fn fill_suboptimal_attestations(&mut self) -> &mut Self { - self.updater.fill_suboptimal_attestations().await.unwrap(); - - self - } - - pub async fn backfill_suboptimal_attestations(&mut self) -> &mut Self { - self.updater - .backfill_suboptimal_attestations() - .await - .unwrap(); - - self - } - - pub async fn fill_block_rewards(&mut self) -> &mut Self { - self.updater.fill_block_rewards().await.unwrap(); - - self - } - - pub async fn backfill_block_rewards(&mut self) -> &mut Self { - self.updater.backfill_block_rewards().await.unwrap(); - - self - } - - pub async fn fill_block_packing(&mut self) -> &mut Self { - self.updater.fill_block_packing().await.unwrap(); - - self - } - - pub async fn backfill_block_packing(&mut self) -> &mut Self { - self.updater.backfill_block_packing().await.unwrap(); - - self - } - - pub async fn assert_canonical_slots_empty(&mut self) -> &mut Self { - let lowest_slot = self - .client - .get_lowest_canonical_slot() - .await - .unwrap() - .map(|slot| slot.slot.as_slot()); - - assert_eq!(lowest_slot, None); - - self - } - - pub async fn assert_lowest_canonical_slot(&mut self, expected: u64) -> &mut Self { - let slot = self - .client - .get_lowest_canonical_slot() - .await - .unwrap() - .unwrap() - .slot - .as_slot(); - - assert_eq!(slot, Slot::new(expected)); - - self - } - - pub async fn assert_highest_canonical_slot(&mut self, expected: u64) -> &mut Self { - let slot = self - .client - .get_highest_canonical_slot() - .await - .unwrap() - .unwrap() - .slot - .as_slot(); - - assert_eq!(slot, Slot::new(expected)); - - self - } - - pub async fn assert_canonical_slots_not_empty(&mut self) -> &mut Self { - self.client - .get_lowest_canonical_slot() - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_slot_is_skipped(&mut self, slot: u64) -> &mut Self { - assert!(self - .client - .get_beacon_blocks(BlockId::Slot(Slot::new(slot))) - .await - .unwrap() - .is_none()); - self - } - - pub async fn assert_all_validators_exist(&mut self) -> &mut Self { - assert_eq!( - self.client - .get_all_validators() - .await - .unwrap() - .unwrap() - .len(), - VALIDATOR_COUNT - ); - self - } - - pub async fn assert_lowest_block_has_proposer_info(&mut self) -> &mut Self { - let mut block = self - .client - .get_lowest_beacon_block() - .await - .unwrap() - .unwrap(); - - if block.slot.as_slot() == 0 { - block = self - .client - .get_next_beacon_block(block.root.as_hash()) - .await - .unwrap() - .unwrap() - } - - self.client - .get_proposer_info(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_highest_block_has_proposer_info(&mut self) -> &mut Self { - let block = self - .client - .get_highest_beacon_block() - .await - .unwrap() - .unwrap(); - - self.client - .get_proposer_info(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_lowest_block_has_block_rewards(&mut self) -> &mut Self { - let mut block = self - .client - .get_lowest_beacon_block() - .await - .unwrap() - .unwrap(); - - if block.slot.as_slot() == 0 { - block = self - .client - .get_next_beacon_block(block.root.as_hash()) - .await - .unwrap() - .unwrap() - } - - self.client - .get_block_reward(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_highest_block_has_block_rewards(&mut self) -> &mut Self { - let block = self - .client - .get_highest_beacon_block() - .await - .unwrap() - .unwrap(); - - self.client - .get_block_reward(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_lowest_block_has_block_packing(&mut self) -> &mut Self { - let mut block = self - .client - .get_lowest_beacon_block() - .await - .unwrap() - .unwrap(); - - while block.slot.as_slot() <= SLOTS_PER_EPOCH { - block = self - .client - .get_next_beacon_block(block.root.as_hash()) - .await - .unwrap() - .unwrap() - } - - self.client - .get_block_packing(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - pub async fn assert_highest_block_has_block_packing(&mut self) -> &mut Self { - let block = self - .client - .get_highest_beacon_block() - .await - .unwrap() - .unwrap(); - - self.client - .get_block_packing(BlockId::Root(block.root.as_hash())) - .await - .unwrap() - .unwrap(); - - self - } - - /// Check that the canonical chain in watch matches that of the harness. Also check that all - /// canonical blocks can be retrieved. - pub async fn assert_canonical_chain_consistent(&mut self, last_slot: u64) -> &mut Self { - let head_root = self.harness.chain.head_beacon_block_root(); - let mut chain: Vec<(Hash256, Slot)> = self - .harness - .chain - .rev_iter_block_roots_from(head_root) - .unwrap() - .map(Result::unwrap) - .collect(); - - // `chain` contains skip slots, but the `watch` API will not return blocks that do not - // exist. - // We need to filter them out. - chain.reverse(); - chain.dedup_by(|(hash1, _), (hash2, _)| hash1 == hash2); - - // Remove any slots below `last_slot` since it is known that the database has not - // backfilled past it. - chain.retain(|(_, slot)| slot.as_u64() >= last_slot); - - for (root, slot) in &chain { - let block = self - .client - .get_beacon_blocks(BlockId::Root(*root)) - .await - .unwrap() - .unwrap(); - assert_eq!(block.slot.as_slot(), *slot); - } - - self - } - - /// Check that every block in the `beacon_blocks` table has corresponding entries in the - /// `proposer_info`, `block_rewards` and `block_packing` tables. - pub async fn assert_all_blocks_have_metadata(&mut self) -> &mut Self { - let pool = database::build_connection_pool(&self.config.database).unwrap(); - - let mut conn = database::get_connection(&pool).unwrap(); - let highest_block_slot = database::get_highest_beacon_block(&mut conn) - .unwrap() - .unwrap() - .slot - .as_slot(); - let lowest_block_slot = database::get_lowest_beacon_block(&mut conn) - .unwrap() - .unwrap() - .slot - .as_slot(); - for slot in lowest_block_slot.as_u64()..=highest_block_slot.as_u64() { - let canonical_slot = database::get_canonical_slot(&mut conn, WatchSlot::new(slot)) - .unwrap() - .unwrap(); - if !canonical_slot.skipped { - database::get_block_rewards_by_slot(&mut conn, WatchSlot::new(slot)) - .unwrap() - .unwrap(); - database::get_proposer_info_by_slot(&mut conn, WatchSlot::new(slot)) - .unwrap() - .unwrap(); - database::get_block_packing_by_slot(&mut conn, WatchSlot::new(slot)) - .unwrap() - .unwrap(); - } - } - - self - } -} - -pub fn random_dbname() -> String { - let mut s: String = thread_rng() - .sample_iter(&Alphanumeric) - .take(8) - .map(char::from) - .collect(); - // Postgres gets weird about capitals in database names. - s.make_ascii_lowercase(); - format!("test_{}", s) -} - -#[cfg(unix)] -#[tokio::test] -async fn short_chain() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - .extend_chain(16) - .await - .assert_canonical_slots_empty() - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_canonical_slots_not_empty() - .await - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn short_chain_sync_starts_on_skip_slot() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - .skip_slot() - .skip_slot() - .extend_chain(6) - .await - .skip_slot() - .extend_chain(6) - .await - .skip_slot() - .assert_canonical_slots_empty() - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_canonical_slots_not_empty() - .await - .assert_canonical_chain_consistent(0) - .await - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn short_chain_with_skip_slot() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - .extend_chain(5) - .await - .assert_canonical_slots_empty() - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_canonical_slots_not_empty() - .await - .assert_highest_canonical_slot(5) - .await - .assert_lowest_canonical_slot(0) - .await - .assert_canonical_chain_consistent(0) - .await - .skip_slot() - .extend_chain(1) - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_highest_canonical_slot(7) - .await - .assert_slot_is_skipped(6) - .await - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn short_chain_with_reorg() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - .extend_chain(5) - .await - .assert_canonical_slots_empty() - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_canonical_slots_not_empty() - .await - .assert_highest_canonical_slot(5) - .await - .assert_lowest_canonical_slot(0) - .await - .assert_canonical_chain_consistent(0) - .await - .skip_slot() - .reorg_chain() - .await - .extend_chain(1) - .await - .run_update_service(1) - .await - .assert_all_validators_exist() - .await - .assert_highest_canonical_slot(8) - .await - .assert_slot_is_skipped(6) - .await - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn chain_grows() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - // Apply four blocks to the chain. - tester - .extend_chain(4) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(4) - .await - // And also backfill to the epoch boundary. - .assert_lowest_canonical_slot(0) - .await - // Fill back to genesis. - .perform_backfill() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(4) - .await - // Apply one block to the chain. - .extend_chain(1) - .await - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(5) - .await - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(7) - .await - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -#[allow(clippy::large_stack_frames)] -async fn chain_grows_with_metadata() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - tester - // Apply four blocks to the chain. - .extend_chain(4) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(4) - .await - // And also backfill to the epoch boundary. - .assert_lowest_canonical_slot(0) - .await - // Fill back to genesis. - .perform_backfill() - .await - // Insert all validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await - // Get other chain data. - // Backfill before forward fill to ensure order is arbitrary. - .backfill_block_rewards() - .await - .fill_block_rewards() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(4) - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await - // Apply one block to the chain. - .extend_chain(1) - .await - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(5) - .await - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(7) - .await - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await - // Get other chain data. - .fill_block_rewards() - .await - .backfill_block_rewards() - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await; -} - -#[cfg(unix)] -#[tokio::test] -#[allow(clippy::large_stack_frames)] -async fn chain_grows_with_metadata_and_multiple_skip_slots() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - - // Apply four blocks to the chain. - tester - .extend_chain(4) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(4) - // And also backfill to the epoch boundary. - .await - .assert_lowest_canonical_slot(0) - .await - // Fill back to genesis. - .perform_backfill() - .await - // Insert all validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Check the chain is consistent. - .assert_canonical_chain_consistent(0) - .await - // Get other chain data. - .fill_block_rewards() - .await - .backfill_block_rewards() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(4) - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await - // Add multiple skip slots. - .skip_slot() - .skip_slot() - .skip_slot() - // Apply one block to the chain. - .extend_chain(1) - .await - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(8) - .await - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(10) - .await - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await - // Get other chain data. - // Backfill before forward fill to ensure order is arbitrary. - .backfill_block_rewards() - .await - .fill_block_rewards() - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn chain_grows_to_second_epoch() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - // Apply 40 blocks to the chain. - tester - .extend_chain(40) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(40) - .await - // And also backfill to the epoch boundary. - .assert_lowest_canonical_slot(32) - .await - // Fill back to genesis. - .perform_backfill() - .await - // Insert all validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Check the chain is consistent. - .assert_canonical_chain_consistent(0) - .await - // Get block packings. - .fill_block_packing() - .await - .backfill_block_packing() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(40) - .await - // All packings should be present. - .assert_lowest_block_has_block_packing() - .await - .assert_highest_block_has_block_packing() - .await - // Skip a slot - .skip_slot() - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - // All blocks should be present. - .assert_lowest_canonical_slot(0) - .await - .assert_highest_canonical_slot(43) - .await - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // Update new block_packing - // Backfill before forward fill to ensure order is arbitrary - .backfill_block_packing() - .await - .fill_block_packing() - .await - // All packings should be present. - .assert_lowest_block_has_block_packing() - .await - .assert_highest_block_has_block_packing() - .await - // Check the chain is consistent - .assert_canonical_chain_consistent(0) - .await; -} - -#[cfg(unix)] -#[tokio::test] -async fn large_chain() { - let builder = TesterBuilder::new().await; - - let docker = Cli::default(); - let image = RunnableImage::from(Postgres::default()) - .with_mapped_port((builder.config.database.port, 5432)); - let _node = docker.run(image); - - let pool = builder.initialize_database().await; - let mut tester = builder.build(pool).await; - // Apply 40 blocks to the chain. - tester - .extend_chain(400) - .await - .perform_head_update() - .await - // Head update should insert the head block. - .assert_highest_canonical_slot(400) - .await - // And also backfill to the epoch boundary. - .assert_lowest_canonical_slot(384) - .await - // Backfill 2 epochs as per default config. - .perform_backfill() - .await - // Insert all validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Check the chain is consistent. - .assert_canonical_chain_consistent(384) - .await - // Get block rewards and proposer info. - .fill_block_rewards() - .await - .backfill_block_rewards() - .await - // Get block packings. - .fill_block_packing() - .await - .backfill_block_packing() - .await - // Should have backfilled 2 more epochs. - .assert_lowest_canonical_slot(320) - .await - .assert_highest_canonical_slot(400) - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await - // All packings should be present. - .assert_lowest_block_has_block_packing() - .await - .assert_highest_block_has_block_packing() - .await - // Skip a slot - .skip_slot() - // Apply two blocks to the chain. - .extend_chain(2) - .await - // Update the head. - .perform_head_update() - .await - .perform_backfill() - .await - // Should have backfilled 2 more epochs - .assert_lowest_canonical_slot(256) - .await - .assert_highest_canonical_slot(403) - .await - // Update validators - .update_validator_set() - .await - // Insert all blocks. - .update_unknown_blocks() - .await - // All validators should be present. - .assert_all_validators_exist() - .await - // Get suboptimal attestations. - .fill_suboptimal_attestations() - .await - .backfill_suboptimal_attestations() - .await - // Get block rewards and proposer info. - .fill_block_rewards() - .await - .backfill_block_rewards() - .await - // Get block packing. - // Backfill before forward fill to ensure order is arbitrary. - .backfill_block_packing() - .await - .fill_block_packing() - .await - // All rewards should be present. - .assert_lowest_block_has_block_rewards() - .await - .assert_highest_block_has_block_rewards() - .await - // All proposers should be present. - .assert_lowest_block_has_proposer_info() - .await - .assert_highest_block_has_proposer_info() - .await - // All packings should be present. - .assert_lowest_block_has_block_packing() - .await - .assert_highest_block_has_block_packing() - .await - // Check the chain is consistent. - .assert_canonical_chain_consistent(256) - .await - // Check every block has rewards, proposer info and packing statistics. - .assert_all_blocks_have_metadata() - .await; -}