@aztec/p2p 0.0.1-commit.f2ce05ee → 0.0.1-commit.f5a9928
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -3
- package/dest/bootstrap/bootstrap.d.ts +1 -1
- package/dest/bootstrap/bootstrap.d.ts.map +1 -1
- package/dest/bootstrap/bootstrap.js +22 -6
- package/dest/client/factory.d.ts +11 -10
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +64 -17
- package/dest/client/interface.d.ts +86 -40
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +67 -64
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +251 -251
- package/dest/config.d.ts +173 -94
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +158 -44
- package/dest/errors/p2p-service.error.d.ts +9 -0
- package/dest/errors/p2p-service.error.d.ts.map +1 -0
- package/dest/errors/p2p-service.error.js +10 -0
- package/dest/errors/reqresp.error.d.ts +9 -16
- package/dest/errors/reqresp.error.d.ts.map +1 -1
- package/dest/errors/reqresp.error.js +10 -17
- package/dest/errors/tx-pool.error.d.ts +8 -0
- package/dest/errors/tx-pool.error.d.ts.map +1 -0
- package/dest/errors/tx-pool.error.js +9 -0
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +106 -55
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +302 -195
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +274 -115
- package/dest/mem_pools/attestation_pool/index.d.ts +2 -2
- package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/index.js +1 -1
- package/dest/mem_pools/attestation_pool/mocks.d.ts +2 -2
- package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/mocks.js +7 -5
- package/dest/mem_pools/index.d.ts +2 -2
- package/dest/mem_pools/index.d.ts.map +1 -1
- package/dest/mem_pools/instrumentation.d.ts +12 -5
- package/dest/mem_pools/instrumentation.d.ts.map +1 -1
- package/dest/mem_pools/instrumentation.js +38 -17
- package/dest/mem_pools/interface.d.ts +3 -3
- package/dest/mem_pools/interface.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts +10 -5
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/archive/tx_archive.js +29 -20
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +104 -0
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool_v2/deleted_pool.js +251 -0
- package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts +3 -3
- package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.js +18 -9
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +8 -4
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +3 -3
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +15 -5
- package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +3 -2
- package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/index.js +2 -1
- package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts +16 -0
- package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.js +64 -0
- package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +54 -5
- package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/interfaces.js +8 -0
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +6 -5
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +9 -8
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +2 -2
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +15 -6
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +4 -4
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +16 -4
- package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +3 -3
- package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +3 -3
- package/dest/mem_pools/tx_pool_v2/index.d.ts +3 -2
- package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/index.js +2 -1
- package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts +25 -0
- package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool_v2/instrumentation.js +67 -0
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +62 -16
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.js +6 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +81 -15
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +147 -19
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +26 -3
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +79 -43
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +27 -7
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +94 -32
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +62 -9
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +537 -246
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +10 -3
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/attestation_validator.js +38 -15
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +7 -3
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +4 -5
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +12 -5
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/block_proposal_validator.js +11 -3
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +12 -5
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +23 -3
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +36 -9
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +130 -48
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +4 -4
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +11 -18
- package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +2 -1
- package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/allowed_public_setup.js +25 -21
- package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
- package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
- package/dest/msg_validators/tx_validator/archive_cache.js +1 -1
- package/dest/msg_validators/tx_validator/block_header_validator.d.ts +16 -3
- package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/block_header_validator.js +1 -1
- package/dest/msg_validators/tx_validator/cached_tx_validator.d.ts +15 -0
- package/dest/msg_validators/tx_validator/cached_tx_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/cached_tx_validator.js +19 -0
- package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
- package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
- package/dest/msg_validators/tx_validator/data_validator.d.ts +2 -1
- package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/data_validator.js +36 -2
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +13 -3
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/double_spend_validator.js +4 -4
- package/dest/msg_validators/tx_validator/factory.d.ts +133 -7
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +261 -61
- package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
- package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
- package/dest/msg_validators/tx_validator/gas_validator.d.ts +104 -3
- package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/gas_validator.js +137 -53
- package/dest/msg_validators/tx_validator/index.d.ts +5 -1
- package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/index.js +4 -0
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
- package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
- package/dest/msg_validators/tx_validator/phases_validator.d.ts +22 -2
- package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/phases_validator.js +72 -24
- package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +20 -4
- package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/timestamp_validator.js +6 -6
- package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts +2 -1
- package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/tx_proof_validator.js +2 -0
- package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts +48 -0
- package/dest/msg_validators/tx_validator/tx_validation_cache.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/tx_validation_cache.js +68 -0
- package/dest/services/data_store.d.ts +1 -1
- package/dest/services/data_store.d.ts.map +1 -1
- package/dest/services/data_store.js +5 -5
- package/dest/services/discv5/discV5_service.d.ts +13 -2
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +119 -23
- package/dest/services/discv5/persisted_enr_store.d.ts +38 -0
- package/dest/services/discv5/persisted_enr_store.d.ts.map +1 -0
- package/dest/services/discv5/persisted_enr_store.js +145 -0
- package/dest/services/dummy_service.d.ts +25 -19
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +28 -20
- package/dest/services/encoding.d.ts +24 -7
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +43 -26
- package/dest/services/gossipsub/topic_score_params.d.ts +23 -17
- package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
- package/dest/services/gossipsub/topic_score_params.js +36 -24
- package/dest/services/libp2p/instrumentation.d.ts +6 -1
- package/dest/services/libp2p/instrumentation.d.ts.map +1 -1
- package/dest/services/libp2p/instrumentation.js +21 -0
- package/dest/services/libp2p/libp2p_service.d.ts +71 -60
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +602 -407
- package/dest/services/peer-manager/metrics.d.ts +3 -1
- package/dest/services/peer-manager/metrics.d.ts.map +1 -1
- package/dest/services/peer-manager/metrics.js +6 -0
- package/dest/services/peer-manager/peer_manager.d.ts +31 -3
- package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_manager.js +127 -26
- package/dest/services/peer-manager/peer_scoring.d.ts +34 -2
- package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_scoring.js +102 -13
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +5 -8
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +95 -127
- package/dest/services/reqresp/batch-tx-requester/interface.d.ts +11 -11
- package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +11 -13
- package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/missing_txs.js +31 -46
- package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +19 -11
- package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/peer_collection.js +52 -15
- package/dest/services/reqresp/batch-tx-requester/tx_validator.d.ts +5 -14
- package/dest/services/reqresp/batch-tx-requester/tx_validator.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/tx_validator.js +6 -20
- package/dest/services/reqresp/config.d.ts +15 -3
- package/dest/services/reqresp/config.d.ts.map +1 -1
- package/dest/services/reqresp/config.js +11 -0
- package/dest/services/reqresp/interface.d.ts +25 -18
- package/dest/services/reqresp/interface.d.ts.map +1 -1
- package/dest/services/reqresp/interface.js +23 -19
- package/dest/services/reqresp/metrics.d.ts +1 -1
- package/dest/services/reqresp/metrics.d.ts.map +1 -1
- package/dest/services/reqresp/metrics.js +0 -1
- package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +4 -3
- package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/bitvector.js +3 -2
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +3 -3
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +31 -28
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +18 -7
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +57 -24
- package/dest/services/reqresp/protocols/index.d.ts +1 -2
- package/dest/services/reqresp/protocols/index.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/index.js +0 -1
- package/dest/services/reqresp/protocols/tx.d.ts +7 -1
- package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/tx.js +21 -3
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +5 -4
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
- package/dest/services/reqresp/rate-limiter/rate_limiter.js +10 -8
- package/dest/services/reqresp/rate-limiter/rate_limits.d.ts +1 -1
- package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -1
- package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -13
- package/dest/services/reqresp/reqresp.d.ts +7 -29
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +85 -219
- package/dest/services/service.d.ts +63 -19
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/config.d.ts +11 -14
- package/dest/services/tx_collection/config.d.ts.map +1 -1
- package/dest/services/tx_collection/config.js +26 -33
- package/dest/services/tx_collection/file_store_tx_collection.d.ts +37 -0
- package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -0
- package/dest/services/tx_collection/file_store_tx_collection.js +127 -0
- package/dest/services/tx_collection/file_store_tx_source.d.ts +38 -0
- package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -0
- package/dest/services/tx_collection/file_store_tx_source.js +100 -0
- package/dest/services/tx_collection/index.d.ts +3 -3
- package/dest/services/tx_collection/index.d.ts.map +1 -1
- package/dest/services/tx_collection/index.js +1 -1
- package/dest/services/tx_collection/instrumentation.js +1 -2
- package/dest/services/tx_collection/request_tracker.d.ts +53 -0
- package/dest/services/tx_collection/request_tracker.d.ts.map +1 -0
- package/dest/services/tx_collection/request_tracker.js +84 -0
- package/dest/services/tx_collection/tx_collection.d.ts +45 -51
- package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_collection.js +296 -68
- package/dest/services/tx_collection/tx_collection_sink.d.ts +19 -9
- package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_collection_sink.js +28 -31
- package/dest/services/tx_collection/tx_source.d.ts +13 -7
- package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_source.js +29 -7
- package/dest/services/tx_file_store/config.d.ts +1 -3
- package/dest/services/tx_file_store/config.d.ts.map +1 -1
- package/dest/services/tx_file_store/config.js +0 -4
- package/dest/services/tx_file_store/tx_file_store.d.ts +4 -5
- package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
- package/dest/services/tx_file_store/tx_file_store.js +13 -20
- package/dest/services/tx_provider.d.ts +6 -4
- package/dest/services/tx_provider.d.ts.map +1 -1
- package/dest/services/tx_provider.js +12 -8
- package/dest/test-helpers/make-test-p2p-clients.d.ts +7 -8
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/make-test-p2p-clients.js +5 -3
- package/dest/test-helpers/mock-pubsub.d.ts +56 -6
- package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.js +128 -14
- package/dest/test-helpers/reqresp-nodes.d.ts +5 -7
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.js +17 -19
- package/dest/test-helpers/test_tx_provider.d.ts +3 -1
- package/dest/test-helpers/test_tx_provider.d.ts.map +1 -1
- package/dest/test-helpers/test_tx_provider.js +3 -0
- package/dest/test-helpers/testbench-utils.d.ts +49 -37
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +142 -50
- package/dest/testbench/p2p_client_testbench_worker.d.ts +3 -5
- package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
- package/dest/testbench/p2p_client_testbench_worker.js +112 -49
- package/dest/testbench/worker_client_manager.d.ts +12 -6
- package/dest/testbench/worker_client_manager.d.ts.map +1 -1
- package/dest/testbench/worker_client_manager.js +57 -11
- package/dest/util.d.ts +13 -8
- package/dest/util.d.ts.map +1 -1
- package/dest/util.js +35 -14
- package/dest/versioning.d.ts +3 -6
- package/dest/versioning.d.ts.map +1 -1
- package/dest/versioning.js +3 -24
- package/package.json +17 -17
- package/src/bootstrap/bootstrap.ts +17 -3
- package/src/client/factory.ts +129 -28
- package/src/client/interface.ts +88 -48
- package/src/client/p2p_client.ts +284 -317
- package/src/client/test/{tx_proposal_collector/README.md → p2p_client.batch_tx_requester.bench.README.md} +23 -53
- package/src/config.ts +280 -49
- package/src/errors/p2p-service.error.ts +11 -0
- package/src/errors/reqresp.error.ts +13 -19
- package/src/errors/tx-pool.error.ts +12 -0
- package/src/index.ts +1 -1
- package/src/mem_pools/attestation_pool/attestation_pool.ts +373 -236
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +298 -119
- package/src/mem_pools/attestation_pool/index.ts +3 -3
- package/src/mem_pools/attestation_pool/mocks.ts +14 -8
- package/src/mem_pools/index.ts +1 -1
- package/src/mem_pools/instrumentation.ts +27 -17
- package/src/mem_pools/interface.ts +2 -2
- package/src/mem_pools/tx_pool_v2/README.md +85 -11
- package/src/mem_pools/tx_pool_v2/archive/tx_archive.ts +30 -20
- package/src/mem_pools/tx_pool_v2/deleted_pool.ts +321 -0
- package/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts +21 -8
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +6 -4
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +21 -6
- package/src/mem_pools/tx_pool_v2/eviction/index.ts +5 -0
- package/src/mem_pools/tx_pool_v2/eviction/insufficient_fee_per_gas_eviction_rule.ts +67 -0
- package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +59 -4
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +4 -6
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +8 -10
- package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +14 -10
- package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +33 -6
- package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +4 -3
- package/src/mem_pools/tx_pool_v2/index.ts +2 -1
- package/src/mem_pools/tx_pool_v2/instrumentation.ts +96 -0
- package/src/mem_pools/tx_pool_v2/interfaces.ts +63 -16
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +215 -27
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +91 -43
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +122 -33
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +602 -242
- package/src/msg_validators/attestation_validator/README.md +49 -0
- package/src/msg_validators/attestation_validator/attestation_validator.ts +46 -14
- package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +15 -7
- package/src/msg_validators/proposal_validator/README.md +123 -0
- package/src/msg_validators/proposal_validator/block_proposal_validator.ts +26 -4
- package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +43 -7
- package/src/msg_validators/proposal_validator/proposal_validator.ts +155 -49
- package/src/msg_validators/tx_validator/README.md +129 -0
- package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +8 -17
- package/src/msg_validators/tx_validator/allowed_public_setup.ts +22 -27
- package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
- package/src/msg_validators/tx_validator/archive_cache.ts +1 -1
- package/src/msg_validators/tx_validator/block_header_validator.ts +15 -3
- package/src/msg_validators/tx_validator/cached_tx_validator.ts +31 -0
- package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
- package/src/msg_validators/tx_validator/data_validator.ts +44 -1
- package/src/msg_validators/tx_validator/double_spend_validator.ts +11 -6
- package/src/msg_validators/tx_validator/factory.ts +413 -80
- package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
- package/src/msg_validators/tx_validator/gas_validator.ts +195 -54
- package/src/msg_validators/tx_validator/index.ts +4 -0
- package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
- package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
- package/src/msg_validators/tx_validator/phases_validator.ts +82 -27
- package/src/msg_validators/tx_validator/timestamp_validator.ts +23 -18
- package/src/msg_validators/tx_validator/tx_proof_validator.ts +2 -0
- package/src/msg_validators/tx_validator/tx_validation_cache.ts +101 -0
- package/src/services/data_store.ts +5 -13
- package/src/services/discv5/discV5_service.ts +126 -14
- package/src/services/discv5/persisted_enr_store.ts +158 -0
- package/src/services/dummy_service.ts +38 -46
- package/src/services/encoding.ts +42 -26
- package/src/services/gossipsub/README.md +59 -22
- package/src/services/gossipsub/topic_score_params.ts +58 -31
- package/src/services/libp2p/instrumentation.ts +22 -0
- package/src/services/libp2p/libp2p_service.ts +687 -466
- package/src/services/peer-manager/metrics.ts +7 -0
- package/src/services/peer-manager/peer_manager.ts +140 -25
- package/src/services/peer-manager/peer_scoring.ts +102 -8
- package/src/services/reqresp/README.md +194 -0
- package/src/services/reqresp/batch-tx-requester/README.md +46 -7
- package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +92 -138
- package/src/services/reqresp/batch-tx-requester/interface.ts +14 -10
- package/src/services/reqresp/batch-tx-requester/missing_txs.ts +30 -71
- package/src/services/reqresp/batch-tx-requester/peer_collection.ts +68 -24
- package/src/services/reqresp/batch-tx-requester/tx_validator.ts +12 -25
- package/src/services/reqresp/config.ts +19 -2
- package/src/services/reqresp/interface.ts +45 -46
- package/src/services/reqresp/metrics.ts +0 -1
- package/src/services/reqresp/protocols/block_txs/bitvector.ts +3 -2
- package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +36 -32
- package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +64 -20
- package/src/services/reqresp/protocols/index.ts +0 -1
- package/src/services/reqresp/protocols/tx.ts +23 -3
- package/src/services/reqresp/rate-limiter/rate_limiter.ts +13 -9
- package/src/services/reqresp/rate-limiter/rate_limits.ts +3 -13
- package/src/services/reqresp/reqresp.ts +100 -263
- package/src/services/service.ts +80 -35
- package/src/services/tx_collection/config.ts +40 -49
- package/src/services/tx_collection/file_store_tx_collection.ts +153 -0
- package/src/services/tx_collection/file_store_tx_source.ts +129 -0
- package/src/services/tx_collection/index.ts +2 -6
- package/src/services/tx_collection/instrumentation.ts +1 -1
- package/src/services/tx_collection/request_tracker.ts +127 -0
- package/src/services/tx_collection/tx_collection.ts +362 -110
- package/src/services/tx_collection/tx_collection_sink.ts +32 -36
- package/src/services/tx_collection/tx_source.ts +29 -8
- package/src/services/tx_file_store/config.ts +0 -6
- package/src/services/tx_file_store/tx_file_store.ts +15 -25
- package/src/services/tx_provider.ts +15 -9
- package/src/test-helpers/make-test-p2p-clients.ts +7 -6
- package/src/test-helpers/mock-pubsub.ts +152 -14
- package/src/test-helpers/reqresp-nodes.ts +15 -28
- package/src/test-helpers/test_tx_provider.ts +5 -0
- package/src/test-helpers/testbench-utils.ts +171 -68
- package/src/testbench/p2p_client_testbench_worker.ts +121 -64
- package/src/testbench/worker_client_manager.ts +72 -25
- package/src/util.ts +40 -19
- package/src/versioning.ts +3 -33
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.d.ts +0 -2
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.d.ts.map +0 -1
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +0 -305
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.d.ts +0 -73
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.d.ts.map +0 -1
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.js +0 -8
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +0 -125
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +0 -596
- package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts +0 -32
- package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/eviction/eviction_manager.js +0 -112
- package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts +0 -157
- package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/eviction/eviction_strategy.js +0 -52
- package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +0 -16
- package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +0 -122
- package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts +0 -17
- package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +0 -84
- package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts +0 -19
- package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.js +0 -78
- package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts +0 -26
- package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.js +0 -84
- package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts +0 -25
- package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.js +0 -57
- package/dest/mem_pools/tx_pool/index.d.ts +0 -3
- package/dest/mem_pools/tx_pool/index.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/index.js +0 -2
- package/dest/mem_pools/tx_pool/priority.d.ts +0 -12
- package/dest/mem_pools/tx_pool/priority.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/priority.js +0 -15
- package/dest/mem_pools/tx_pool/tx_pool.d.ts +0 -127
- package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/tx_pool.js +0 -3
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +0 -7
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +0 -1
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +0 -400
- package/dest/msg_validators/clock_tolerance.d.ts +0 -21
- package/dest/msg_validators/clock_tolerance.d.ts.map +0 -1
- package/dest/msg_validators/clock_tolerance.js +0 -37
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -23
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -212
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts +0 -64
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts.map +0 -1
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.js +0 -151
- package/dest/services/reqresp/protocols/block.d.ts +0 -9
- package/dest/services/reqresp/protocols/block.d.ts.map +0 -1
- package/dest/services/reqresp/protocols/block.js +0 -32
- package/dest/services/tx_collection/fast_tx_collection.d.ts +0 -52
- package/dest/services/tx_collection/fast_tx_collection.d.ts.map +0 -1
- package/dest/services/tx_collection/fast_tx_collection.js +0 -307
- package/dest/services/tx_collection/proposal_tx_collector.d.ts +0 -48
- package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +0 -1
- package/dest/services/tx_collection/proposal_tx_collector.js +0 -49
- package/dest/services/tx_collection/slow_tx_collection.d.ts +0 -53
- package/dest/services/tx_collection/slow_tx_collection.d.ts.map +0 -1
- package/dest/services/tx_collection/slow_tx_collection.js +0 -177
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +0 -336
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker_protocol.ts +0 -43
- package/src/mem_pools/tx_pool/README.md +0 -270
- package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +0 -746
- package/src/mem_pools/tx_pool/eviction/eviction_manager.ts +0 -132
- package/src/mem_pools/tx_pool/eviction/eviction_strategy.ts +0 -208
- package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +0 -162
- package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +0 -104
- package/src/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.ts +0 -93
- package/src/mem_pools/tx_pool/eviction/low_priority_eviction_rule.ts +0 -106
- package/src/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.ts +0 -75
- package/src/mem_pools/tx_pool/index.ts +0 -2
- package/src/mem_pools/tx_pool/priority.ts +0 -20
- package/src/mem_pools/tx_pool/tx_pool.ts +0 -141
- package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +0 -319
- package/src/msg_validators/clock_tolerance.ts +0 -51
- package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -230
- package/src/services/reqresp/connection-sampler/batch_connection_sampler.ts +0 -161
- package/src/services/reqresp/protocols/block.ts +0 -37
- package/src/services/tx_collection/fast_tx_collection.ts +0 -354
- package/src/services/tx_collection/proposal_tx_collector.ts +0 -112
- package/src/services/tx_collection/slow_tx_collection.ts +0 -233
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
2
|
import { BlockNumber, type SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
-
import {
|
|
4
|
-
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
+
import { compactArray, maxBy, merge } from '@aztec/foundation/collection';
|
|
5
4
|
import { type Logger, createLibp2pComponentLogger, createLogger } from '@aztec/foundation/log';
|
|
6
5
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
7
6
|
import { Timer } from '@aztec/foundation/timer';
|
|
8
7
|
import type { AztecAsyncKVStore } from '@aztec/kv-store';
|
|
9
8
|
import { protocolContractsHash } from '@aztec/protocol-contracts';
|
|
10
|
-
import type { EthAddress,
|
|
9
|
+
import type { EthAddress, L2BlockSource } from '@aztec/stdlib/block';
|
|
10
|
+
import { DEFAULT_MAX_BLOCKS_PER_CHECKPOINT } from '@aztec/stdlib/config';
|
|
11
11
|
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
12
|
-
import { GasFees } from '@aztec/stdlib/gas';
|
|
13
|
-
import type {
|
|
12
|
+
import { type BlockMinFeesProvider, GasFees, getNetworkTxGasLimits } from '@aztec/stdlib/gas';
|
|
13
|
+
import type {
|
|
14
|
+
ClientProtocolCircuitVerifier,
|
|
15
|
+
P2PConnectivity,
|
|
16
|
+
PeerInfo,
|
|
17
|
+
WorldStateSynchronizer,
|
|
18
|
+
} from '@aztec/stdlib/interfaces/server';
|
|
14
19
|
import {
|
|
15
20
|
BlockProposal,
|
|
16
21
|
CheckpointAttestation,
|
|
17
22
|
CheckpointProposal,
|
|
18
|
-
type CheckpointProposalCore,
|
|
19
23
|
type Gossipable,
|
|
20
|
-
P2PClientType,
|
|
21
24
|
P2PMessage,
|
|
22
|
-
type ValidationResult as P2PValidationResult,
|
|
23
25
|
PeerErrorSeverity,
|
|
26
|
+
PeerErrorSeverityByHarshness,
|
|
24
27
|
TopicType,
|
|
28
|
+
ValidatedBlockProposal,
|
|
29
|
+
ValidatedCheckpointProposalCore,
|
|
25
30
|
createTopicString,
|
|
26
|
-
|
|
31
|
+
getTopicsForConfig,
|
|
27
32
|
metricsTopicStrToLabels,
|
|
28
33
|
} from '@aztec/stdlib/p2p';
|
|
34
|
+
import { ConsensusTimetable, getDefaultCheckpointProposalSyncGrace } from '@aztec/stdlib/timetable';
|
|
29
35
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
30
|
-
import { Tx, type
|
|
36
|
+
import { Tx, type TxValidationResult } from '@aztec/stdlib/tx';
|
|
31
37
|
import type { UInt64 } from '@aztec/stdlib/types';
|
|
38
|
+
import { InvalidBlockProposalTxsError } from '@aztec/stdlib/validators';
|
|
32
39
|
import { compressComponentVersions } from '@aztec/stdlib/versioning';
|
|
33
40
|
import {
|
|
34
41
|
Attributes,
|
|
@@ -52,13 +59,15 @@ import { yamux } from '@chainsafe/libp2p-yamux';
|
|
|
52
59
|
import { bootstrap } from '@libp2p/bootstrap';
|
|
53
60
|
import { identify } from '@libp2p/identify';
|
|
54
61
|
import { type Message, type MultiaddrConnection, type PeerId, TopicValidatorResult } from '@libp2p/interface';
|
|
55
|
-
import type { ConnectionManager } from '@libp2p/interface-internal';
|
|
62
|
+
import type { AddressManager, ConnectionManager } from '@libp2p/interface-internal';
|
|
56
63
|
import { mplex } from '@libp2p/mplex';
|
|
57
64
|
import { tcp } from '@libp2p/tcp';
|
|
65
|
+
import { multiaddr } from '@multiformats/multiaddr';
|
|
58
66
|
import { ENR } from '@nethermindeth/enr';
|
|
59
67
|
import { createLibp2p } from 'libp2p';
|
|
60
68
|
|
|
61
69
|
import type { P2PConfig } from '../../config.js';
|
|
70
|
+
import { CheckpointProposalReceivedCallbackNotRegisteredError } from '../../errors/p2p-service.error.js';
|
|
62
71
|
import type { MemPools } from '../../mem_pools/interface.js';
|
|
63
72
|
import {
|
|
64
73
|
BlockProposalValidator,
|
|
@@ -70,42 +79,38 @@ import {
|
|
|
70
79
|
} from '../../msg_validators/index.js';
|
|
71
80
|
import { MessageSeenValidator } from '../../msg_validators/msg_seen_validator/msg_seen_validator.js';
|
|
72
81
|
import {
|
|
73
|
-
type
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
type TransactionValidator,
|
|
83
|
+
createFirstStageTxValidationsForGossipedTransactions,
|
|
84
|
+
createSecondStageTxValidationsForGossipedTransactions,
|
|
85
|
+
createTxValidatorForBlockProposalReceivedTxs,
|
|
76
86
|
} from '../../msg_validators/tx_validator/factory.js';
|
|
87
|
+
import { TxValidationCache } from '../../msg_validators/tx_validator/tx_validation_cache.js';
|
|
77
88
|
import { GossipSubEvent } from '../../types/index.js';
|
|
78
89
|
import { type PubSubLibp2p, convertToMultiaddr } from '../../util.js';
|
|
79
90
|
import { getVersions } from '../../versioning.js';
|
|
80
91
|
import { AztecDatastore } from '../data_store.js';
|
|
81
92
|
import { DiscV5Service } from '../discv5/discV5_service.js';
|
|
82
|
-
import { SnappyTransform,
|
|
93
|
+
import { SnappyTransform, getMsgIdFn, msgIdToStrFn } from '../encoding.js';
|
|
83
94
|
import { APP_SPECIFIC_WEIGHT, gossipScoreThresholds } from '../gossipsub/scoring.js';
|
|
84
95
|
import { createAllTopicScoreParams } from '../gossipsub/topic_score_params.js';
|
|
85
96
|
import type { PeerManagerInterface } from '../peer-manager/interface.js';
|
|
86
97
|
import { PeerManager } from '../peer-manager/peer_manager.js';
|
|
87
98
|
import { PeerScoring } from '../peer-manager/peer_scoring.js';
|
|
88
99
|
import type { BatchTxRequesterLibP2PService } from '../reqresp/batch-tx-requester/interface.js';
|
|
89
|
-
import type
|
|
100
|
+
import { type P2PReqRespConfig, YAMUX_MAX_MESSAGE_SIZE_BYTES } from '../reqresp/config.js';
|
|
90
101
|
import {
|
|
91
|
-
|
|
102
|
+
AuthRequest,
|
|
103
|
+
BlockTxsRequest,
|
|
104
|
+
BlockTxsResponse,
|
|
92
105
|
type ReqRespInterface,
|
|
93
106
|
type ReqRespResponse,
|
|
94
107
|
ReqRespSubProtocol,
|
|
95
108
|
type ReqRespSubProtocolHandler,
|
|
96
109
|
type ReqRespSubProtocolHandlers,
|
|
97
|
-
type ReqRespSubProtocolValidators,
|
|
98
|
-
type SubProtocolMap,
|
|
99
|
-
ValidationError,
|
|
100
|
-
} from '../reqresp/index.js';
|
|
101
|
-
import {
|
|
102
|
-
AuthRequest,
|
|
103
|
-
BlockTxsRequest,
|
|
104
|
-
BlockTxsResponse,
|
|
105
110
|
StatusMessage,
|
|
111
|
+
ValidationError,
|
|
106
112
|
pingHandler,
|
|
107
113
|
reqGoodbyeHandler,
|
|
108
|
-
reqRespBlockHandler,
|
|
109
114
|
reqRespBlockTxsHandler,
|
|
110
115
|
reqRespStatusHandler,
|
|
111
116
|
reqRespTxHandler,
|
|
@@ -113,12 +118,33 @@ import {
|
|
|
113
118
|
import { ReqResp } from '../reqresp/reqresp.js';
|
|
114
119
|
import type {
|
|
115
120
|
P2PBlockReceivedCallback,
|
|
121
|
+
P2PCheckpointAttestationCallback,
|
|
116
122
|
P2PCheckpointReceivedCallback,
|
|
123
|
+
P2PDuplicateAttestationCallback,
|
|
124
|
+
P2POversizedProposalCallback,
|
|
117
125
|
P2PService,
|
|
118
126
|
PeerDiscoveryService,
|
|
119
127
|
} from '../service.js';
|
|
120
128
|
import { P2PInstrumentation } from './instrumentation.js';
|
|
121
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Builds the {@link ConsensusTimetable} shared by the gossip validators for proposal/attestation receive-window
|
|
132
|
+
* bounds. Derived purely from protocol slot-timing constants plus the block sub-slot duration and the consensus
|
|
133
|
+
* materialization grace, so every node agrees on these bounds without depending on proposer operational budgets.
|
|
134
|
+
*/
|
|
135
|
+
function buildConsensusTimetable(
|
|
136
|
+
config: P2PConfig,
|
|
137
|
+
l1Constants: ReturnType<EpochCacheInterface['getL1Constants']>,
|
|
138
|
+
): ConsensusTimetable {
|
|
139
|
+
const blockDuration = config.blockDurationMs / 1000;
|
|
140
|
+
return new ConsensusTimetable({
|
|
141
|
+
l1Constants,
|
|
142
|
+
blockDuration,
|
|
143
|
+
checkpointProposalSyncGrace:
|
|
144
|
+
config.checkpointProposalSyncGraceSeconds ?? getDefaultCheckpointProposalSyncGrace(blockDuration),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
122
148
|
interface ValidationResult {
|
|
123
149
|
name: string;
|
|
124
150
|
isValid: TxValidationResult;
|
|
@@ -130,12 +156,12 @@ type ValidationOutcome = { allPassed: true } | { allPassed: false; failure: Vali
|
|
|
130
156
|
// REFACTOR: Unify with the type above
|
|
131
157
|
type ReceivedMessageValidationResult<T, M = undefined> =
|
|
132
158
|
| { obj: T; result: Exclude<TopicValidatorResult, TopicValidatorResult.Reject>; metadata?: M }
|
|
133
|
-
| { obj?: T; result: TopicValidatorResult.Reject; metadata?: M };
|
|
159
|
+
| { obj?: T; result: TopicValidatorResult.Reject; metadata?: M; severity: PeerErrorSeverity };
|
|
134
160
|
|
|
135
161
|
/**
|
|
136
162
|
* Lib P2P implementation of the P2PService interface.
|
|
137
163
|
*/
|
|
138
|
-
export class LibP2PService
|
|
164
|
+
export class LibP2PService extends WithTracer implements P2PService {
|
|
139
165
|
private discoveryRunningPromise?: RunningPromise;
|
|
140
166
|
private msgIdSeenValidators: Record<TopicType, MessageSeenValidator> = {} as Record<TopicType, MessageSeenValidator>;
|
|
141
167
|
|
|
@@ -147,8 +173,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
147
173
|
private protocolVersion = '';
|
|
148
174
|
private topicStrings: Record<TopicType, string> = {} as Record<TopicType, string>;
|
|
149
175
|
|
|
150
|
-
private feesCache: { blockNumber: BlockNumber; gasFees: GasFees } | undefined;
|
|
151
|
-
|
|
152
176
|
/** Callback invoked when a duplicate proposal is detected (triggers slashing). */
|
|
153
177
|
private duplicateProposalCallback?: (info: {
|
|
154
178
|
slot: SlotNumber;
|
|
@@ -156,6 +180,15 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
156
180
|
type: 'checkpoint' | 'block';
|
|
157
181
|
}) => void;
|
|
158
182
|
|
|
183
|
+
/** Callback invoked when an oversized block proposal is stored as slashing evidence (triggers slashing). */
|
|
184
|
+
private oversizedProposalCallback?: P2POversizedProposalCallback;
|
|
185
|
+
|
|
186
|
+
/** Callback invoked when a duplicate attestation is detected (triggers slashing). */
|
|
187
|
+
private duplicateAttestationCallback?: P2PDuplicateAttestationCallback;
|
|
188
|
+
|
|
189
|
+
/** Callback invoked when a valid checkpoint attestation is accepted into the pool. */
|
|
190
|
+
private checkpointAttestationCallback?: P2PCheckpointAttestationCallback;
|
|
191
|
+
|
|
159
192
|
/**
|
|
160
193
|
* Callback for when a block is received from a peer.
|
|
161
194
|
* @param block - The block received from the peer.
|
|
@@ -168,10 +201,19 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
168
201
|
* @param checkpoint - The checkpoint proposal received from the peer.
|
|
169
202
|
* @returns The attestations for the checkpoint, if any.
|
|
170
203
|
*/
|
|
171
|
-
private
|
|
204
|
+
private allNodesCheckpointReceivedCallback: P2PCheckpointReceivedCallback;
|
|
205
|
+
/**
|
|
206
|
+
* Callback for when a checkpoint proposal is received - specifically for validators - from a peer.
|
|
207
|
+
* @param checkpoint - The checkpoint proposal received from the peer.
|
|
208
|
+
* @returns The attestations for the checkpoint, if any.
|
|
209
|
+
*/
|
|
210
|
+
private validatorCheckpointReceivedCallback: P2PCheckpointReceivedCallback;
|
|
172
211
|
|
|
173
212
|
private gossipSubEventHandler: (e: CustomEvent<GossipsubMessage>) => void;
|
|
174
213
|
|
|
214
|
+
private ipChangedHandler?: (ip: string) => void;
|
|
215
|
+
private discoveredP2pIp?: string;
|
|
216
|
+
|
|
175
217
|
private instrumentation: P2PInstrumentation;
|
|
176
218
|
|
|
177
219
|
private telemetry: TelemetryClient;
|
|
@@ -179,7 +221,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
179
221
|
protected logger: Logger;
|
|
180
222
|
|
|
181
223
|
constructor(
|
|
182
|
-
private clientType: T,
|
|
183
224
|
private config: P2PConfig,
|
|
184
225
|
protected node: PubSubLibp2p,
|
|
185
226
|
private peerDiscoveryService: PeerDiscoveryService,
|
|
@@ -190,8 +231,10 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
190
231
|
private epochCache: EpochCacheInterface,
|
|
191
232
|
private proofVerifier: ClientProtocolCircuitVerifier,
|
|
192
233
|
private worldStateSynchronizer: WorldStateSynchronizer,
|
|
234
|
+
private blockMinFeesProvider: BlockMinFeesProvider,
|
|
193
235
|
telemetry: TelemetryClient,
|
|
194
236
|
logger: Logger = createLogger('p2p:libp2p_service'),
|
|
237
|
+
private txValidationCache?: TxValidationCache,
|
|
195
238
|
) {
|
|
196
239
|
super(telemetry, 'LibP2PService');
|
|
197
240
|
this.telemetry = telemetry;
|
|
@@ -221,36 +264,66 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
221
264
|
this.protocolVersion,
|
|
222
265
|
);
|
|
223
266
|
|
|
224
|
-
|
|
225
|
-
|
|
267
|
+
// Build the consensus timetable once from protocol slot-timing constants and inject it into every
|
|
268
|
+
// validator so they share one set of receive-window bounds, independent of proposer operational budgets.
|
|
269
|
+
const consensusTimetable = buildConsensusTimetable(config, epochCache.getL1Constants());
|
|
270
|
+
const proposalValidatorOpts = {
|
|
226
271
|
txsPermitted: !config.disableTransactions,
|
|
227
|
-
|
|
272
|
+
maxTxsPerBlock: config.validateMaxTxsPerBlock ?? config.validateMaxTxsPerCheckpoint,
|
|
273
|
+
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint,
|
|
274
|
+
skipSlotValidation: config.skipProposalSlotValidation,
|
|
275
|
+
signatureContext: {
|
|
276
|
+
chainId: config.l1ChainId,
|
|
277
|
+
rollupAddress: config.rollupAddress,
|
|
278
|
+
},
|
|
279
|
+
clockDisparityMs: config.maxGossipClockDisparityMs,
|
|
280
|
+
};
|
|
281
|
+
this.blockProposalValidator = new BlockProposalValidator(epochCache, consensusTimetable, proposalValidatorOpts);
|
|
282
|
+
this.checkpointProposalValidator = new CheckpointProposalValidator(
|
|
283
|
+
epochCache,
|
|
284
|
+
consensusTimetable,
|
|
285
|
+
proposalValidatorOpts,
|
|
286
|
+
);
|
|
287
|
+
const attestationValidatorOpts = {
|
|
288
|
+
signatureContext: proposalValidatorOpts.signatureContext,
|
|
289
|
+
clockDisparityMs: config.maxGossipClockDisparityMs,
|
|
290
|
+
};
|
|
228
291
|
this.checkpointAttestationValidator = config.fishermanMode
|
|
229
|
-
? new FishermanAttestationValidator(
|
|
230
|
-
|
|
292
|
+
? new FishermanAttestationValidator(
|
|
293
|
+
epochCache,
|
|
294
|
+
consensusTimetable,
|
|
295
|
+
mempools.attestationPool,
|
|
296
|
+
telemetry,
|
|
297
|
+
attestationValidatorOpts,
|
|
298
|
+
)
|
|
299
|
+
: new CheckpointAttestationValidator(epochCache, consensusTimetable, attestationValidatorOpts);
|
|
231
300
|
|
|
232
301
|
this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
|
|
233
302
|
|
|
234
303
|
this.blockReceivedCallback = async (block: BlockProposal): Promise<boolean> => {
|
|
235
|
-
this.logger.
|
|
236
|
-
`Handler not yet registered
|
|
304
|
+
this.logger.warn(
|
|
305
|
+
`Handler for block received not yet registered on P2P service. Received block ${block.blockNumber} for slot ${block.slotNumber} from peer.`,
|
|
237
306
|
{ p2pMessageIdentifier: await block.p2pMessageLoggingIdentifier() },
|
|
238
307
|
);
|
|
239
|
-
return
|
|
308
|
+
return true;
|
|
240
309
|
};
|
|
241
310
|
|
|
242
|
-
this.
|
|
243
|
-
|
|
311
|
+
this.allNodesCheckpointReceivedCallback = (
|
|
312
|
+
_checkpoint: ValidatedCheckpointProposalCore,
|
|
313
|
+
): Promise<CheckpointAttestation[] | undefined> => {
|
|
314
|
+
throw new CheckpointProposalReceivedCallbackNotRegisteredError();
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
this.validatorCheckpointReceivedCallback = (
|
|
318
|
+
_checkpoint: ValidatedCheckpointProposalCore,
|
|
244
319
|
): Promise<CheckpointAttestation[] | undefined> => {
|
|
245
|
-
this.logger.debug(
|
|
246
|
-
`Handler not yet registered: Checkpoint received callback not set. Received checkpoint for slot ${checkpoint.slotNumber} from peer.`,
|
|
247
|
-
);
|
|
248
320
|
return Promise.resolve(undefined);
|
|
249
321
|
};
|
|
250
322
|
}
|
|
251
323
|
|
|
252
|
-
public updateConfig(config: Partial<P2PReqRespConfig
|
|
324
|
+
public updateConfig(config: Partial<P2PReqRespConfig & Pick<P2PConfig, 'skipIncomingProposals'>>) {
|
|
253
325
|
this.reqresp.updateConfig(config);
|
|
326
|
+
this.config = merge(this.config, config);
|
|
254
327
|
}
|
|
255
328
|
|
|
256
329
|
/**
|
|
@@ -259,8 +332,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
259
332
|
* @param txPool - The transaction pool to be accessed by the service.
|
|
260
333
|
* @returns The new service.
|
|
261
334
|
*/
|
|
262
|
-
public static async new
|
|
263
|
-
clientType: T,
|
|
335
|
+
public static async new(
|
|
264
336
|
config: P2PConfig,
|
|
265
337
|
peerId: PeerId,
|
|
266
338
|
deps: {
|
|
@@ -270,9 +342,11 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
270
342
|
proofVerifier: ClientProtocolCircuitVerifier;
|
|
271
343
|
worldStateSynchronizer: WorldStateSynchronizer;
|
|
272
344
|
peerStore: AztecAsyncKVStore;
|
|
345
|
+
blockMinFeesProvider: BlockMinFeesProvider;
|
|
273
346
|
telemetry: TelemetryClient;
|
|
274
347
|
logger: Logger;
|
|
275
348
|
packageVersion: string;
|
|
349
|
+
txValidationCache?: TxValidationCache;
|
|
276
350
|
},
|
|
277
351
|
) {
|
|
278
352
|
const {
|
|
@@ -282,9 +356,11 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
282
356
|
mempools,
|
|
283
357
|
proofVerifier,
|
|
284
358
|
peerStore,
|
|
359
|
+
blockMinFeesProvider,
|
|
285
360
|
telemetry,
|
|
286
361
|
logger,
|
|
287
362
|
packageVersion,
|
|
363
|
+
txValidationCache,
|
|
288
364
|
} = deps;
|
|
289
365
|
const { p2pPort, maxPeerCount, listenAddress } = config;
|
|
290
366
|
const bindAddrTcp = convertToMultiaddr(listenAddress, p2pPort, 'tcp');
|
|
@@ -299,6 +375,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
299
375
|
packageVersion,
|
|
300
376
|
telemetry,
|
|
301
377
|
createLogger(`${logger.module}:discv5_service`, logger.getBindings()),
|
|
378
|
+
peerStore,
|
|
302
379
|
);
|
|
303
380
|
|
|
304
381
|
// Seed libp2p's bootstrap discovery with private and trusted peers
|
|
@@ -313,33 +390,49 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
313
390
|
const protocolVersion = compressComponentVersions(versions);
|
|
314
391
|
|
|
315
392
|
const preferredPeersEnrs: ENR[] = config.preferredPeers.map(enr => ENR.decodeTxt(enr));
|
|
316
|
-
const directPeers = (
|
|
393
|
+
const directPeers = compactArray(
|
|
317
394
|
await Promise.all(
|
|
318
395
|
preferredPeersEnrs.map(async enr => {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
396
|
+
try {
|
|
397
|
+
const peerId = await enr.peerId();
|
|
398
|
+
const address = enr.getLocationMultiaddr('tcp');
|
|
399
|
+
if (address === undefined) {
|
|
400
|
+
throw new Error(`Direct peer ${peerId.toString()} has no TCP address, ENR: ${enr.encodeTxt()}`);
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
id: peerId,
|
|
404
|
+
addrs: [address],
|
|
405
|
+
};
|
|
406
|
+
} catch (err) {
|
|
407
|
+
// A malformed configured ENR shouldn't abort node setup — skip it and log.
|
|
408
|
+
logger.warn(`Skipping preferred peer with invalid ENR`, { err });
|
|
409
|
+
return undefined;
|
|
323
410
|
}
|
|
324
|
-
return {
|
|
325
|
-
id: peerId,
|
|
326
|
-
addrs: [address],
|
|
327
|
-
};
|
|
328
411
|
}),
|
|
329
|
-
)
|
|
330
|
-
)
|
|
412
|
+
),
|
|
413
|
+
);
|
|
331
414
|
|
|
332
415
|
const announceTcpMultiaddr = config.p2pIp ? [convertToMultiaddr(config.p2pIp, p2pPort, 'tcp')] : [];
|
|
333
416
|
|
|
334
|
-
// Create dynamic topic score params based on network configuration
|
|
417
|
+
// Create dynamic topic score params based on network configuration. Scoring uses the network-wide
|
|
418
|
+
// max-blocks-per-checkpoint config value directly to size expected per-slot message rates; these are
|
|
419
|
+
// peer-rate thresholds, not consensus deadlines, so they need no proposer operational budgets.
|
|
335
420
|
const l1Constants = epochCache.getL1Constants();
|
|
336
421
|
const topicScoreParams = createAllTopicScoreParams(protocolVersion, {
|
|
337
422
|
slotDurationMs: l1Constants.slotDuration * 1000,
|
|
338
423
|
heartbeatIntervalMs: config.gossipsubInterval,
|
|
339
424
|
targetCommitteeSize: l1Constants.targetCommitteeSize,
|
|
340
|
-
|
|
425
|
+
maxBlocksPerCheckpoint: config.maxBlocksPerCheckpoint ?? DEFAULT_MAX_BLOCKS_PER_CHECKPOINT,
|
|
426
|
+
expectedBlockProposalsPerSlot: config.expectedBlockProposalsPerSlot,
|
|
341
427
|
});
|
|
342
428
|
|
|
429
|
+
// Restrict gossipsub to exactly the topics we subscribe to. Without this, an arbitrary-topic
|
|
430
|
+
// message is transformed, msg-id'd and inserted into the seenCache before the subscription check,
|
|
431
|
+
// so a crafted topic colliding on msg id can suppress a real message as a duplicate.
|
|
432
|
+
const allowedTopics = getTopicsForConfig(config.disableTransactions).map(topic =>
|
|
433
|
+
createTopicString(topic, protocolVersion),
|
|
434
|
+
);
|
|
435
|
+
|
|
343
436
|
const node = await createLibp2p({
|
|
344
437
|
start: false,
|
|
345
438
|
peerId,
|
|
@@ -371,7 +464,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
371
464
|
],
|
|
372
465
|
datastore,
|
|
373
466
|
peerDiscovery,
|
|
374
|
-
|
|
467
|
+
// Pin the yamux frame size: MAX_REQRESP_REQUEST_SIZE_BYTES relies on a reqresp request fitting in one frame.
|
|
468
|
+
streamMuxers: [yamux({ maxMessageSize: YAMUX_MAX_MESSAGE_SIZE_BYTES }), mplex()],
|
|
375
469
|
connectionEncryption: [noise()],
|
|
376
470
|
connectionManager: {
|
|
377
471
|
minConnections: 0, // Disable libp2p peer dialing, we do it manually
|
|
@@ -385,7 +479,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
385
479
|
},
|
|
386
480
|
connectionGater: {
|
|
387
481
|
denyInboundConnection: (maConn: MultiaddrConnection) => {
|
|
388
|
-
const allowed = peerManager.
|
|
482
|
+
const allowed = peerManager.isAddressAllowedToConnect(maConn.remoteAddr.nodeAddress().address);
|
|
389
483
|
if (allowed) {
|
|
390
484
|
return false;
|
|
391
485
|
}
|
|
@@ -396,7 +490,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
396
490
|
denyInboundEncryptedConnection: (peerId: PeerId, _maConn: MultiaddrConnection) => {
|
|
397
491
|
//NOTE: it is not necessary to check address here because this was already done by
|
|
398
492
|
// denyInboundConnection
|
|
399
|
-
const allowed = peerManager.
|
|
493
|
+
const allowed = peerManager.isPeerAllowedToConnect(peerId);
|
|
400
494
|
if (allowed) {
|
|
401
495
|
return false;
|
|
402
496
|
}
|
|
@@ -424,9 +518,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
424
518
|
mcacheLength: config.gossipsubMcacheLength,
|
|
425
519
|
mcacheGossip: config.gossipsubMcacheGossip,
|
|
426
520
|
seenTTL: config.gossipsubSeenTTL,
|
|
521
|
+
allowedTopics,
|
|
522
|
+
// No fastMsgIdFn: the fast-path dedup cache keys on a non-cryptographic 64-bit hash of the
|
|
523
|
+
// raw data only (no topic), so a collision — accidental or engineered via a weak seed — drops
|
|
524
|
+
// a different message with no fallback to the full id. Dedup instead rests solely on the
|
|
525
|
+
// cryptographic, topic-framed msgIdFn below.
|
|
427
526
|
msgIdFn: getMsgIdFn,
|
|
428
527
|
msgIdToStrFn: msgIdToStrFn,
|
|
429
|
-
fastMsgIdFn: fastMsgIdFn,
|
|
430
528
|
dataTransform: new SnappyTransform(),
|
|
431
529
|
metricsRegister: otelMetricsAdapter,
|
|
432
530
|
metricsTopicStrToLabel: metricsTopicStrToLabels(protocolVersion),
|
|
@@ -438,8 +536,9 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
438
536
|
topics: topicScoreParams,
|
|
439
537
|
}),
|
|
440
538
|
}) as (components: GossipSubComponents) => GossipSub,
|
|
441
|
-
components: (components: { connectionManager: ConnectionManager }) => ({
|
|
539
|
+
components: (components: { connectionManager: ConnectionManager; addressManager: AddressManager }) => ({
|
|
442
540
|
connectionManager: components.connectionManager,
|
|
541
|
+
addressManager: components.addressManager,
|
|
443
542
|
}),
|
|
444
543
|
},
|
|
445
544
|
logger: createLibp2pComponentLogger(logger.module, logger.getBindings()),
|
|
@@ -461,6 +560,9 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
461
560
|
epochCache,
|
|
462
561
|
);
|
|
463
562
|
|
|
563
|
+
// Gate req/resp data protocols for unauthenticated peers when p2pAllowOnlyValidators is enabled
|
|
564
|
+
reqresp.setShouldRejectPeer(peerId => peerManager.shouldDisableP2PGossip(peerId));
|
|
565
|
+
|
|
464
566
|
// Configure application-specific scoring for gossipsub.
|
|
465
567
|
// The weight scales app score to align with gossipsub thresholds:
|
|
466
568
|
// - Disconnect (-50) × 10 = -500 = gossipThreshold (stops receiving gossip)
|
|
@@ -471,7 +573,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
471
573
|
peerManager.shouldDisableP2PGossip(peerId) ? -Infinity : peerManager.getPeerScore(peerId);
|
|
472
574
|
|
|
473
575
|
return new LibP2PService(
|
|
474
|
-
clientType,
|
|
475
576
|
config,
|
|
476
577
|
node,
|
|
477
578
|
peerDiscoveryService,
|
|
@@ -482,8 +583,10 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
482
583
|
epochCache,
|
|
483
584
|
proofVerifier,
|
|
484
585
|
worldStateSynchronizer,
|
|
586
|
+
blockMinFeesProvider,
|
|
485
587
|
telemetry,
|
|
486
588
|
logger,
|
|
589
|
+
txValidationCache,
|
|
487
590
|
);
|
|
488
591
|
}
|
|
489
592
|
|
|
@@ -497,24 +600,21 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
497
600
|
throw new Error('P2P service already started');
|
|
498
601
|
}
|
|
499
602
|
|
|
500
|
-
// Get listen & announce addresses for logging
|
|
501
603
|
const { p2pIp, p2pPort } = this.config;
|
|
502
|
-
if (!p2pIp) {
|
|
503
|
-
throw new Error('Announce address not provided.');
|
|
604
|
+
if (!p2pIp && !this.config.queryForIp) {
|
|
605
|
+
throw new Error('Announce address not provided and queryForIp is not enabled.');
|
|
504
606
|
}
|
|
505
|
-
const announceTcpMultiaddr = convertToMultiaddr(p2pIp, p2pPort, 'tcp');
|
|
607
|
+
const announceTcpMultiaddr = p2pIp ? convertToMultiaddr(p2pIp, p2pPort, 'tcp') : undefined;
|
|
506
608
|
|
|
507
609
|
// Create request response protocol handlers
|
|
508
610
|
const txHandler = reqRespTxHandler(this.mempools);
|
|
509
611
|
const goodbyeHandler = reqGoodbyeHandler(this.peerManager);
|
|
510
|
-
const blockHandler = reqRespBlockHandler(this.archiver);
|
|
511
612
|
const statusHandler = reqRespStatusHandler(this.protocolVersion, this.worldStateSynchronizer, this.logger);
|
|
512
613
|
|
|
513
614
|
const requestResponseHandlers: Partial<ReqRespSubProtocolHandlers> = {
|
|
514
615
|
[ReqRespSubProtocol.PING]: pingHandler,
|
|
515
616
|
[ReqRespSubProtocol.STATUS]: statusHandler.bind(this),
|
|
516
617
|
[ReqRespSubProtocol.GOODBYE]: goodbyeHandler.bind(this),
|
|
517
|
-
[ReqRespSubProtocol.BLOCK]: blockHandler.bind(this),
|
|
518
618
|
};
|
|
519
619
|
|
|
520
620
|
if (!this.config.disableTransactions) {
|
|
@@ -530,22 +630,14 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
530
630
|
requestResponseHandlers[ReqRespSubProtocol.TX] = txHandler.bind(this);
|
|
531
631
|
}
|
|
532
632
|
|
|
533
|
-
// Define the sub protocol validators - This is done within this start() method to gain a callback to the existing validateTx function
|
|
534
|
-
const reqrespSubProtocolValidators = {
|
|
535
|
-
...DEFAULT_SUB_PROTOCOL_VALIDATORS,
|
|
536
|
-
[ReqRespSubProtocol.TX]: this.validateRequestedTxs.bind(this),
|
|
537
|
-
[ReqRespSubProtocol.BLOCK_TXS]: this.validateRequestedBlockTxs.bind(this),
|
|
538
|
-
[ReqRespSubProtocol.BLOCK]: this.validateRequestedBlock.bind(this),
|
|
539
|
-
};
|
|
540
|
-
|
|
541
633
|
await this.peerManager.initializePeers();
|
|
542
634
|
|
|
543
|
-
await this.reqresp.start(requestResponseHandlers
|
|
635
|
+
await this.reqresp.start(requestResponseHandlers);
|
|
544
636
|
|
|
545
637
|
await this.node.start();
|
|
546
638
|
|
|
547
639
|
// Subscribe to standard GossipSub topics by default
|
|
548
|
-
for (const topic of
|
|
640
|
+
for (const topic of getTopicsForConfig(this.config.disableTransactions)) {
|
|
549
641
|
this.subscribeToTopic(this.topicStrings[topic]);
|
|
550
642
|
}
|
|
551
643
|
|
|
@@ -556,6 +648,38 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
556
648
|
if (!this.config.p2pDiscoveryDisabled) {
|
|
557
649
|
await this.peerDiscoveryService.start();
|
|
558
650
|
}
|
|
651
|
+
|
|
652
|
+
// Bridge discv5 IP changes to libp2p's AddressManager so peers see the updated address
|
|
653
|
+
if (this.config.queryForIp) {
|
|
654
|
+
this.discoveredP2pIp = this.config.p2pIp;
|
|
655
|
+
this.logger.info('IP change tracking enabled, bridging discv5 IP updates to libp2p AddressManager');
|
|
656
|
+
this.ipChangedHandler = (ip: string) => {
|
|
657
|
+
const addressManager = this.node.services.components.addressManager;
|
|
658
|
+
const newAddr = multiaddr(convertToMultiaddr(ip, this.config.p2pPort, 'tcp'));
|
|
659
|
+
const previousIp = this.discoveredP2pIp;
|
|
660
|
+
|
|
661
|
+
if (previousIp) {
|
|
662
|
+
const oldAddr = multiaddr(convertToMultiaddr(previousIp, this.config.p2pPort, 'tcp'));
|
|
663
|
+
addressManager.removeObservedAddr(oldAddr);
|
|
664
|
+
this.logger.info('Libp2p announce address updated due to IP change', {
|
|
665
|
+
previousIp,
|
|
666
|
+
newIp: ip,
|
|
667
|
+
newMultiaddr: newAddr.toString(),
|
|
668
|
+
});
|
|
669
|
+
} else {
|
|
670
|
+
this.logger.info('Libp2p announce address set from initial discv5 IP discovery', {
|
|
671
|
+
ip,
|
|
672
|
+
multiaddr: newAddr.toString(),
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
addressManager.addObservedAddr(newAddr);
|
|
677
|
+
addressManager.confirmObservedAddr(newAddr);
|
|
678
|
+
this.discoveredP2pIp = ip;
|
|
679
|
+
};
|
|
680
|
+
this.peerDiscoveryService.on('ip:changed', this.ipChangedHandler);
|
|
681
|
+
}
|
|
682
|
+
|
|
559
683
|
this.discoveryRunningPromise = new RunningPromise(
|
|
560
684
|
async () => {
|
|
561
685
|
await this.peerManager.heartbeat();
|
|
@@ -581,6 +705,11 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
581
705
|
// Remove gossip sub listener
|
|
582
706
|
this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
|
|
583
707
|
|
|
708
|
+
if (this.ipChangedHandler) {
|
|
709
|
+
this.peerDiscoveryService.removeListener('ip:changed', this.ipChangedHandler);
|
|
710
|
+
this.ipChangedHandler = undefined;
|
|
711
|
+
}
|
|
712
|
+
|
|
584
713
|
// Stop peer manager
|
|
585
714
|
this.logger.debug('Stopping peer manager...');
|
|
586
715
|
await this.peerManager.stop();
|
|
@@ -595,12 +724,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
595
724
|
this.logger.info('LibP2P service stopped');
|
|
596
725
|
}
|
|
597
726
|
|
|
598
|
-
addReqRespSubProtocol(
|
|
599
|
-
subProtocol
|
|
600
|
-
handler: ReqRespSubProtocolHandler,
|
|
601
|
-
validator?: ReqRespSubProtocolValidators[ReqRespSubProtocol],
|
|
602
|
-
): Promise<void> {
|
|
603
|
-
return this.reqresp.addSubProtocol(subProtocol, handler, validator);
|
|
727
|
+
addReqRespSubProtocol(subProtocol: ReqRespSubProtocol, handler: ReqRespSubProtocolHandler): Promise<void> {
|
|
728
|
+
return this.reqresp.addSubProtocol(subProtocol, handler);
|
|
604
729
|
}
|
|
605
730
|
|
|
606
731
|
public registerThisValidatorAddresses(address: EthAddress[]): void {
|
|
@@ -611,6 +736,15 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
611
736
|
return this.peerManager.getPeers(includePending);
|
|
612
737
|
}
|
|
613
738
|
|
|
739
|
+
public getP2PConnectivity(): P2PConnectivity {
|
|
740
|
+
const connectedPeers = this.peerManager.getPeers().filter(peer => peer.status === 'connected').length;
|
|
741
|
+
return { enabled: true, connectedPeers };
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
public getGossipMeshPeerCount(topicType: TopicType): number {
|
|
745
|
+
return this.node.services.pubsub.getMeshPeers(this.topicStrings[topicType]).length;
|
|
746
|
+
}
|
|
747
|
+
|
|
614
748
|
private handleGossipSubEvent(e: CustomEvent<GossipsubMessage>) {
|
|
615
749
|
this.logger.trace(`Received PUBSUB message.`);
|
|
616
750
|
|
|
@@ -624,20 +758,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
624
758
|
setImmediate(() => void safeJob());
|
|
625
759
|
}
|
|
626
760
|
|
|
627
|
-
/**
|
|
628
|
-
* Send a batch of requests to peers, and return the responses
|
|
629
|
-
* @param protocol - The request response protocol to use
|
|
630
|
-
* @param requests - The requests to send to the peers
|
|
631
|
-
* @returns The responses to the requests
|
|
632
|
-
*/
|
|
633
|
-
sendBatchRequest<SubProtocol extends ReqRespSubProtocol>(
|
|
634
|
-
protocol: SubProtocol,
|
|
635
|
-
requests: InstanceType<SubProtocolMap[SubProtocol]['request']>[],
|
|
636
|
-
pinnedPeerId: PeerId | undefined,
|
|
637
|
-
): Promise<InstanceType<SubProtocolMap[SubProtocol]['response']>[]> {
|
|
638
|
-
return this.reqresp.sendBatchRequest(protocol, requests, pinnedPeerId);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
761
|
public sendRequestToPeer(
|
|
642
762
|
peerId: PeerId,
|
|
643
763
|
subProtocol: ReqRespSubProtocol,
|
|
@@ -659,8 +779,12 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
659
779
|
this.blockReceivedCallback = callback;
|
|
660
780
|
}
|
|
661
781
|
|
|
662
|
-
public
|
|
663
|
-
this.
|
|
782
|
+
public registerValidatorCheckpointReceivedCallback(callback: P2PCheckpointReceivedCallback) {
|
|
783
|
+
this.validatorCheckpointReceivedCallback = callback;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
public registerAllNodesCheckpointReceivedCallback(callback: P2PCheckpointReceivedCallback) {
|
|
787
|
+
this.allNodesCheckpointReceivedCallback = callback;
|
|
664
788
|
}
|
|
665
789
|
|
|
666
790
|
/**
|
|
@@ -673,6 +797,26 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
673
797
|
this.duplicateProposalCallback = callback;
|
|
674
798
|
}
|
|
675
799
|
|
|
800
|
+
/**
|
|
801
|
+
* Registers a callback to be invoked when an oversized block proposal is stored as slashing evidence.
|
|
802
|
+
*/
|
|
803
|
+
public registerOversizedProposalCallback(callback: P2POversizedProposalCallback): void {
|
|
804
|
+
this.oversizedProposalCallback = callback;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Registers a callback to be invoked when a duplicate attestation is detected.
|
|
809
|
+
* A validator signing attestations for different proposals at the same slot.
|
|
810
|
+
* This callback is triggered on the first duplicate (when count goes from 1 to 2).
|
|
811
|
+
*/
|
|
812
|
+
public registerDuplicateAttestationCallback(callback: P2PDuplicateAttestationCallback): void {
|
|
813
|
+
this.duplicateAttestationCallback = callback;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
public registerCheckpointAttestationCallback(callback: P2PCheckpointAttestationCallback): void {
|
|
817
|
+
this.checkpointAttestationCallback = callback;
|
|
818
|
+
}
|
|
819
|
+
|
|
676
820
|
/**
|
|
677
821
|
* Subscribes to a topic.
|
|
678
822
|
* @param topic - The topic to subscribe to.
|
|
@@ -737,6 +881,9 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
737
881
|
if (!validator || !validator.addMessage(msgId)) {
|
|
738
882
|
this.instrumentation.incMessagePrevalidationStatus(false, topicType);
|
|
739
883
|
this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), TopicValidatorResult.Ignore);
|
|
884
|
+
if (topicType === TopicType.tx) {
|
|
885
|
+
this.logger.verbose(`Ignoring already-seen tx gossip message`, { msgId, source: source.toString() });
|
|
886
|
+
}
|
|
740
887
|
return { result: false, topicType };
|
|
741
888
|
}
|
|
742
889
|
|
|
@@ -798,12 +945,19 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
798
945
|
|
|
799
946
|
// Process the message, optionally within a linked span for trace propagation
|
|
800
947
|
const processMessage = async () => {
|
|
948
|
+
if (
|
|
949
|
+
this.config.skipIncomingProposals &&
|
|
950
|
+
(msg.topic === this.topicStrings[TopicType.block_proposal] ||
|
|
951
|
+
msg.topic === this.topicStrings[TopicType.checkpoint_proposal])
|
|
952
|
+
) {
|
|
953
|
+
this.logger.warn(`Ignoring incoming proposal (skipIncomingProposals is set)`, { topic: msg.topic });
|
|
954
|
+
this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), TopicValidatorResult.Ignore);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
801
957
|
if (msg.topic === this.topicStrings[TopicType.tx]) {
|
|
802
958
|
await this.handleGossipedTx(p2pMessage.payload, msgId, source);
|
|
803
959
|
} else if (msg.topic === this.topicStrings[TopicType.checkpoint_attestation]) {
|
|
804
|
-
|
|
805
|
-
await this.processCheckpointAttestationFromPeer(p2pMessage.payload, msgId, source);
|
|
806
|
-
}
|
|
960
|
+
await this.processCheckpointAttestationFromPeer(p2pMessage.payload, msgId, source);
|
|
807
961
|
} else if (msg.topic === this.topicStrings[TopicType.block_proposal]) {
|
|
808
962
|
await this.processBlockFromPeer(p2pMessage.payload, msgId, source);
|
|
809
963
|
} else if (msg.topic === this.topicStrings[TopicType.checkpoint_proposal]) {
|
|
@@ -864,54 +1018,182 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
864
1018
|
msgId: string,
|
|
865
1019
|
source: PeerId,
|
|
866
1020
|
topicType: TopicType,
|
|
1021
|
+
stageTimings?: Record<string, number>,
|
|
867
1022
|
): Promise<ReceivedMessageValidationResult<T, M>> {
|
|
868
|
-
|
|
1023
|
+
// Default to reject result with a penalty if validation function throws an error
|
|
1024
|
+
let resultAndObj: ReceivedMessageValidationResult<T, M> = {
|
|
1025
|
+
result: TopicValidatorResult.Reject,
|
|
1026
|
+
severity: PeerErrorSeverity.MidToleranceError,
|
|
1027
|
+
};
|
|
869
1028
|
const timer = new Timer();
|
|
870
1029
|
try {
|
|
871
1030
|
resultAndObj = await validationFunc();
|
|
872
1031
|
} catch (err) {
|
|
873
|
-
this.
|
|
874
|
-
|
|
1032
|
+
this.logger.error(`Error validating gossipsub message`, err, { msgId, source: source.toString(), topicType });
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const validationTimeMs = timer.ms();
|
|
1036
|
+
const mcacheWindowMs = this.config.gossipsubMcacheLength * this.config.gossipsubInterval;
|
|
1037
|
+
if (validationTimeMs > mcacheWindowMs * 0.75) {
|
|
1038
|
+
this.instrumentation.incSlowValidation(topicType);
|
|
1039
|
+
this.logger.warn(
|
|
1040
|
+
`Gossip validation for ${topicType} took ${validationTimeMs}ms, approaching mcache eviction window of ${mcacheWindowMs}ms. ` +
|
|
1041
|
+
`Message forwarding may be skipped if validation exceeds the window.`,
|
|
1042
|
+
{ msgId, source: source.toString(), topicType, validationTimeMs, mcacheWindowMs, stageTimings },
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (resultAndObj.result === TopicValidatorResult.Accept) {
|
|
1047
|
+
this.logger.debug(`Message ${topicType} accepted by validator`, {
|
|
875
1048
|
msgId,
|
|
876
1049
|
source: source.toString(),
|
|
877
1050
|
topicType,
|
|
1051
|
+
validationTimeMs,
|
|
1052
|
+
stageTimings,
|
|
878
1053
|
});
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
if (resultAndObj.result === TopicValidatorResult.Accept) {
|
|
882
1054
|
this.instrumentation.recordMessageValidation(topicType, timer);
|
|
1055
|
+
} else if (resultAndObj.result === TopicValidatorResult.Reject) {
|
|
1056
|
+
this.logger.warn(`Message ${topicType} rejected by validator with severity ${resultAndObj.severity}`, {
|
|
1057
|
+
msgId,
|
|
1058
|
+
source: source.toString(),
|
|
1059
|
+
topicType,
|
|
1060
|
+
severity: resultAndObj.severity,
|
|
1061
|
+
});
|
|
1062
|
+
this.peerManager.penalizePeer(source, resultAndObj.severity);
|
|
1063
|
+
} else {
|
|
1064
|
+
this.logger.trace(`Message ${topicType} ignored by validator`, { msgId, source: source.toString(), topicType });
|
|
883
1065
|
}
|
|
884
1066
|
|
|
885
1067
|
this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), resultAndObj.result);
|
|
886
1068
|
return resultAndObj;
|
|
887
1069
|
}
|
|
888
1070
|
|
|
1071
|
+
private tryDeserialize<T>(deserializeFunc: () => T, msgId: string, source: PeerId): T | undefined {
|
|
1072
|
+
try {
|
|
1073
|
+
return deserializeFunc();
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
this.logger.warn(`Failed to deserialize gossipsub message from buffer`, {
|
|
1076
|
+
err,
|
|
1077
|
+
msgId,
|
|
1078
|
+
source: source.toString(),
|
|
1079
|
+
});
|
|
1080
|
+
return undefined;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
889
1084
|
protected async handleGossipedTx(payloadData: Buffer, msgId: string, source: PeerId) {
|
|
1085
|
+
// Per-stage timings so slow validations can be attributed to a specific stage (see A-1656).
|
|
1086
|
+
const stageTimings: Record<string, number> = {};
|
|
1087
|
+
const timed = async <T>(stage: string, fn: () => T | Promise<T>): Promise<T> => {
|
|
1088
|
+
const timer = new Timer();
|
|
1089
|
+
try {
|
|
1090
|
+
return await fn();
|
|
1091
|
+
} finally {
|
|
1092
|
+
const ms = timer.ms();
|
|
1093
|
+
stageTimings[stage] = Math.ceil(ms);
|
|
1094
|
+
this.instrumentation.recordTxValidationStage(stage, ms);
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
|
|
890
1098
|
const validationFunc: () => Promise<ReceivedMessageValidationResult<Tx>> = async () => {
|
|
891
|
-
const tx = Tx.fromBuffer(payloadData);
|
|
892
|
-
|
|
893
|
-
|
|
1099
|
+
const tx = await timed('deserialize', () => this.tryDeserialize(() => Tx.fromBuffer(payloadData), msgId, source));
|
|
1100
|
+
if (!tx) {
|
|
1101
|
+
return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.LowToleranceError };
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Stage 1 setup: chain tip, epoch info, gas fees, and L1 constants reads
|
|
1105
|
+
const { currentBlockNumber, firstStageValidators } = await timed('fast_validation_setup', async () => {
|
|
1106
|
+
const currentBlockNumber = await this.archiver.getBlockNumber();
|
|
1107
|
+
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1108
|
+
const firstStageValidators = await this.createFirstStageMessageValidators(
|
|
1109
|
+
currentBlockNumber,
|
|
1110
|
+
nextSlotTimestamp,
|
|
1111
|
+
);
|
|
1112
|
+
return { currentBlockNumber, firstStageValidators };
|
|
1113
|
+
});
|
|
894
1114
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1115
|
+
// Stage 1: fast validators (metadata, data, timestamps, double-spend, gas, phases, block header)
|
|
1116
|
+
const firstStageOutcome = await timed('fast_validation', () => this.runValidations(tx, firstStageValidators));
|
|
1117
|
+
if (!firstStageOutcome.allPassed) {
|
|
1118
|
+
const { name } = firstStageOutcome.failure;
|
|
1119
|
+
let { severity } = firstStageOutcome.failure;
|
|
1120
|
+
|
|
1121
|
+
// Double spend validator has a special case handler. We perform more detailed examination
|
|
1122
|
+
// as to how recently the nullifier was entered into the tree and if the transaction should
|
|
1123
|
+
// have 'known' the nullifier existed. This determines the severity of the penalty applied to the peer.
|
|
1124
|
+
if (name === 'doubleSpendValidator') {
|
|
1125
|
+
const txBlockNumber = BlockNumber(currentBlockNumber + 1);
|
|
1126
|
+
severity = await this.handleDoubleSpendFailure(tx, txBlockNumber);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
this.logger.verbose(`Rejecting gossiped tx ${tx.getTxHash().toString()}: stage 1 validation failed`, {
|
|
1130
|
+
validator: name,
|
|
1131
|
+
severity,
|
|
1132
|
+
source: source.toString(),
|
|
1133
|
+
});
|
|
1134
|
+
return { result: TopicValidatorResult.Reject, severity };
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Pool pre-check: see if the pool would accept this tx before doing expensive proof verification
|
|
1138
|
+
const canAdd = await timed('pool_precheck', () => this.mempools.txPool.canAddPendingTx(tx));
|
|
1139
|
+
if (canAdd === 'ignored') {
|
|
1140
|
+
this.logger.verbose(`Ignoring gossiped tx ${tx.getTxHash().toString()}: pool pre-check returned ignored`, {
|
|
1141
|
+
source: source.toString(),
|
|
1142
|
+
});
|
|
1143
|
+
return { result: TopicValidatorResult.Ignore, obj: tx };
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// Stage 2: expensive proof verification
|
|
1147
|
+
const secondStageValidators = this.createSecondStageMessageValidators();
|
|
1148
|
+
const secondStageOutcome = await timed('proof_verify', () => this.runValidations(tx, secondStageValidators));
|
|
1149
|
+
if (!secondStageOutcome.allPassed) {
|
|
1150
|
+
const { severity, name } = secondStageOutcome.failure;
|
|
1151
|
+
this.logger.verbose(`Rejecting gossiped tx ${tx.getTxHash().toString()}: stage 2 validation failed`, {
|
|
1152
|
+
validator: name,
|
|
1153
|
+
severity,
|
|
1154
|
+
source: source.toString(),
|
|
1155
|
+
});
|
|
1156
|
+
return { result: TopicValidatorResult.Reject, severity };
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Pool add: persist the tx
|
|
1160
|
+
const txHash = tx.getTxHash();
|
|
1161
|
+
const addResult = await timed('pool_add', () => this.mempools.txPool.addPendingTxs([tx], { source: 'gossip' }));
|
|
1162
|
+
|
|
1163
|
+
const wasAccepted = addResult.accepted.some(h => h.equals(txHash));
|
|
1164
|
+
const wasIgnored = addResult.ignored.some(h => h.equals(txHash));
|
|
1165
|
+
|
|
1166
|
+
this.logger.verbose(`Validate propagated tx ${txHash.toString()}`, {
|
|
1167
|
+
wasAccepted,
|
|
1168
|
+
wasIgnored,
|
|
898
1169
|
[Attributes.P2P_ID]: source.toString(),
|
|
899
1170
|
});
|
|
900
1171
|
|
|
901
|
-
if (
|
|
902
|
-
return { result: TopicValidatorResult.
|
|
903
|
-
} else if (
|
|
1172
|
+
if (wasAccepted) {
|
|
1173
|
+
return { result: TopicValidatorResult.Accept, obj: tx };
|
|
1174
|
+
} else if (wasIgnored) {
|
|
904
1175
|
return { result: TopicValidatorResult.Ignore, obj: tx };
|
|
905
1176
|
} else {
|
|
906
|
-
|
|
1177
|
+
this.logger.warn(`Gossiped tx ${txHash.toString()} unexpectedly rejected by pool`, {
|
|
1178
|
+
source: source.toString(),
|
|
1179
|
+
txHash: txHash.toString(),
|
|
1180
|
+
});
|
|
1181
|
+
return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError };
|
|
907
1182
|
}
|
|
908
1183
|
};
|
|
909
1184
|
|
|
910
|
-
const { result, obj: tx } = await this.validateReceivedMessage<Tx>(
|
|
1185
|
+
const { result, obj: tx } = await this.validateReceivedMessage<Tx>(
|
|
1186
|
+
validationFunc,
|
|
1187
|
+
msgId,
|
|
1188
|
+
source,
|
|
1189
|
+
TopicType.tx,
|
|
1190
|
+
stageTimings,
|
|
1191
|
+
);
|
|
911
1192
|
if (result !== TopicValidatorResult.Accept || !tx) {
|
|
912
1193
|
return;
|
|
913
1194
|
}
|
|
914
1195
|
|
|
1196
|
+
// Tx was accepted into pool and will be propagated - just log and record metrics
|
|
915
1197
|
const txHash = tx.getTxHash();
|
|
916
1198
|
const txHashString = txHash.toString();
|
|
917
1199
|
this.logger.verbose(`Received tx ${txHashString} from external peer ${source.toString()} via gossip`, {
|
|
@@ -919,13 +1201,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
919
1201
|
txHash: txHashString,
|
|
920
1202
|
});
|
|
921
1203
|
|
|
922
|
-
if (this.config.dropTransactions && randomInt(1000) < this.config.dropTransactionsProbability * 1000) {
|
|
923
|
-
this.logger.warn(`Intentionally dropping tx ${txHashString} (probability rule)`);
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
1204
|
this.instrumentation.incrementTxReceived(1);
|
|
928
|
-
await this.mempools.txPool.addTxs([tx]);
|
|
929
1205
|
}
|
|
930
1206
|
|
|
931
1207
|
/**
|
|
@@ -938,7 +1214,16 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
938
1214
|
source: PeerId,
|
|
939
1215
|
): Promise<void> {
|
|
940
1216
|
const { result, obj: attestation } = await this.validateReceivedMessage<CheckpointAttestation>(
|
|
941
|
-
() =>
|
|
1217
|
+
() => {
|
|
1218
|
+
const attestation = this.tryDeserialize(() => CheckpointAttestation.fromBuffer(payloadData), msgId, source);
|
|
1219
|
+
if (!attestation) {
|
|
1220
|
+
return Promise.resolve({
|
|
1221
|
+
result: TopicValidatorResult.Reject,
|
|
1222
|
+
severity: PeerErrorSeverity.LowToleranceError,
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
return this.validateAndStoreCheckpointAttestation(source, attestation);
|
|
1226
|
+
},
|
|
942
1227
|
msgId,
|
|
943
1228
|
source,
|
|
944
1229
|
TopicType.checkpoint_attestation,
|
|
@@ -971,48 +1256,61 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
971
1256
|
|
|
972
1257
|
if (validationResult.result === 'reject') {
|
|
973
1258
|
this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
|
|
974
|
-
|
|
975
|
-
return { result: TopicValidatorResult.Reject };
|
|
1259
|
+
return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
|
|
976
1260
|
}
|
|
977
1261
|
|
|
978
1262
|
if (validationResult.result === 'ignore') {
|
|
979
1263
|
return { result: TopicValidatorResult.Ignore, obj: attestation };
|
|
980
1264
|
}
|
|
981
1265
|
|
|
982
|
-
// Get committee size for the attestation's slot
|
|
983
|
-
const slot = attestation.payload.header.slotNumber;
|
|
984
|
-
const { committee } = await this.epochCache.getCommittee(slot);
|
|
985
|
-
const committeeSize = committee?.length ?? 0;
|
|
986
|
-
|
|
987
1266
|
// Try to add the attestation: this handles existence check, cap check, and adding in one call
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1267
|
+
// count is the number of attestations by this signer for this slot (for duplicate detection)
|
|
1268
|
+
const slot = attestation.payload.header.slotNumber;
|
|
1269
|
+
const { added, alreadyExists, count } =
|
|
1270
|
+
await this.mempools.attestationPool.tryAddCheckpointAttestation(attestation);
|
|
992
1271
|
|
|
993
1272
|
this.logger.trace(`Validate propagated checkpoint attestation`, {
|
|
994
1273
|
added,
|
|
995
1274
|
alreadyExists,
|
|
1275
|
+
count,
|
|
996
1276
|
[Attributes.SLOT_NUMBER]: slot.toString(),
|
|
997
1277
|
[Attributes.P2P_ID]: peerId.toString(),
|
|
998
1278
|
});
|
|
999
1279
|
|
|
1000
|
-
//
|
|
1280
|
+
// Exact same attestation received, no need to re-broadcast
|
|
1001
1281
|
if (alreadyExists) {
|
|
1002
1282
|
return { result: TopicValidatorResult.Ignore, obj: attestation };
|
|
1003
1283
|
}
|
|
1004
1284
|
|
|
1005
|
-
// Could not add (cap reached),
|
|
1285
|
+
// Could not add (cap reached for signer), penalize and do not re-broadcast
|
|
1006
1286
|
if (!added) {
|
|
1007
|
-
this.logger.warn(`
|
|
1287
|
+
this.logger.warn(`Rejecting checkpoint attestation due to cap`, {
|
|
1008
1288
|
slot: slot.toString(),
|
|
1009
1289
|
archive: attestation.archive.toString(),
|
|
1010
1290
|
source: peerId.toString(),
|
|
1291
|
+
attester: attestation.getSender()?.toString(),
|
|
1292
|
+
count,
|
|
1011
1293
|
});
|
|
1012
|
-
return { result: TopicValidatorResult.
|
|
1294
|
+
return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError };
|
|
1013
1295
|
}
|
|
1014
1296
|
|
|
1015
|
-
//
|
|
1297
|
+
// Check if this is a duplicate attestation (signer attested to a different proposal at the same slot)
|
|
1298
|
+
// count is the number of attestations by this signer for this slot
|
|
1299
|
+
if (count === 2) {
|
|
1300
|
+
const attester = attestation.getSender();
|
|
1301
|
+
if (attester) {
|
|
1302
|
+
this.logger.warn(`Detected duplicate attestation (equivocation) at slot ${slot}`, {
|
|
1303
|
+
slot: slot.toString(),
|
|
1304
|
+
archive: attestation.archive.toString(),
|
|
1305
|
+
source: peerId.toString(),
|
|
1306
|
+
attester: attester.toString(),
|
|
1307
|
+
});
|
|
1308
|
+
this.duplicateAttestationCallback?.({ slot, attester });
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// Attestation was added successfully - accept it so other nodes can also detect the equivocation
|
|
1313
|
+
this.checkpointAttestationCallback?.(attestation);
|
|
1016
1314
|
return { result: TopicValidatorResult.Accept, obj: attestation };
|
|
1017
1315
|
}
|
|
1018
1316
|
|
|
@@ -1020,20 +1318,21 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1020
1318
|
const {
|
|
1021
1319
|
result,
|
|
1022
1320
|
obj: block,
|
|
1023
|
-
metadata: { isEquivocated } = {},
|
|
1024
|
-
} = await this.validateReceivedMessage<BlockProposal, { isEquivocated: boolean }>(
|
|
1321
|
+
metadata: { isEquivocated, isOversized } = {},
|
|
1322
|
+
} = await this.validateReceivedMessage<BlockProposal, { isEquivocated: boolean; isOversized: boolean }>(
|
|
1025
1323
|
() => this.validateAndStoreBlockProposal(source, BlockProposal.fromBuffer(payloadData)),
|
|
1026
1324
|
msgId,
|
|
1027
1325
|
source,
|
|
1028
1326
|
TopicType.block_proposal,
|
|
1029
1327
|
);
|
|
1030
1328
|
|
|
1031
|
-
// If not accepted or
|
|
1032
|
-
|
|
1329
|
+
// If not accepted, equivocated, or oversized, return. Oversized proposals are re-broadcast as
|
|
1330
|
+
// slashing evidence but never attested or processed.
|
|
1331
|
+
if (result !== TopicValidatorResult.Accept || !block || isEquivocated || isOversized) {
|
|
1033
1332
|
return;
|
|
1034
1333
|
}
|
|
1035
1334
|
|
|
1036
|
-
await this.processValidBlockProposal(block, source);
|
|
1335
|
+
await this.processValidBlockProposal(ValidatedBlockProposal(block), source);
|
|
1037
1336
|
}
|
|
1038
1337
|
|
|
1039
1338
|
/** Validates a block proposal. Triggers a penalization to the peer that sent it if invalid. Adds to the mempool if valid. */
|
|
@@ -1044,13 +1343,12 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1044
1343
|
protected async validateAndStoreBlockProposal(
|
|
1045
1344
|
peerId: PeerId,
|
|
1046
1345
|
block: BlockProposal,
|
|
1047
|
-
): Promise<ReceivedMessageValidationResult<BlockProposal, { isEquivocated: boolean }>> {
|
|
1346
|
+
): Promise<ReceivedMessageValidationResult<BlockProposal, { isEquivocated: boolean; isOversized: boolean }>> {
|
|
1048
1347
|
const validationResult = await this.blockProposalValidator.validate(block);
|
|
1049
1348
|
|
|
1050
1349
|
if (validationResult.result === 'reject') {
|
|
1051
1350
|
this.logger.warn(`Penalizing peer ${peerId} for block proposal validation failure`);
|
|
1052
|
-
|
|
1053
|
-
return { result: TopicValidatorResult.Reject };
|
|
1351
|
+
return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
|
|
1054
1352
|
}
|
|
1055
1353
|
|
|
1056
1354
|
if (validationResult.result === 'ignore') {
|
|
@@ -1058,8 +1356,14 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1058
1356
|
}
|
|
1059
1357
|
|
|
1060
1358
|
// Try to add the proposal: this handles existence check, cap check, and adding in one call
|
|
1061
|
-
const { added, alreadyExists,
|
|
1062
|
-
const isEquivocated =
|
|
1359
|
+
const { added, alreadyExists, count } = await this.mempools.attestationPool.tryAddBlockProposal(block);
|
|
1360
|
+
const isEquivocated = count !== undefined && count > 1;
|
|
1361
|
+
// An oversized proposal (index at or beyond the consensus per-checkpoint limit) is structurally valid
|
|
1362
|
+
// proposer misbehavior: it is stored and re-broadcast as slashing evidence but never processed or
|
|
1363
|
+
// attested to. No-ops when maxBlocksPerCheckpoint is unset (local/test).
|
|
1364
|
+
const isOversized =
|
|
1365
|
+
this.config.maxBlocksPerCheckpoint !== undefined &&
|
|
1366
|
+
block.indexWithinCheckpoint >= this.config.maxBlocksPerCheckpoint;
|
|
1063
1367
|
|
|
1064
1368
|
// Duplicate proposal received, no need to re-broadcast
|
|
1065
1369
|
if (alreadyExists) {
|
|
@@ -1069,20 +1373,39 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1069
1373
|
proposer: block.getSender()?.toString(),
|
|
1070
1374
|
source: peerId.toString(),
|
|
1071
1375
|
});
|
|
1072
|
-
return { result: TopicValidatorResult.Ignore, obj: block, metadata: { isEquivocated } };
|
|
1376
|
+
return { result: TopicValidatorResult.Ignore, obj: block, metadata: { isEquivocated, isOversized } };
|
|
1073
1377
|
}
|
|
1074
1378
|
|
|
1075
1379
|
// Too many blocks received for this slot and index, penalize peer and do not re-broadcast
|
|
1076
1380
|
if (!added) {
|
|
1077
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
|
|
1078
1381
|
this.logger.warn(`Penalizing peer for block proposal exceeding per-position cap`, {
|
|
1079
1382
|
...block.toBlockInfo(),
|
|
1080
1383
|
indexWithinCheckpoint: block.indexWithinCheckpoint,
|
|
1081
|
-
|
|
1384
|
+
count,
|
|
1082
1385
|
proposer: block.getSender()?.toString(),
|
|
1083
1386
|
source: peerId.toString(),
|
|
1084
1387
|
});
|
|
1085
|
-
return {
|
|
1388
|
+
return {
|
|
1389
|
+
result: TopicValidatorResult.Reject,
|
|
1390
|
+
metadata: { isEquivocated, isOversized },
|
|
1391
|
+
severity: PeerErrorSeverity.HighToleranceError,
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// The proposal was stored: if oversized, invoke the oversized callback so the proposer can be
|
|
1396
|
+
// slashed. Fired alongside (not instead of) equivocation detection below.
|
|
1397
|
+
if (isOversized) {
|
|
1398
|
+
const proposer = block.getSender();
|
|
1399
|
+
if (proposer) {
|
|
1400
|
+
this.logger.warn(`Detected oversized block proposal at slot ${block.slotNumber}`, {
|
|
1401
|
+
...block.toBlockInfo(),
|
|
1402
|
+
indexWithinCheckpoint: block.indexWithinCheckpoint,
|
|
1403
|
+
maxBlocksPerCheckpoint: this.config.maxBlocksPerCheckpoint,
|
|
1404
|
+
source: peerId.toString(),
|
|
1405
|
+
proposer: proposer.toString(),
|
|
1406
|
+
});
|
|
1407
|
+
this.oversizedProposalCallback?.({ slot: block.slotNumber, proposer });
|
|
1408
|
+
}
|
|
1086
1409
|
}
|
|
1087
1410
|
|
|
1088
1411
|
// If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
|
|
@@ -1095,14 +1418,14 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1095
1418
|
proposer: proposer?.toString(),
|
|
1096
1419
|
});
|
|
1097
1420
|
// Invoke the duplicate callback on the first duplicate spotted only
|
|
1098
|
-
if (proposer &&
|
|
1421
|
+
if (proposer && count === 2) {
|
|
1099
1422
|
this.duplicateProposalCallback?.({ slot: block.slotNumber, proposer, type: 'block' });
|
|
1100
1423
|
}
|
|
1101
|
-
return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated } };
|
|
1424
|
+
return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated, isOversized } };
|
|
1102
1425
|
}
|
|
1103
1426
|
|
|
1104
1427
|
// Otherwise, we're good to go!
|
|
1105
|
-
return { result: TopicValidatorResult.Accept, obj: block };
|
|
1428
|
+
return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated: false, isOversized } };
|
|
1106
1429
|
}
|
|
1107
1430
|
|
|
1108
1431
|
// REFACTOR(palla): This method should be moved to the p2p_client or to a separate component,
|
|
@@ -1112,7 +1435,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1112
1435
|
[Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
|
|
1113
1436
|
[Attributes.P2P_ID]: await block.p2pMessageLoggingIdentifier().then(i => i.toString()),
|
|
1114
1437
|
}))
|
|
1115
|
-
protected async processValidBlockProposal(block:
|
|
1438
|
+
protected async processValidBlockProposal(block: ValidatedBlockProposal, sender: PeerId) {
|
|
1116
1439
|
const slot = block.slotNumber;
|
|
1117
1440
|
this.logger.verbose(`Received block proposal for slot ${slot} from external peer ${sender.toString()}.`, {
|
|
1118
1441
|
p2pMessageIdentifier: await block.p2pMessageLoggingIdentifier(),
|
|
@@ -1120,14 +1443,32 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1120
1443
|
...block.toBlockInfo(),
|
|
1121
1444
|
});
|
|
1122
1445
|
|
|
1123
|
-
// Mark the txs in this proposal as
|
|
1124
|
-
await this.mempools.txPool.
|
|
1446
|
+
// Mark the txs in this proposal as protected
|
|
1447
|
+
await this.mempools.txPool.protectTxs(block.txHashes, block.blockHeader);
|
|
1125
1448
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1449
|
+
if (!(await this.tryBlockReceivedCallback(block, sender))) {
|
|
1450
|
+
// Release the protections this proposal created so its txs return to pending. Only entries still
|
|
1451
|
+
// keyed to this slot are cleared, so a tx referenced by a live proposal at another slot stays protected.
|
|
1452
|
+
await this.mempools.txPool.unprotectTxs(block.txHashes, slot);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* Runs the block received callback to validate a proposal, and returns whether it was accepted.
|
|
1458
|
+
* A callback that throws is reported as a rejection: the proposal is no more usable than one explicitly
|
|
1459
|
+
* rejected, and letting the error escape would leave the proposal's tx protections in place.
|
|
1460
|
+
* Note: Validators do NOT attest to individual blocks, only to checkpoint proposals.
|
|
1461
|
+
*/
|
|
1462
|
+
private async tryBlockReceivedCallback(block: ValidatedBlockProposal, sender: PeerId): Promise<boolean> {
|
|
1463
|
+
try {
|
|
1464
|
+
const isValid = await this.blockReceivedCallback(block, sender);
|
|
1465
|
+
if (!isValid) {
|
|
1466
|
+
this.logger.info(`Block proposal validation failed for block ${block.blockNumber}`, block.toBlockInfo());
|
|
1467
|
+
}
|
|
1468
|
+
return isValid;
|
|
1469
|
+
} catch (err) {
|
|
1470
|
+
this.logger.error(`Error validating block proposal for block ${block.blockNumber}`, err, block.toBlockInfo());
|
|
1471
|
+
return false;
|
|
1131
1472
|
}
|
|
1132
1473
|
}
|
|
1133
1474
|
|
|
@@ -1139,25 +1480,45 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1139
1480
|
const {
|
|
1140
1481
|
result,
|
|
1141
1482
|
obj: checkpoint,
|
|
1142
|
-
metadata: { isEquivocated, processBlock } = {},
|
|
1143
|
-
} = await this.validateReceivedMessage<
|
|
1483
|
+
metadata: { isEquivocated, processBlock, isOversized } = {},
|
|
1484
|
+
} = await this.validateReceivedMessage<
|
|
1485
|
+
CheckpointProposal,
|
|
1486
|
+
{ isEquivocated: boolean; processBlock: boolean; isOversized: boolean }
|
|
1487
|
+
>(
|
|
1144
1488
|
() => this.validateAndStoreCheckpointProposal(source, CheckpointProposal.fromBuffer(payloadData)),
|
|
1145
1489
|
msgId,
|
|
1146
1490
|
source,
|
|
1147
1491
|
TopicType.checkpoint_proposal,
|
|
1148
1492
|
);
|
|
1149
1493
|
|
|
1494
|
+
// An oversized checkpoint is re-broadcast as slashing evidence but never attested or processed.
|
|
1495
|
+
// Process checkpoint proposal if valid and neither equivocated nor oversized.
|
|
1496
|
+
const processCheckpointFn = () =>
|
|
1497
|
+
result === TopicValidatorResult.Accept && checkpoint && !isEquivocated && !isOversized
|
|
1498
|
+
? this.processValidCheckpointProposal(ValidatedCheckpointProposalCore(checkpoint.toCore()), source)
|
|
1499
|
+
: Promise.resolve();
|
|
1500
|
+
|
|
1150
1501
|
// If the checkpoint contained a valid last block, we process it even if the checkpoint itself is to be rejected
|
|
1151
1502
|
// TODO(palla/mbps): Is this ok? Should we be considering a block from a checkpoint that was equivocated?
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1503
|
+
const processBlockFn = () =>
|
|
1504
|
+
processBlock && checkpoint && checkpoint.getBlockProposal()
|
|
1505
|
+
? this.processValidBlockProposal(ValidatedBlockProposal(checkpoint.getBlockProposal()!), source)
|
|
1506
|
+
: Promise.resolve();
|
|
1507
|
+
|
|
1508
|
+
// A node that skips checkpoint validation attests without re-executing the embedded last block, so run
|
|
1509
|
+
// the checkpoint callback first: this creates and broadcasts the attestation before the block is
|
|
1510
|
+
// processed. Otherwise the block's re-execution — which can stall until the re-execution deadline
|
|
1511
|
+
// waiting for a parent that may never arrive — would delay the attestation past the slot's attestation
|
|
1512
|
+
// window, after which peers reject it as stale.
|
|
1513
|
+
if (this.config.skipCheckpointProposalValidation) {
|
|
1514
|
+
await processCheckpointFn();
|
|
1515
|
+
await processBlockFn();
|
|
1157
1516
|
return;
|
|
1158
1517
|
}
|
|
1159
1518
|
|
|
1160
|
-
|
|
1519
|
+
// Process the block first, since it's required for the checkpoint proposal validation.
|
|
1520
|
+
await processBlockFn();
|
|
1521
|
+
await processCheckpointFn();
|
|
1161
1522
|
}
|
|
1162
1523
|
|
|
1163
1524
|
/**
|
|
@@ -1170,13 +1531,17 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1170
1531
|
protected async validateAndStoreCheckpointProposal(
|
|
1171
1532
|
peerId: PeerId,
|
|
1172
1533
|
checkpoint: CheckpointProposal,
|
|
1173
|
-
): Promise<
|
|
1534
|
+
): Promise<
|
|
1535
|
+
ReceivedMessageValidationResult<
|
|
1536
|
+
CheckpointProposal,
|
|
1537
|
+
{ isEquivocated: boolean; processBlock: boolean; isOversized: boolean }
|
|
1538
|
+
>
|
|
1539
|
+
> {
|
|
1174
1540
|
const validationResult = await this.checkpointProposalValidator.validate(checkpoint);
|
|
1175
1541
|
|
|
1176
1542
|
if (validationResult.result === 'reject') {
|
|
1177
1543
|
this.logger.warn(`Penalizing peer ${peerId} for checkpoint proposal validation failure`);
|
|
1178
|
-
|
|
1179
|
-
return { result: TopicValidatorResult.Reject };
|
|
1544
|
+
return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
|
|
1180
1545
|
}
|
|
1181
1546
|
|
|
1182
1547
|
if (validationResult.result === 'ignore') {
|
|
@@ -1186,25 +1551,30 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1186
1551
|
// Extract and try to add the block proposal first if present
|
|
1187
1552
|
const blockProposal = checkpoint.getBlockProposal();
|
|
1188
1553
|
let processBlock = false;
|
|
1554
|
+
let isOversized = false;
|
|
1189
1555
|
if (blockProposal) {
|
|
1190
1556
|
this.logger.debug(`Validating block proposal from propagated checkpoint`, {
|
|
1191
1557
|
[Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
|
|
1192
1558
|
[Attributes.P2P_ID]: peerId.toString(),
|
|
1193
1559
|
});
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
if (result === TopicValidatorResult.Reject || !obj || isEquivocated) {
|
|
1560
|
+
const blockProposalResult = await this.validateAndStoreBlockProposal(peerId, blockProposal);
|
|
1561
|
+
const { obj, metadata: { isEquivocated, isOversized: blockIsOversized } = {} } = blockProposalResult;
|
|
1562
|
+
isOversized = blockIsOversized ?? false;
|
|
1563
|
+
|
|
1564
|
+
if (blockProposalResult.result === TopicValidatorResult.Reject || !obj || isEquivocated) {
|
|
1200
1565
|
this.logger.debug(`Rejecting checkpoint due to invalid last block proposal`, {
|
|
1201
1566
|
[Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
|
|
1202
1567
|
[Attributes.P2P_ID]: peerId.toString(),
|
|
1203
1568
|
isEquivocated,
|
|
1204
|
-
result,
|
|
1569
|
+
result: blockProposalResult.result,
|
|
1205
1570
|
});
|
|
1206
|
-
return {
|
|
1207
|
-
|
|
1571
|
+
return {
|
|
1572
|
+
result: TopicValidatorResult.Reject,
|
|
1573
|
+
severity:
|
|
1574
|
+
'severity' in blockProposalResult ? blockProposalResult.severity : PeerErrorSeverity.MidToleranceError,
|
|
1575
|
+
};
|
|
1576
|
+
} else if (blockProposalResult.result === TopicValidatorResult.Accept && obj && !isEquivocated && !isOversized) {
|
|
1577
|
+
// An oversized terminal block is re-broadcast as slashing evidence but never processed.
|
|
1208
1578
|
processBlock = true;
|
|
1209
1579
|
}
|
|
1210
1580
|
}
|
|
@@ -1212,8 +1582,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1212
1582
|
// Try to add the checkpoint proposal core: this handles existence check, cap check, and adding in one call
|
|
1213
1583
|
const checkpointCore = checkpoint.toCore();
|
|
1214
1584
|
const tryAddResult = await this.mempools.attestationPool.tryAddCheckpointProposal(checkpointCore);
|
|
1215
|
-
const { added, alreadyExists,
|
|
1216
|
-
const isEquivocated =
|
|
1585
|
+
const { added, alreadyExists, count } = tryAddResult;
|
|
1586
|
+
const isEquivocated = count !== undefined && count > 1;
|
|
1217
1587
|
|
|
1218
1588
|
// Duplicate proposal received, do not re-broadcast
|
|
1219
1589
|
if (alreadyExists) {
|
|
@@ -1224,20 +1594,24 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1224
1594
|
return {
|
|
1225
1595
|
result: TopicValidatorResult.Ignore,
|
|
1226
1596
|
obj: checkpoint,
|
|
1227
|
-
metadata: { isEquivocated, processBlock },
|
|
1597
|
+
metadata: { isEquivocated, processBlock, isOversized },
|
|
1228
1598
|
};
|
|
1229
1599
|
}
|
|
1230
1600
|
|
|
1231
|
-
// Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast
|
|
1601
|
+
// Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast.
|
|
1232
1602
|
// Note: We still return the checkpoint obj so the lastBlock can be processed if valid
|
|
1233
1603
|
if (!added) {
|
|
1234
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
|
|
1235
1604
|
this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
|
|
1236
1605
|
...checkpoint.toCheckpointInfo(),
|
|
1237
|
-
|
|
1606
|
+
count,
|
|
1238
1607
|
source: peerId.toString(),
|
|
1239
1608
|
});
|
|
1240
|
-
return {
|
|
1609
|
+
return {
|
|
1610
|
+
result: TopicValidatorResult.Reject,
|
|
1611
|
+
obj: checkpoint,
|
|
1612
|
+
metadata: { isEquivocated, processBlock, isOversized },
|
|
1613
|
+
severity: PeerErrorSeverity.HighToleranceError,
|
|
1614
|
+
};
|
|
1241
1615
|
}
|
|
1242
1616
|
|
|
1243
1617
|
// If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
|
|
@@ -1250,18 +1624,22 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1250
1624
|
proposer: proposer?.toString(),
|
|
1251
1625
|
});
|
|
1252
1626
|
// Invoke the duplicate callback on the first duplicate spotted only
|
|
1253
|
-
if (proposer &&
|
|
1627
|
+
if (proposer && count === 2) {
|
|
1254
1628
|
this.duplicateProposalCallback?.({ slot: checkpoint.slotNumber, proposer, type: 'checkpoint' });
|
|
1255
1629
|
}
|
|
1256
1630
|
return {
|
|
1257
1631
|
result: TopicValidatorResult.Accept,
|
|
1258
1632
|
obj: checkpoint,
|
|
1259
|
-
metadata: { isEquivocated, processBlock },
|
|
1633
|
+
metadata: { isEquivocated, processBlock, isOversized },
|
|
1260
1634
|
};
|
|
1261
1635
|
}
|
|
1262
1636
|
|
|
1263
1637
|
// Otherwise, we're good to go!
|
|
1264
|
-
return {
|
|
1638
|
+
return {
|
|
1639
|
+
result: TopicValidatorResult.Accept,
|
|
1640
|
+
obj: checkpoint,
|
|
1641
|
+
metadata: { processBlock, isEquivocated, isOversized },
|
|
1642
|
+
};
|
|
1265
1643
|
}
|
|
1266
1644
|
|
|
1267
1645
|
/**
|
|
@@ -1273,7 +1651,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1273
1651
|
[Attributes.BLOCK_ARCHIVE]: checkpoint.archive.toString(),
|
|
1274
1652
|
[Attributes.P2P_ID]: await checkpoint.p2pMessageLoggingIdentifier().then(i => i.toString()),
|
|
1275
1653
|
}))
|
|
1276
|
-
protected async processValidCheckpointProposal(checkpoint:
|
|
1654
|
+
protected async processValidCheckpointProposal(checkpoint: ValidatedCheckpointProposalCore, sender: PeerId) {
|
|
1277
1655
|
const slot = checkpoint.slotNumber;
|
|
1278
1656
|
this.logger.verbose(`Received checkpoint proposal for slot ${slot} from external peer ${sender.toString()}.`, {
|
|
1279
1657
|
p2pMessageIdentifier: await checkpoint.p2pMessageLoggingIdentifier(),
|
|
@@ -1282,9 +1660,11 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1282
1660
|
source: sender.toString(),
|
|
1283
1661
|
});
|
|
1284
1662
|
|
|
1663
|
+
await this.allNodesCheckpointReceivedCallback(checkpoint, sender);
|
|
1664
|
+
|
|
1285
1665
|
// Call the checkpoint received callback with the core version (without lastBlock)
|
|
1286
1666
|
// to validate and potentially generate attestations
|
|
1287
|
-
const attestations = await this.
|
|
1667
|
+
const attestations = await this.validatorCheckpointReceivedCallback(checkpoint, sender);
|
|
1288
1668
|
if (attestations && attestations.length > 0) {
|
|
1289
1669
|
// If the callback returned attestations, add them to the pool and propagate them
|
|
1290
1670
|
await this.mempools.attestationPool.addOwnCheckpointAttestations(attestations);
|
|
@@ -1307,82 +1687,87 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1307
1687
|
}
|
|
1308
1688
|
|
|
1309
1689
|
/**
|
|
1310
|
-
* Validate the requested block transactions
|
|
1690
|
+
* Validate the requested block transactions request-response consistency.
|
|
1691
|
+
* It does NOT validate the transactions themselves.
|
|
1311
1692
|
* @param request - The block transactions request.
|
|
1312
1693
|
* @param response - The block transactions response.
|
|
1313
1694
|
* @param peerId - The ID of the peer that made the request.
|
|
1314
|
-
* @returns True if the
|
|
1695
|
+
* @returns True if the request-response is consistent, false otherwise.
|
|
1315
1696
|
*/
|
|
1316
|
-
@trackSpan('Libp2pService.
|
|
1697
|
+
@trackSpan('Libp2pService.validateRequestedBlockTxsConsistency', request => ({
|
|
1317
1698
|
[Attributes.BLOCK_ARCHIVE]: request.archiveRoot.toString(),
|
|
1318
1699
|
}))
|
|
1319
|
-
protected async
|
|
1700
|
+
protected async validateRequestedBlockTxsConsistency(
|
|
1320
1701
|
request: BlockTxsRequest,
|
|
1321
1702
|
response: BlockTxsResponse,
|
|
1322
1703
|
peerId: PeerId,
|
|
1323
1704
|
): Promise<boolean> {
|
|
1324
|
-
const requestedTxValidator = this.createRequestedTxValidator();
|
|
1325
|
-
|
|
1326
1705
|
try {
|
|
1327
|
-
|
|
1706
|
+
// Check for duplicates txs (or hashes) in the response.
|
|
1707
|
+
const returnedHashes = response.txs.map(tx => tx.getTxHash().toString());
|
|
1708
|
+
const uniqueReturned = new Set(returnedHashes.map(h => h.toString()));
|
|
1709
|
+
if (uniqueReturned.size !== returnedHashes.length) {
|
|
1328
1710
|
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
1329
|
-
throw new ValidationError(
|
|
1330
|
-
|
|
1711
|
+
throw new ValidationError(`Received duplicate txs in block txs response`);
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// We get the block tx hashes from the proposal or the archiver.
|
|
1715
|
+
const proposal = await this.mempools.attestationPool.getBlockProposalByArchive(request.archiveRoot.toString());
|
|
1716
|
+
const blockTxHashes =
|
|
1717
|
+
proposal?.txHashes ??
|
|
1718
|
+
(await this.archiver.getBlock({ archive: request.archiveRoot }))?.body.txEffects.map(e => e.txHash);
|
|
1719
|
+
|
|
1720
|
+
// If we don't have the block, we can't verify membership/order of the returned txs.
|
|
1721
|
+
if (!blockTxHashes) {
|
|
1722
|
+
// This shouldn't happen, since we asked for the block (proposal).
|
|
1723
|
+
// Neither a local proposal nor an archived block: we cannot verify membership/order of the
|
|
1724
|
+
// returned txs. This is a local-state gap, not a peer fault, so we do not penalize.
|
|
1725
|
+
this.logger.warn(
|
|
1726
|
+
`Block ${request.archiveRoot.toString()} not found in attestation pool or archiver; cannot validate membership/order of returned txs`,
|
|
1331
1727
|
);
|
|
1728
|
+
// NOTE: We mark the response as invalid, given the limitations of a true/false return value.
|
|
1729
|
+
return false;
|
|
1332
1730
|
}
|
|
1333
1731
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1732
|
+
// Verify that the returned tx hashes are a subset of (tx hashes by index) U (explicitly requested tx hashes).
|
|
1733
|
+
const uniqueRequestedHashes = new Set([
|
|
1734
|
+
// tx hashes requested by index.
|
|
1735
|
+
...request.txIndices.getTrueIndices().map(i => blockTxHashes[i].toString()),
|
|
1736
|
+
// explicitly requested tx hashes.
|
|
1737
|
+
...request.txHashes.map(h => h.toString()),
|
|
1738
|
+
]);
|
|
1739
|
+
if (!returnedHashes.every(h => uniqueRequestedHashes.has(h))) {
|
|
1740
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
1336
1741
|
throw new ValidationError(
|
|
1337
|
-
|
|
1742
|
+
'Returned txs should be a subset of (tx hashes by index) U (explicitly requested tx hashes)',
|
|
1338
1743
|
);
|
|
1339
1744
|
}
|
|
1340
1745
|
|
|
1341
|
-
//
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
const returnedHashes = await Promise.all(response.txs.map(tx => tx.getTxHash().toString()));
|
|
1347
|
-
const uniqueReturned = new Set(returnedHashes.map(h => h.toString()));
|
|
1348
|
-
if (uniqueReturned.size !== returnedHashes.length) {
|
|
1349
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
1350
|
-
throw new ValidationError(`Received duplicate txs in block txs response`);
|
|
1746
|
+
// Tx indices information is optional, but if present, we need to verify a few things.
|
|
1747
|
+
if (!response.peerHasBlock()) {
|
|
1748
|
+
this.logger.debug(`Peer ${peerId.toString()} signalled missing block`);
|
|
1749
|
+
return true;
|
|
1351
1750
|
}
|
|
1352
|
-
|
|
1751
|
+
|
|
1752
|
+
// If the response has a non-empty bitvector, it needs to be consistent with the number of txs in the block.
|
|
1753
|
+
if (response.txIndices.getLength() !== blockTxHashes.length) {
|
|
1353
1754
|
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
1354
1755
|
throw new ValidationError(
|
|
1355
|
-
`Received
|
|
1756
|
+
`Received block txs with mismatched bitvector length: expected ${blockTxHashes.length}, got ${response.txIndices.getLength()}`,
|
|
1356
1757
|
);
|
|
1357
1758
|
}
|
|
1358
1759
|
|
|
1359
|
-
//
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
);
|
|
1369
|
-
const allowedIndexSet = new Set(intersectIdx);
|
|
1370
|
-
const indices = returnedHashes.map(h => hashToIndexInProposal.get(h));
|
|
1371
|
-
const allAllowed = indices.every(idx => idx !== undefined && allowedIndexSet.has(idx));
|
|
1372
|
-
const strictlyIncreasing = indices.every((idx, i) => (i === 0 ? idx !== undefined : idx! > indices[i - 1]!));
|
|
1373
|
-
if (!allAllowed || !strictlyIncreasing) {
|
|
1374
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
1375
|
-
throw new ValidationError('Returned txs do not match expected subset/order for requested indices');
|
|
1376
|
-
}
|
|
1377
|
-
} else {
|
|
1378
|
-
// No local proposal, cannot check the membership/order of the returned txs
|
|
1379
|
-
this.logger.warn(
|
|
1380
|
-
`Block proposal not found for archive root ${request.archiveRoot.toString()}; cannot validate membership/order of returned txs`,
|
|
1381
|
-
);
|
|
1382
|
-
return false;
|
|
1760
|
+
// For every index we requested that the peer claims to have (per its bitvector), the
|
|
1761
|
+
// corresponding tx must be present in the response. A peer advertising availability but
|
|
1762
|
+
// withholding the tx is inconsistent.
|
|
1763
|
+
const returnedHashSet = new Set(returnedHashes);
|
|
1764
|
+
const expectedIndices = request.txIndices.getTrueIndices().filter(i => response.txIndices.isSet(i));
|
|
1765
|
+
const missingExpected = expectedIndices.some(i => !returnedHashSet.has(blockTxHashes[i].toString()));
|
|
1766
|
+
if (missingExpected) {
|
|
1767
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
1768
|
+
throw new ValidationError('Peer advertised requested txs via indices but did not return them');
|
|
1383
1769
|
}
|
|
1384
1770
|
|
|
1385
|
-
await Promise.all(response.txs.map(tx => this.validateRequestedTx(tx, peerId, requestedTxValidator)));
|
|
1386
1771
|
return true;
|
|
1387
1772
|
} catch (e: any) {
|
|
1388
1773
|
if (e instanceof ValidationError) {
|
|
@@ -1395,156 +1780,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1395
1780
|
}
|
|
1396
1781
|
}
|
|
1397
1782
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
*
|
|
1401
|
-
* The core component of this validator is that each tx hash MUST match the requested tx hash,
|
|
1402
|
-
* In order to perform this check, the tx proof must be verified.
|
|
1403
|
-
*
|
|
1404
|
-
* Note: This function is called from within `ReqResp.sendRequest` as part of the
|
|
1405
|
-
* ReqRespSubProtocol.TX subprotocol validation.
|
|
1406
|
-
*
|
|
1407
|
-
* @param requestedTxHash - The collection of the txs that was requested.
|
|
1408
|
-
* @param responseTx - The collection of txs that was received as a response to the request.
|
|
1409
|
-
* @param peerId - The peer ID of the peer that sent the tx.
|
|
1410
|
-
* @returns True if the whole collection of txs is valid, false otherwise.
|
|
1411
|
-
*/
|
|
1412
|
-
@trackSpan('Libp2pService.validateRequestedTx', (requestedTxHash, _responseTx) => ({
|
|
1413
|
-
[Attributes.TX_HASH]: requestedTxHash.toString(),
|
|
1414
|
-
}))
|
|
1415
|
-
private async validateRequestedTxs(requestedTxHash: TxHash[], responseTx: Tx[], peerId: PeerId): Promise<boolean> {
|
|
1416
|
-
const requested = new Set(requestedTxHash.map(h => h.toString()));
|
|
1417
|
-
const requestedTxValidator = this.createRequestedTxValidator();
|
|
1418
|
-
|
|
1419
|
-
//TODO: (mralj) - this is somewhat naive implementation, if single tx is invalid we consider the whole response invalid.
|
|
1420
|
-
// I think we should still extract the valid txs and return them, so that we can still use the response.
|
|
1421
|
-
try {
|
|
1422
|
-
await Promise.all(responseTx.map(tx => this.validateRequestedTx(tx, peerId, requestedTxValidator, requested)));
|
|
1423
|
-
return true;
|
|
1424
|
-
} catch (e: any) {
|
|
1425
|
-
if (e instanceof ValidationError) {
|
|
1426
|
-
this.logger.warn(`Failed to validate requested txs from peer ${peerId.toString()}, reason ${e.message}`);
|
|
1427
|
-
} else {
|
|
1428
|
-
this.logger.error(`Error during validation of requested txs`, e);
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
return false;
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
/**
|
|
1436
|
-
* Validates a BLOCK response.
|
|
1437
|
-
*
|
|
1438
|
-
* If a local copy exists, enforces hash equality. If missing, rejects (no penalty) since the hash cannot be verified.
|
|
1439
|
-
* Penalizes on block number mismatch or hash mismatch.
|
|
1440
|
-
*
|
|
1441
|
-
* @param requestedBlockNumber - The requested block number.
|
|
1442
|
-
* @param responseBlock - The block returned by the peer.
|
|
1443
|
-
* @param peerId - The peer that returned the block.
|
|
1444
|
-
* @returns True if the response is valid, false otherwise.
|
|
1445
|
-
*/
|
|
1446
|
-
@trackSpan('Libp2pService.validateRequestedBlock', (requestedBlockNumber, _responseBlock) => ({
|
|
1447
|
-
[Attributes.BLOCK_NUMBER]: requestedBlockNumber.toString(),
|
|
1448
|
-
}))
|
|
1449
|
-
protected async validateRequestedBlock(
|
|
1450
|
-
requestedBlockNumber: Fr,
|
|
1451
|
-
responseBlock: L2Block,
|
|
1452
|
-
peerId: PeerId,
|
|
1453
|
-
): Promise<boolean> {
|
|
1454
|
-
try {
|
|
1455
|
-
const reqNum = Number(requestedBlockNumber.toString());
|
|
1456
|
-
if (responseBlock.number !== reqNum) {
|
|
1457
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
1458
|
-
return false;
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
const local = await this.archiver.getBlock(BlockNumber(reqNum));
|
|
1462
|
-
if (!local) {
|
|
1463
|
-
// We are missing the local block; we cannot verify the hash yet. Reject without penalizing.
|
|
1464
|
-
// TODO: Consider extending this validator to accept an expected hash or
|
|
1465
|
-
// performing quorum-based checks when using P2P syncing prior to L1 sync.
|
|
1466
|
-
this.logger.warn(`Local block ${reqNum} not found; rejecting BLOCK response without hash verification`);
|
|
1467
|
-
return false;
|
|
1468
|
-
}
|
|
1469
|
-
const [localHash, respHash] = await Promise.all([local.hash(), responseBlock.hash()]);
|
|
1470
|
-
if (!localHash.equals(respHash)) {
|
|
1471
|
-
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
1472
|
-
return false;
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
return true;
|
|
1476
|
-
} catch (e) {
|
|
1477
|
-
this.logger.warn(`Error validating requested block`, e);
|
|
1478
|
-
return false;
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
protected async validateRequestedTx(
|
|
1483
|
-
tx: Tx,
|
|
1484
|
-
peerId: PeerId,
|
|
1485
|
-
txValidator: TxValidator,
|
|
1486
|
-
requested?: Set<`0x${string}`>,
|
|
1487
|
-
) {
|
|
1488
|
-
const penalize = (severity: PeerErrorSeverity) => this.peerManager.penalizePeer(peerId, severity);
|
|
1489
|
-
if (requested && !requested.has(tx.getTxHash().toString())) {
|
|
1490
|
-
penalize(PeerErrorSeverity.MidToleranceError);
|
|
1491
|
-
throw new ValidationError(`Received tx with hash ${tx.getTxHash().toString()} that was not requested.`);
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
const { result } = await txValidator.validateTx(tx);
|
|
1495
|
-
if (result === 'invalid') {
|
|
1496
|
-
penalize(PeerErrorSeverity.LowToleranceError);
|
|
1497
|
-
throw new ValidationError(`Received tx with hash ${tx.getTxHash().toString()} that is invalid.`);
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
protected createRequestedTxValidator(): TxValidator {
|
|
1502
|
-
return createTxReqRespValidator(this.proofVerifier, {
|
|
1503
|
-
l1ChainId: this.config.l1ChainId,
|
|
1504
|
-
rollupVersion: this.config.rollupVersion,
|
|
1505
|
-
});
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
@trackSpan('Libp2pService.validatePropagatedTx', tx => ({
|
|
1509
|
-
[Attributes.TX_HASH]: tx.getTxHash().toString(),
|
|
1510
|
-
}))
|
|
1511
|
-
private async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> {
|
|
1512
|
-
const currentBlockNumber = await this.archiver.getBlockNumber();
|
|
1513
|
-
|
|
1514
|
-
// We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
|
|
1515
|
-
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1516
|
-
const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
|
|
1517
|
-
|
|
1518
|
-
for (const validator of messageValidators) {
|
|
1519
|
-
const outcome = await this.runValidations(tx, validator);
|
|
1520
|
-
|
|
1521
|
-
if (outcome.allPassed) {
|
|
1522
|
-
continue;
|
|
1523
|
-
}
|
|
1524
|
-
const { name } = outcome.failure;
|
|
1525
|
-
let { severity } = outcome.failure;
|
|
1526
|
-
|
|
1527
|
-
// Double spend validator has a special case handler
|
|
1528
|
-
if (name === 'doubleSpendValidator') {
|
|
1529
|
-
const txBlockNumber = BlockNumber(currentBlockNumber + 1); // tx is expected to be in the next block
|
|
1530
|
-
severity = await this.handleDoubleSpendFailure(tx, txBlockNumber);
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
this.peerManager.penalizePeer(peerId, severity);
|
|
1534
|
-
return false;
|
|
1535
|
-
}
|
|
1536
|
-
return true;
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
private async getGasFees(blockNumber: BlockNumber): Promise<GasFees> {
|
|
1540
|
-
if (blockNumber === this.feesCache?.blockNumber) {
|
|
1541
|
-
return this.feesCache.gasFees;
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
const header = await this.archiver.getBlockHeader(blockNumber);
|
|
1545
|
-
const gasFees = header?.globalVariables.gasFees ?? GasFees.empty();
|
|
1546
|
-
this.feesCache = { blockNumber, gasFees };
|
|
1547
|
-
return gasFees;
|
|
1783
|
+
private getGasFees(): Promise<GasFees> {
|
|
1784
|
+
return this.blockMinFeesProvider.getCurrentMinFees();
|
|
1548
1785
|
}
|
|
1549
1786
|
|
|
1550
1787
|
/**
|
|
@@ -1558,65 +1795,72 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1558
1795
|
l1ChainId: this.config.l1ChainId,
|
|
1559
1796
|
rollupVersion: this.config.rollupVersion,
|
|
1560
1797
|
proofVerifier: this.proofVerifier,
|
|
1798
|
+
txValidationCache: this.txValidationCache,
|
|
1561
1799
|
},
|
|
1562
1800
|
peerScoring: this.peerManager,
|
|
1801
|
+
validateRequestedBlockTxsConsistency: this.validateRequestedBlockTxsConsistency.bind(this),
|
|
1563
1802
|
};
|
|
1564
1803
|
}
|
|
1565
1804
|
|
|
1566
|
-
public async
|
|
1567
|
-
const
|
|
1805
|
+
public async validateTxsReceivedInBlockProposal(txs: Tx[]): Promise<void> {
|
|
1806
|
+
const validator = createTxValidatorForBlockProposalReceivedTxs(
|
|
1807
|
+
this.proofVerifier,
|
|
1808
|
+
{ l1ChainId: this.config.l1ChainId, rollupVersion: this.config.rollupVersion },
|
|
1809
|
+
this.logger.getBindings(),
|
|
1810
|
+
this.txValidationCache,
|
|
1811
|
+
);
|
|
1568
1812
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1813
|
+
const results = await Promise.all(
|
|
1814
|
+
txs.map(async tx => ({ txHash: tx.getTxHash(), result: await validator.validateTx(tx) })),
|
|
1815
|
+
);
|
|
1572
1816
|
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
for (const validator of messageValidators) {
|
|
1576
|
-
const outcome = await this.runValidations(tx, validator);
|
|
1577
|
-
if (!outcome.allPassed) {
|
|
1578
|
-
throw new Error('Invalid tx detected', { cause: { outcome } });
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
}),
|
|
1817
|
+
const invalidTxs = results.flatMap(({ txHash, result }) =>
|
|
1818
|
+
result.result === 'invalid' ? [{ txHash, reasons: result.reason }] : [],
|
|
1582
1819
|
);
|
|
1820
|
+
|
|
1821
|
+
if (invalidTxs.length > 0) {
|
|
1822
|
+
throw new InvalidBlockProposalTxsError(invalidTxs);
|
|
1823
|
+
}
|
|
1583
1824
|
}
|
|
1584
1825
|
|
|
1585
|
-
/**
|
|
1586
|
-
|
|
1587
|
-
*
|
|
1588
|
-
* Each validator is a pair of a validator and a severity.
|
|
1589
|
-
* If a validator fails, the peer is penalized with the severity of the validator.
|
|
1590
|
-
*
|
|
1591
|
-
* @param currentBlockNumber - The current synced block number.
|
|
1592
|
-
* @param nextSlotTimestamp - The timestamp of the next slot (used to validate txs are not expired).
|
|
1593
|
-
* @returns The message validators.
|
|
1594
|
-
*/
|
|
1595
|
-
private async createMessageValidators(
|
|
1826
|
+
/** Creates the first stage (fast) validators for gossiped transactions. */
|
|
1827
|
+
protected async createFirstStageMessageValidators(
|
|
1596
1828
|
currentBlockNumber: BlockNumber,
|
|
1597
1829
|
nextSlotTimestamp: UInt64,
|
|
1598
|
-
): Promise<Record<string,
|
|
1599
|
-
const gasFees = await this.getGasFees(
|
|
1600
|
-
const allowedInSetup =
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1830
|
+
): Promise<Record<string, TransactionValidator>> {
|
|
1831
|
+
const gasFees = await this.getGasFees();
|
|
1832
|
+
const allowedInSetup = [
|
|
1833
|
+
...(await getDefaultAllowedSetupFunctions()),
|
|
1834
|
+
...(this.config.txPublicSetupAllowListExtend ?? []),
|
|
1835
|
+
];
|
|
1836
|
+
const blockNumber = BlockNumber(currentBlockNumber + 1);
|
|
1837
|
+
const l1Constants = await this.archiver.getL1Constants();
|
|
1838
|
+
const networkTxGasLimits = getNetworkTxGasLimits(this.config, l1Constants);
|
|
1839
|
+
|
|
1840
|
+
return createFirstStageTxValidationsForGossipedTransactions(
|
|
1605
1841
|
nextSlotTimestamp,
|
|
1606
|
-
|
|
1842
|
+
blockNumber,
|
|
1607
1843
|
this.worldStateSynchronizer,
|
|
1608
1844
|
gasFees,
|
|
1609
1845
|
this.config.l1ChainId,
|
|
1610
1846
|
this.config.rollupVersion,
|
|
1611
1847
|
protocolContractsHash,
|
|
1612
1848
|
this.archiver,
|
|
1613
|
-
this.proofVerifier,
|
|
1614
1849
|
!this.config.disableTransactions,
|
|
1615
1850
|
allowedInSetup,
|
|
1616
1851
|
this.logger.getBindings(),
|
|
1852
|
+
{
|
|
1853
|
+
maxTxL2Gas: networkTxGasLimits.l2Gas,
|
|
1854
|
+
maxTxDAGas: networkTxGasLimits.daGas,
|
|
1855
|
+
},
|
|
1617
1856
|
);
|
|
1618
1857
|
}
|
|
1619
1858
|
|
|
1859
|
+
/** Creates the second stage (expensive proof verification) validators for gossiped transactions. */
|
|
1860
|
+
protected createSecondStageMessageValidators(): Record<string, TransactionValidator> {
|
|
1861
|
+
return createSecondStageTxValidationsForGossipedTransactions(this.proofVerifier, this.logger.getBindings());
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1620
1864
|
/**
|
|
1621
1865
|
* Run validations on a tx.
|
|
1622
1866
|
* @param tx - The tx to validate.
|
|
@@ -1625,7 +1869,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1625
1869
|
*/
|
|
1626
1870
|
private async runValidations(
|
|
1627
1871
|
tx: Tx,
|
|
1628
|
-
messageValidators: Record<string,
|
|
1872
|
+
messageValidators: Record<string, TransactionValidator>,
|
|
1629
1873
|
): Promise<ValidationOutcome> {
|
|
1630
1874
|
const validationPromises = Object.entries(messageValidators).map(async ([name, { validator, severity }]) => {
|
|
1631
1875
|
const { result } = await validator.validateTx(tx);
|
|
@@ -1634,8 +1878,10 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1634
1878
|
|
|
1635
1879
|
// A promise that resolves when all validations have been run
|
|
1636
1880
|
const allValidations = await Promise.all(validationPromises);
|
|
1637
|
-
const
|
|
1638
|
-
if (
|
|
1881
|
+
const failures = allValidations.filter(x => !x.isValid);
|
|
1882
|
+
if (failures.length > 0) {
|
|
1883
|
+
// Pick the most severe failure (lowest tolerance = harshest penalty)
|
|
1884
|
+
const failed = maxBy(failures, f => PeerErrorSeverityByHarshness.indexOf(f.severity))!;
|
|
1639
1885
|
return {
|
|
1640
1886
|
allPassed: false,
|
|
1641
1887
|
failure: {
|
|
@@ -1688,31 +1934,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1688
1934
|
return PeerErrorSeverity.HighToleranceError;
|
|
1689
1935
|
}
|
|
1690
1936
|
|
|
1691
|
-
/**
|
|
1692
|
-
* Validate a checkpoint attestation.
|
|
1693
|
-
*
|
|
1694
|
-
* @param attestation - The checkpoint attestation to validate.
|
|
1695
|
-
* @returns True if the checkpoint attestation is valid, false otherwise.
|
|
1696
|
-
*/
|
|
1697
|
-
@trackSpan('Libp2pService.validateCheckpointAttestation', async (_, attestation) => ({
|
|
1698
|
-
[Attributes.SLOT_NUMBER]: attestation.payload.header.slotNumber,
|
|
1699
|
-
[Attributes.BLOCK_ARCHIVE]: attestation.archive.toString(),
|
|
1700
|
-
[Attributes.P2P_ID]: await attestation.p2pMessageLoggingIdentifier().then(i => i.toString()),
|
|
1701
|
-
}))
|
|
1702
|
-
public async validateCheckpointAttestation(
|
|
1703
|
-
peerId: PeerId,
|
|
1704
|
-
attestation: CheckpointAttestation,
|
|
1705
|
-
): Promise<P2PValidationResult> {
|
|
1706
|
-
const result = await this.checkpointAttestationValidator.validate(attestation);
|
|
1707
|
-
|
|
1708
|
-
if (result.result === 'reject') {
|
|
1709
|
-
this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
|
|
1710
|
-
this.peerManager.penalizePeer(peerId, result.severity);
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
return result;
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
1937
|
public getPeerScore(peerId: PeerId): number {
|
|
1717
1938
|
return this.node.services.pubsub.score.score(peerId.toString());
|
|
1718
1939
|
}
|