Merge branch 'unstable' into merge-unstable-to-deneb-20230808
# Conflicts: # Cargo.lock # beacon_node/beacon_chain/src/lib.rs # beacon_node/execution_layer/src/engine_api.rs # beacon_node/execution_layer/src/engine_api/http.rs # beacon_node/execution_layer/src/test_utils/mod.rs # beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs # beacon_node/lighthouse_network/src/rpc/handler.rs # beacon_node/lighthouse_network/src/rpc/protocol.rs # beacon_node/lighthouse_network/src/service/utils.rs # beacon_node/lighthouse_network/tests/rpc_tests.rs # beacon_node/network/Cargo.toml # beacon_node/network/src/network_beacon_processor/tests.rs # lcli/src/parse_ssz.rs # scripts/cross/Dockerfile # validator_client/src/block_service.rs # validator_client/src/validator_store.rs
This commit is contained in:
@@ -331,7 +331,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
// compute ideal head rewards
|
||||
let head = get_attestation_component_delta(
|
||||
true,
|
||||
total_balances.previous_epoch_attesters(),
|
||||
total_balances.previous_epoch_head_attesters(),
|
||||
total_balances,
|
||||
base_reward,
|
||||
finality_delay,
|
||||
@@ -353,7 +353,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
// compute ideal source rewards
|
||||
let source = get_attestation_component_delta(
|
||||
true,
|
||||
total_balances.previous_epoch_head_attesters(),
|
||||
total_balances.previous_epoch_attesters(),
|
||||
total_balances,
|
||||
base_reward,
|
||||
finality_delay,
|
||||
|
||||
@@ -35,10 +35,8 @@
|
||||
mod batch;
|
||||
|
||||
use crate::{
|
||||
beacon_chain::{MAXIMUM_GOSSIP_CLOCK_DISPARITY, VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT},
|
||||
metrics,
|
||||
observed_aggregates::ObserveOutcome,
|
||||
observed_attesters::Error as ObservedAttestersError,
|
||||
beacon_chain::VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT, metrics,
|
||||
observed_aggregates::ObserveOutcome, observed_attesters::Error as ObservedAttestersError,
|
||||
BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||
};
|
||||
use bls::verify_signature_sets;
|
||||
@@ -57,8 +55,8 @@ use std::borrow::Cow;
|
||||
use strum::AsRefStr;
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
Attestation, BeaconCommittee, CommitteeIndex, Epoch, EthSpec, Hash256, IndexedAttestation,
|
||||
SelectionProof, SignedAggregateAndProof, Slot, SubnetId,
|
||||
Attestation, BeaconCommittee, ChainSpec, CommitteeIndex, Epoch, EthSpec, Hash256,
|
||||
IndexedAttestation, SelectionProof, SignedAggregateAndProof, Slot, SubnetId,
|
||||
};
|
||||
|
||||
pub use batch::{batch_verify_aggregated_attestations, batch_verify_unaggregated_attestations};
|
||||
@@ -454,7 +452,7 @@ impl<'a, T: BeaconChainTypes> IndexedAggregatedAttestation<'a, T> {
|
||||
// MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
|
||||
//
|
||||
// We do not queue future attestations for later processing.
|
||||
verify_propagation_slot_range(&chain.slot_clock, attestation)?;
|
||||
verify_propagation_slot_range(&chain.slot_clock, attestation, &chain.spec)?;
|
||||
|
||||
// Check the attestation's epoch matches its target.
|
||||
if attestation.data.slot.epoch(T::EthSpec::slots_per_epoch())
|
||||
@@ -722,7 +720,7 @@ impl<'a, T: BeaconChainTypes> IndexedUnaggregatedAttestation<'a, T> {
|
||||
// MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
|
||||
//
|
||||
// We do not queue future attestations for later processing.
|
||||
verify_propagation_slot_range(&chain.slot_clock, attestation)?;
|
||||
verify_propagation_slot_range(&chain.slot_clock, attestation, &chain.spec)?;
|
||||
|
||||
// Check to ensure that the attestation is "unaggregated". I.e., it has exactly one
|
||||
// aggregation bit set.
|
||||
@@ -1037,11 +1035,11 @@ fn verify_head_block_is_known<T: BeaconChainTypes>(
|
||||
pub fn verify_propagation_slot_range<S: SlotClock, E: EthSpec>(
|
||||
slot_clock: &S,
|
||||
attestation: &Attestation<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let attestation_slot = attestation.data.slot;
|
||||
|
||||
let latest_permissible_slot = slot_clock
|
||||
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
||||
.now_with_future_tolerance(spec.maximum_gossip_clock_disparity())
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?;
|
||||
if attestation_slot > latest_permissible_slot {
|
||||
return Err(Error::FutureSlot {
|
||||
@@ -1052,7 +1050,7 @@ pub fn verify_propagation_slot_range<S: SlotClock, E: EthSpec>(
|
||||
|
||||
// Taking advantage of saturating subtraction on `Slot`.
|
||||
let earliest_permissible_slot = slot_clock
|
||||
.now_with_past_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
||||
.now_with_past_tolerance(spec.maximum_gossip_clock_disparity())
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?
|
||||
- E::slots_per_epoch();
|
||||
if attestation_slot < earliest_permissible_slot {
|
||||
|
||||
@@ -259,11 +259,6 @@ pub enum OverrideForkchoiceUpdate {
|
||||
AlreadyApplied,
|
||||
}
|
||||
|
||||
/// The accepted clock drift for nodes gossiping blocks and attestations. See:
|
||||
///
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/p2p-interface.md#configuration
|
||||
pub const MAXIMUM_GOSSIP_CLOCK_DISPARITY: Duration = Duration::from_millis(500);
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AttestationProcessingOutcome {
|
||||
Processed,
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::beacon_chain::{
|
||||
BeaconChain, BeaconChainTypes, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT,
|
||||
MAXIMUM_GOSSIP_CLOCK_DISPARITY, VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
|
||||
VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
|
||||
};
|
||||
use crate::data_availability_checker::AvailabilityCheckError;
|
||||
use crate::kzg_utils::{validate_blob, validate_blobs};
|
||||
@@ -196,7 +196,7 @@ pub fn validate_blob_sidecar_for_gossip<T: BeaconChainTypes>(
|
||||
// Verify that the sidecar is not from a future slot.
|
||||
let latest_permissible_slot = chain
|
||||
.slot_clock
|
||||
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
||||
.now_with_future_tolerance(chain.spec.maximum_gossip_clock_disparity())
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?;
|
||||
if blob_slot > latest_permissible_slot {
|
||||
return Err(GossipBlobError::FutureSlot {
|
||||
|
||||
@@ -65,7 +65,7 @@ use crate::validator_pubkey_cache::ValidatorPubkeyCache;
|
||||
use crate::{
|
||||
beacon_chain::{
|
||||
BeaconForkChoice, ForkChoiceError, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT,
|
||||
MAXIMUM_GOSSIP_CLOCK_DISPARITY, VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
|
||||
VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT,
|
||||
},
|
||||
metrics, BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||
};
|
||||
@@ -786,7 +786,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
// Do not gossip or process blocks from future slots.
|
||||
let present_slot_with_tolerance = chain
|
||||
.slot_clock
|
||||
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
||||
.now_with_future_tolerance(chain.spec.maximum_gossip_clock_disparity())
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?;
|
||||
if block.slot() > present_slot_with_tolerance {
|
||||
return Err(BlockError::FutureSlot {
|
||||
|
||||
@@ -61,7 +61,7 @@ pub use self::beacon_chain::{
|
||||
BeaconStore, ChainSegmentResult, ForkChoiceError, OverrideForkchoiceUpdate,
|
||||
ProduceBlockVerification, StateSkipConfig, WhenSlotSkipped,
|
||||
INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON,
|
||||
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON,
|
||||
};
|
||||
pub use self::beacon_snapshot::BeaconSnapshot;
|
||||
pub use self::chain_config::ChainConfig;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use crate::{
|
||||
beacon_chain::MAXIMUM_GOSSIP_CLOCK_DISPARITY, BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||
};
|
||||
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
|
||||
use derivative::Derivative;
|
||||
use slot_clock::SlotClock;
|
||||
use std::time::Duration;
|
||||
@@ -103,7 +101,8 @@ impl<T: BeaconChainTypes> VerifiedLightClientFinalityUpdate<T> {
|
||||
// verify that enough time has passed for the block to have been propagated
|
||||
match start_time {
|
||||
Some(time) => {
|
||||
if seen_timestamp + MAXIMUM_GOSSIP_CLOCK_DISPARITY < time + one_third_slot_duration
|
||||
if seen_timestamp + chain.spec.maximum_gossip_clock_disparity()
|
||||
< time + one_third_slot_duration
|
||||
{
|
||||
return Err(Error::TooEarly);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use crate::{
|
||||
beacon_chain::MAXIMUM_GOSSIP_CLOCK_DISPARITY, BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||
};
|
||||
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
|
||||
use derivative::Derivative;
|
||||
use eth2::types::Hash256;
|
||||
use slot_clock::SlotClock;
|
||||
@@ -103,7 +101,8 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
|
||||
// verify that enough time has passed for the block to have been propagated
|
||||
match start_time {
|
||||
Some(time) => {
|
||||
if seen_timestamp + MAXIMUM_GOSSIP_CLOCK_DISPARITY < time + one_third_slot_duration
|
||||
if seen_timestamp + chain.spec.maximum_gossip_clock_disparity()
|
||||
< time + one_third_slot_duration
|
||||
{
|
||||
return Err(Error::TooEarly);
|
||||
}
|
||||
|
||||
@@ -86,9 +86,6 @@ pub enum MergeReadiness {
|
||||
#[serde(serialize_with = "serialize_uint256")]
|
||||
current_difficulty: Option<Uint256>,
|
||||
},
|
||||
/// The transition configuration with the EL failed, there might be a problem with
|
||||
/// connectivity, authentication or a difference in configuration.
|
||||
ExchangeTransitionConfigurationFailed { error: String },
|
||||
/// The EL can be reached and has the correct configuration, however it's not yet synced.
|
||||
NotSynced,
|
||||
/// The user has not configured this node to use an execution endpoint.
|
||||
@@ -109,12 +106,6 @@ impl fmt::Display for MergeReadiness {
|
||||
params, current_difficulty
|
||||
)
|
||||
}
|
||||
MergeReadiness::ExchangeTransitionConfigurationFailed { error } => write!(
|
||||
f,
|
||||
"Could not confirm the transition configuration with the \
|
||||
execution endpoint: {:?}",
|
||||
error
|
||||
),
|
||||
MergeReadiness::NotSynced => write!(
|
||||
f,
|
||||
"The execution endpoint is connected and configured, \
|
||||
@@ -155,14 +146,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
/// Attempts to connect to the EL and confirm that it is ready for the merge.
|
||||
pub async fn check_merge_readiness(&self) -> MergeReadiness {
|
||||
if let Some(el) = self.execution_layer.as_ref() {
|
||||
if let Err(e) = el.exchange_transition_configuration(&self.spec).await {
|
||||
// The EL was either unreachable, responded with an error or has a different
|
||||
// configuration.
|
||||
return MergeReadiness::ExchangeTransitionConfigurationFailed {
|
||||
error: format!("{:?}", e),
|
||||
};
|
||||
}
|
||||
|
||||
if !el.is_synced_for_notifier().await {
|
||||
// The EL is not synced.
|
||||
return MergeReadiness::NotSynced;
|
||||
|
||||
@@ -28,10 +28,8 @@
|
||||
|
||||
use crate::observed_attesters::SlotSubcommitteeIndex;
|
||||
use crate::{
|
||||
beacon_chain::{MAXIMUM_GOSSIP_CLOCK_DISPARITY, VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT},
|
||||
metrics,
|
||||
observed_aggregates::ObserveOutcome,
|
||||
BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||
beacon_chain::VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT, metrics,
|
||||
observed_aggregates::ObserveOutcome, BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||
};
|
||||
use bls::{verify_signature_sets, PublicKeyBytes};
|
||||
use derivative::Derivative;
|
||||
@@ -52,6 +50,7 @@ use tree_hash_derive::TreeHash;
|
||||
use types::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT;
|
||||
use types::slot_data::SlotData;
|
||||
use types::sync_committee::Error as SyncCommitteeError;
|
||||
use types::ChainSpec;
|
||||
use types::{
|
||||
sync_committee_contribution::Error as ContributionError, AggregateSignature, BeaconStateError,
|
||||
EthSpec, Hash256, SignedContributionAndProof, Slot, SyncCommitteeContribution,
|
||||
@@ -297,7 +296,7 @@ impl<T: BeaconChainTypes> VerifiedSyncContribution<T> {
|
||||
let subcommittee_index = contribution.subcommittee_index as usize;
|
||||
|
||||
// Ensure sync committee contribution is within the MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance.
|
||||
verify_propagation_slot_range(&chain.slot_clock, contribution)?;
|
||||
verify_propagation_slot_range(&chain.slot_clock, contribution, &chain.spec)?;
|
||||
|
||||
// Validate subcommittee index.
|
||||
if contribution.subcommittee_index >= SYNC_COMMITTEE_SUBNET_COUNT {
|
||||
@@ -460,7 +459,7 @@ impl VerifiedSyncCommitteeMessage {
|
||||
// MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
|
||||
//
|
||||
// We do not queue future sync committee messages for later processing.
|
||||
verify_propagation_slot_range(&chain.slot_clock, &sync_message)?;
|
||||
verify_propagation_slot_range(&chain.slot_clock, &sync_message, &chain.spec)?;
|
||||
|
||||
// Ensure the `subnet_id` is valid for the given validator.
|
||||
let pubkey = chain
|
||||
@@ -576,11 +575,11 @@ impl VerifiedSyncCommitteeMessage {
|
||||
pub fn verify_propagation_slot_range<S: SlotClock, U: SlotData>(
|
||||
slot_clock: &S,
|
||||
sync_contribution: &U,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let message_slot = sync_contribution.get_slot();
|
||||
|
||||
let latest_permissible_slot = slot_clock
|
||||
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
||||
.now_with_future_tolerance(spec.maximum_gossip_clock_disparity())
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?;
|
||||
if message_slot > latest_permissible_slot {
|
||||
return Err(Error::FutureSlot {
|
||||
@@ -590,7 +589,7 @@ pub fn verify_propagation_slot_range<S: SlotClock, U: SlotData>(
|
||||
}
|
||||
|
||||
let earliest_permissible_slot = slot_clock
|
||||
.now_with_past_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
||||
.now_with_past_tolerance(spec.maximum_gossip_clock_disparity())
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?;
|
||||
|
||||
if message_slot < earliest_permissible_slot {
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::time::Duration;
|
||||
/// A simple wrapper around `parking_lot::RwLock` that only permits read/write access with a
|
||||
/// time-out (i.e., no indefinitely-blocking operations).
|
||||
///
|
||||
/// Timeouts can be optionally be disabled at runtime for all instances of this type by calling
|
||||
/// Timeouts can be optionally disabled at runtime for all instances of this type by calling
|
||||
/// `TimeoutRwLock::disable_timeouts()`.
|
||||
pub struct TimeoutRwLock<T>(RwLock<T>);
|
||||
|
||||
|
||||
@@ -687,7 +687,8 @@ impl<E: EthSpec> BeaconProcessor<E> {
|
||||
work_reprocessing_rx: mpsc::Receiver<ReprocessQueueMessage>,
|
||||
work_journal_tx: Option<mpsc::Sender<&'static str>>,
|
||||
slot_clock: S,
|
||||
) {
|
||||
maximum_gossip_clock_disparity: Duration,
|
||||
) -> Result<(), String> {
|
||||
// Used by workers to communicate that they are finished a task.
|
||||
let (idle_tx, idle_rx) = mpsc::channel::<()>(MAX_IDLE_QUEUE_LEN);
|
||||
|
||||
@@ -747,13 +748,15 @@ impl<E: EthSpec> BeaconProcessor<E> {
|
||||
// receive them back once they are ready (`ready_work_rx`).
|
||||
let (ready_work_tx, ready_work_rx) =
|
||||
mpsc::channel::<ReadyWork>(MAX_SCHEDULED_WORK_QUEUE_LEN);
|
||||
|
||||
spawn_reprocess_scheduler(
|
||||
ready_work_tx,
|
||||
work_reprocessing_rx,
|
||||
&self.executor,
|
||||
slot_clock,
|
||||
self.log.clone(),
|
||||
);
|
||||
maximum_gossip_clock_disparity,
|
||||
)?;
|
||||
|
||||
let executor = self.executor.clone();
|
||||
|
||||
@@ -1255,6 +1258,7 @@ impl<E: EthSpec> BeaconProcessor<E> {
|
||||
|
||||
// Spawn on the core executor.
|
||||
executor.spawn(manager_future, MANAGER_TASK_NAME);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Spawns a blocking worker thread to process some `Work`.
|
||||
|
||||
@@ -361,7 +361,12 @@ pub fn spawn_reprocess_scheduler<S: SlotClock + 'static>(
|
||||
executor: &TaskExecutor,
|
||||
slot_clock: S,
|
||||
log: Logger,
|
||||
) {
|
||||
maximum_gossip_clock_disparity: Duration,
|
||||
) -> Result<(), String> {
|
||||
// Sanity check
|
||||
if ADDITIONAL_QUEUED_BLOCK_DELAY >= maximum_gossip_clock_disparity {
|
||||
return Err("The block delay and gossip disparity don't match.".to_string());
|
||||
}
|
||||
let mut queue = ReprocessQueue {
|
||||
work_reprocessing_rx,
|
||||
ready_work_tx,
|
||||
@@ -400,6 +405,7 @@ pub fn spawn_reprocess_scheduler<S: SlotClock + 'static>(
|
||||
},
|
||||
TASK_NAME,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<S: SlotClock> ReprocessQueue<S> {
|
||||
|
||||
@@ -778,7 +778,8 @@ where
|
||||
self.work_reprocessing_rx,
|
||||
None,
|
||||
beacon_chain.slot_clock.clone(),
|
||||
);
|
||||
beacon_chain.spec.maximum_gossip_clock_disparity(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let state_advance_context = runtime_context.service_context("state_advance".into());
|
||||
@@ -833,9 +834,6 @@ where
|
||||
execution_layer.spawn_clean_proposer_caches_routine::<TSlotClock>(
|
||||
beacon_chain.slot_clock.clone(),
|
||||
);
|
||||
|
||||
// Spawns a routine that polls the `exchange_transition_configuration` endpoint.
|
||||
execution_layer.spawn_transition_configuration_poll(beacon_chain.spec.clone());
|
||||
}
|
||||
|
||||
// Spawn a service to publish BLS to execution changes at the Capella fork.
|
||||
|
||||
@@ -404,14 +404,6 @@ async fn merge_readiness_logging<T: BeaconChainTypes>(
|
||||
"config" => ?other
|
||||
),
|
||||
},
|
||||
readiness @ MergeReadiness::ExchangeTransitionConfigurationFailed { error: _ } => {
|
||||
error!(
|
||||
log,
|
||||
"Not ready for merge";
|
||||
"info" => %readiness,
|
||||
"hint" => "try updating Lighthouse and/or the execution layer",
|
||||
)
|
||||
}
|
||||
readiness @ MergeReadiness::NotSynced => warn!(
|
||||
log,
|
||||
"Not ready for merge";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::engines::ForkchoiceState;
|
||||
use crate::http::{
|
||||
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1, ENGINE_FORKCHOICE_UPDATED_V1,
|
||||
ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1,
|
||||
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2,
|
||||
ENGINE_GET_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3,
|
||||
ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2,
|
||||
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1,
|
||||
ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, ENGINE_GET_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V1,
|
||||
ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3,
|
||||
};
|
||||
use crate::BlobTxConversionError;
|
||||
use eth2::types::{SsePayloadAttributes, SsePayloadAttributesV1, SsePayloadAttributesV2};
|
||||
@@ -547,7 +547,6 @@ pub struct EngineCapabilities {
|
||||
pub get_payload_v1: bool,
|
||||
pub get_payload_v2: bool,
|
||||
pub get_payload_v3: bool,
|
||||
pub exchange_transition_configuration_v1: bool,
|
||||
}
|
||||
|
||||
impl EngineCapabilities {
|
||||
@@ -583,9 +582,6 @@ impl EngineCapabilities {
|
||||
if self.get_payload_v3 {
|
||||
response.push(ENGINE_GET_PAYLOAD_V3);
|
||||
}
|
||||
if self.exchange_transition_configuration_v1 {
|
||||
response.push(ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1);
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
@@ -48,10 +48,6 @@ pub const ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1: &str = "engine_getPayloadBodiesB
|
||||
pub const ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1: &str = "engine_getPayloadBodiesByRangeV1";
|
||||
pub const ENGINE_GET_PAYLOAD_BODIES_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
pub const ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1: &str =
|
||||
"engine_exchangeTransitionConfigurationV1";
|
||||
pub const ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
pub const ENGINE_EXCHANGE_CAPABILITIES: &str = "engine_exchangeCapabilities";
|
||||
pub const ENGINE_EXCHANGE_CAPABILITIES_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
@@ -72,7 +68,6 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[
|
||||
ENGINE_FORKCHOICE_UPDATED_V2,
|
||||
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1,
|
||||
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1,
|
||||
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1,
|
||||
];
|
||||
|
||||
/// This is necessary because a user might run a capella-enabled version of
|
||||
@@ -90,7 +85,6 @@ pub static PRE_CAPELLA_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilit
|
||||
get_payload_v1: true,
|
||||
get_payload_v2: false,
|
||||
get_payload_v3: false,
|
||||
exchange_transition_configuration_v1: true,
|
||||
};
|
||||
|
||||
/// Contains methods to convert arbitrary bytes to an ETH2 deposit contract object.
|
||||
@@ -994,24 +988,6 @@ impl HttpJsonRpc {
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn exchange_transition_configuration_v1(
|
||||
&self,
|
||||
transition_configuration: TransitionConfigurationV1,
|
||||
) -> Result<TransitionConfigurationV1, Error> {
|
||||
let params = json!([transition_configuration]);
|
||||
|
||||
let response = self
|
||||
.rpc_request(
|
||||
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1,
|
||||
params,
|
||||
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1_TIMEOUT
|
||||
* self.execution_timeout_multiplier,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn exchange_capabilities(&self) -> Result<EngineCapabilities, Error> {
|
||||
let params = json!([LIGHTHOUSE_CAPABILITIES]);
|
||||
|
||||
@@ -1044,8 +1020,6 @@ impl HttpJsonRpc {
|
||||
get_payload_v1: capabilities.contains(ENGINE_GET_PAYLOAD_V1),
|
||||
get_payload_v2: capabilities.contains(ENGINE_GET_PAYLOAD_V2),
|
||||
get_payload_v3: capabilities.contains(ENGINE_GET_PAYLOAD_V3),
|
||||
exchange_transition_configuration_v1: capabilities
|
||||
.contains(ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,6 @@ const EXECUTION_BLOCKS_LRU_CACHE_SIZE: usize = 128;
|
||||
const DEFAULT_SUGGESTED_FEE_RECIPIENT: [u8; 20] =
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
|
||||
|
||||
const CONFIG_POLL_INTERVAL: Duration = Duration::from_secs(60);
|
||||
|
||||
/// A payload alongside some information about where it came from.
|
||||
pub enum ProvenancedPayload<P> {
|
||||
/// A good ol' fashioned farm-to-table payload from your local EE.
|
||||
@@ -582,24 +580,6 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
self.spawn(preparation_cleaner, "exec_preparation_cleanup");
|
||||
}
|
||||
|
||||
/// Spawns a routine that polls the `exchange_transition_configuration` endpoint.
|
||||
pub fn spawn_transition_configuration_poll(&self, spec: ChainSpec) {
|
||||
let routine = |el: ExecutionLayer<T>| async move {
|
||||
loop {
|
||||
if let Err(e) = el.exchange_transition_configuration(&spec).await {
|
||||
error!(
|
||||
el.log(),
|
||||
"Failed to check transition config";
|
||||
"error" => ?e
|
||||
);
|
||||
}
|
||||
sleep(CONFIG_POLL_INTERVAL).await;
|
||||
}
|
||||
};
|
||||
|
||||
self.spawn(routine, "exec_config_poll");
|
||||
}
|
||||
|
||||
/// Returns `true` if the execution engine is synced and reachable.
|
||||
pub async fn is_synced(&self) -> bool {
|
||||
self.engine().is_synced().await
|
||||
@@ -1394,53 +1374,6 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
.map_err(Error::EngineError)
|
||||
}
|
||||
|
||||
pub async fn exchange_transition_configuration(&self, spec: &ChainSpec) -> Result<(), Error> {
|
||||
let local = TransitionConfigurationV1 {
|
||||
terminal_total_difficulty: spec.terminal_total_difficulty,
|
||||
terminal_block_hash: spec.terminal_block_hash,
|
||||
terminal_block_number: 0,
|
||||
};
|
||||
|
||||
let result = self
|
||||
.engine()
|
||||
.request(|engine| engine.api.exchange_transition_configuration_v1(local))
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(remote) => {
|
||||
if local.terminal_total_difficulty != remote.terminal_total_difficulty
|
||||
|| local.terminal_block_hash != remote.terminal_block_hash
|
||||
{
|
||||
error!(
|
||||
self.log(),
|
||||
"Execution client config mismatch";
|
||||
"msg" => "ensure lighthouse and the execution client are up-to-date and \
|
||||
configured consistently",
|
||||
"remote" => ?remote,
|
||||
"local" => ?local,
|
||||
);
|
||||
Err(Error::EngineError(Box::new(EngineError::Api {
|
||||
error: ApiError::TransitionConfigurationMismatch,
|
||||
})))
|
||||
} else {
|
||||
debug!(
|
||||
self.log(),
|
||||
"Execution client config is OK";
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
self.log(),
|
||||
"Unable to get transition config";
|
||||
"error" => ?e,
|
||||
);
|
||||
Err(Error::EngineError(Box::new(e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the execution engine capabilities resulting from a call to
|
||||
/// engine_exchangeCapabilities. If the capabilities cache is not populated,
|
||||
/// or if it is populated with a cached result of age >= `age_limit`, this
|
||||
|
||||
@@ -435,15 +435,6 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
|
||||
Ok(serde_json::to_value(response).unwrap())
|
||||
}
|
||||
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1 => {
|
||||
let block_generator = ctx.execution_block_generator.read();
|
||||
let transition_config: TransitionConfigurationV1 = TransitionConfigurationV1 {
|
||||
terminal_total_difficulty: block_generator.terminal_total_difficulty,
|
||||
terminal_block_hash: block_generator.terminal_block_hash,
|
||||
terminal_block_number: block_generator.terminal_block_number,
|
||||
};
|
||||
Ok(serde_json::to_value(transition_config).unwrap())
|
||||
}
|
||||
ENGINE_EXCHANGE_CAPABILITIES => {
|
||||
let engine_capabilities = ctx.engine_capabilities.read();
|
||||
Ok(serde_json::to_value(engine_capabilities.to_response()).unwrap())
|
||||
|
||||
@@ -49,7 +49,6 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
|
||||
get_payload_v1: true,
|
||||
get_payload_v2: true,
|
||||
get_payload_v3: true,
|
||||
exchange_transition_configuration_v1: true,
|
||||
};
|
||||
|
||||
mod execution_block_generator;
|
||||
|
||||
@@ -42,6 +42,7 @@ operation_pool = { path = "../operation_pool" }
|
||||
sensitive_url = { path = "../../common/sensitive_url" }
|
||||
unused_port = {path = "../../common/unused_port"}
|
||||
store = { path = "../store" }
|
||||
bytes = "1.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
environment = { path = "../../lighthouse/environment" }
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
//! Contains the handler for the `GET validator/duties/attester/{epoch}` endpoint.
|
||||
|
||||
use crate::state_id::StateId;
|
||||
use beacon_chain::{
|
||||
BeaconChain, BeaconChainError, BeaconChainTypes, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
};
|
||||
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes};
|
||||
use eth2::types::{self as api_types};
|
||||
use slot_clock::SlotClock;
|
||||
use state_processing::state_advance::partial_state_advance;
|
||||
@@ -32,7 +30,7 @@ pub fn attester_duties<T: BeaconChainTypes>(
|
||||
// will equal `current_epoch + 1`
|
||||
let tolerant_current_epoch = chain
|
||||
.slot_clock
|
||||
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
||||
.now_with_future_tolerance(chain.spec.maximum_gossip_clock_disparity())
|
||||
.ok_or_else(|| warp_utils::reject::custom_server_error("unable to read slot clock".into()))?
|
||||
.epoch(T::EthSpec::slots_per_epoch());
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ use beacon_chain::{
|
||||
BeaconChainTypes, ProduceBlockVerification, WhenSlotSkipped,
|
||||
};
|
||||
pub use block_id::BlockId;
|
||||
use bytes::Bytes;
|
||||
use directory::DEFAULT_ROOT_DIR;
|
||||
use eth2::types::{
|
||||
self as api_types, BroadcastValidation, EndpointVersion, ForkChoice, ForkChoiceNode,
|
||||
@@ -1236,6 +1237,41 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
},
|
||||
);
|
||||
|
||||
let post_beacon_blocks_ssz = eth_v1
|
||||
.and(warp::path("beacon"))
|
||||
.and(warp::path("blocks"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::bytes())
|
||||
.and(chain_filter.clone())
|
||||
.and(network_tx_filter.clone())
|
||||
.and(log_filter.clone())
|
||||
.and_then(
|
||||
|block_bytes: Bytes,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||
log: Logger| async move {
|
||||
let block_contents = match SignedBlockContents::<T::EthSpec>::from_ssz_bytes(
|
||||
&block_bytes,
|
||||
&chain.spec,
|
||||
) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return Err(warp_utils::reject::custom_bad_request(format!("{:?}", e)))
|
||||
}
|
||||
};
|
||||
publish_blocks::publish_block(
|
||||
None,
|
||||
ProvenancedBlock::local(block_contents),
|
||||
chain,
|
||||
&network_tx,
|
||||
log,
|
||||
BroadcastValidation::default(),
|
||||
)
|
||||
.await
|
||||
.map(|()| warp::reply().into_response())
|
||||
},
|
||||
);
|
||||
|
||||
let post_beacon_blocks_v2 = eth_v2
|
||||
.and(warp::path("beacon"))
|
||||
.and(warp::path("blocks"))
|
||||
@@ -1274,6 +1310,57 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
},
|
||||
);
|
||||
|
||||
let post_beacon_blocks_v2_ssz = eth_v2
|
||||
.and(warp::path("beacon"))
|
||||
.and(warp::path("blocks"))
|
||||
.and(warp::query::<api_types::BroadcastValidationQuery>())
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::bytes())
|
||||
.and(chain_filter.clone())
|
||||
.and(network_tx_filter.clone())
|
||||
.and(log_filter.clone())
|
||||
.then(
|
||||
|validation_level: api_types::BroadcastValidationQuery,
|
||||
block_bytes: Bytes,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||
log: Logger| async move {
|
||||
let block_contents = match SignedBlockContents::<T::EthSpec>::from_ssz_bytes(
|
||||
&block_bytes,
|
||||
&chain.spec,
|
||||
) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
return warp::reply::with_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
eth2::StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
match publish_blocks::publish_block(
|
||||
None,
|
||||
ProvenancedBlock::local(block_contents),
|
||||
chain,
|
||||
&network_tx,
|
||||
log,
|
||||
validation_level.broadcast_validation,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => warp::reply().into_response(),
|
||||
Err(e) => match warp_utils::reject::handle_rejection(e).await {
|
||||
Ok(reply) => reply.into_response(),
|
||||
Err(_) => warp::reply::with_status(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
eth2::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
.into_response(),
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/*
|
||||
* beacon/blocks
|
||||
*/
|
||||
@@ -1304,6 +1391,42 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
},
|
||||
);
|
||||
|
||||
// POST beacon/blocks
|
||||
let post_beacon_blinded_blocks_ssz = eth_v1
|
||||
.and(warp::path("beacon"))
|
||||
.and(warp::path("blinded_blocks"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::bytes())
|
||||
.and(chain_filter.clone())
|
||||
.and(network_tx_filter.clone())
|
||||
.and(log_filter.clone())
|
||||
.and_then(
|
||||
|block_bytes: Bytes,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||
log: Logger| async move {
|
||||
let block =
|
||||
match SignedBlockContents::<T::EthSpec, BlindedPayload<_>>::from_ssz_bytes(
|
||||
&block_bytes,
|
||||
&chain.spec,
|
||||
) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return Err(warp_utils::reject::custom_bad_request(format!("{:?}", e)))
|
||||
}
|
||||
};
|
||||
publish_blocks::publish_blinded_block(
|
||||
block,
|
||||
chain,
|
||||
&network_tx,
|
||||
log,
|
||||
BroadcastValidation::default(),
|
||||
)
|
||||
.await
|
||||
.map(|()| warp::reply().into_response())
|
||||
},
|
||||
);
|
||||
|
||||
let post_beacon_blinded_blocks_v2 = eth_v2
|
||||
.and(warp::path("beacon"))
|
||||
.and(warp::path("blinded_blocks"))
|
||||
@@ -1341,6 +1464,57 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
},
|
||||
);
|
||||
|
||||
let post_beacon_blinded_blocks_v2_ssz = eth_v2
|
||||
.and(warp::path("beacon"))
|
||||
.and(warp::path("blinded_blocks"))
|
||||
.and(warp::query::<api_types::BroadcastValidationQuery>())
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::bytes())
|
||||
.and(chain_filter.clone())
|
||||
.and(network_tx_filter.clone())
|
||||
.and(log_filter.clone())
|
||||
.then(
|
||||
|validation_level: api_types::BroadcastValidationQuery,
|
||||
block_bytes: Bytes,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
|
||||
log: Logger| async move {
|
||||
let block =
|
||||
match SignedBlockContents::<T::EthSpec, BlindedPayload<_>>::from_ssz_bytes(
|
||||
&block_bytes,
|
||||
&chain.spec,
|
||||
) {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
return warp::reply::with_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
eth2::StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
match publish_blocks::publish_blinded_block(
|
||||
block,
|
||||
chain,
|
||||
&network_tx,
|
||||
log,
|
||||
validation_level.broadcast_validation,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => warp::reply().into_response(),
|
||||
Err(e) => match warp_utils::reject::handle_rejection(e).await {
|
||||
Ok(reply) => reply.into_response(),
|
||||
Err(_) => warp::reply::with_status(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
eth2::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
.into_response(),
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let block_id_or_err = warp::path::param::<BlockId>().or_else(|_| async {
|
||||
Err(warp_utils::reject::custom_bad_request(
|
||||
"Invalid block ID".to_string(),
|
||||
@@ -2741,6 +2915,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
|
||||
fork_versioned_response(endpoint_version, fork_name, block_contents?)
|
||||
.map(|response| warp::reply::json(&response).into_response())
|
||||
.map(|res| add_consensus_version_header(res, fork_name))
|
||||
},
|
||||
);
|
||||
|
||||
@@ -2798,6 +2973,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
// Pose as a V2 endpoint so we return the fork `version`.
|
||||
fork_versioned_response(V2, fork_name, block)
|
||||
.map(|response| warp::reply::json(&response).into_response())
|
||||
.map(|res| add_consensus_version_header(res, fork_name))
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3345,6 +3521,45 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
},
|
||||
);
|
||||
|
||||
// POST vaidator/liveness/{epoch}
|
||||
let post_validator_liveness_epoch = eth_v1
|
||||
.and(warp::path("validator"))
|
||||
.and(warp::path("liveness"))
|
||||
.and(warp::path::param::<Epoch>())
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::json())
|
||||
.and(chain_filter.clone())
|
||||
.and_then(
|
||||
|epoch: Epoch, indices: Vec<u64>, chain: Arc<BeaconChain<T>>| {
|
||||
blocking_json_task(move || {
|
||||
// Ensure the request is for either the current, previous or next epoch.
|
||||
let current_epoch = chain
|
||||
.epoch()
|
||||
.map_err(warp_utils::reject::beacon_chain_error)?;
|
||||
let prev_epoch = current_epoch.saturating_sub(Epoch::new(1));
|
||||
let next_epoch = current_epoch.saturating_add(Epoch::new(1));
|
||||
|
||||
if epoch < prev_epoch || epoch > next_epoch {
|
||||
return Err(warp_utils::reject::custom_bad_request(format!(
|
||||
"request epoch {} is more than one epoch from the current epoch {}",
|
||||
epoch, current_epoch
|
||||
)));
|
||||
}
|
||||
|
||||
let liveness: Vec<api_types::StandardLivenessResponseData> = indices
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|index| {
|
||||
let is_live = chain.validator_seen_at_epoch(index as usize, epoch);
|
||||
api_types::StandardLivenessResponseData { index, is_live }
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(api_types::GenericResponse::from(liveness))
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// POST lighthouse/liveness
|
||||
let post_lighthouse_liveness = warp::path("lighthouse")
|
||||
.and(warp::path("liveness"))
|
||||
@@ -3967,7 +4182,15 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.boxed()
|
||||
.uor(
|
||||
warp::post().and(
|
||||
post_beacon_blocks
|
||||
warp::header::exact("Content-Type", "application/octet-stream")
|
||||
// Routes which expect `application/octet-stream` go within this `and`.
|
||||
.and(
|
||||
post_beacon_blocks_ssz
|
||||
.uor(post_beacon_blocks_v2_ssz)
|
||||
.uor(post_beacon_blinded_blocks_ssz)
|
||||
.uor(post_beacon_blinded_blocks_v2_ssz),
|
||||
)
|
||||
.uor(post_beacon_blocks)
|
||||
.uor(post_beacon_blinded_blocks)
|
||||
.uor(post_beacon_blocks_v2)
|
||||
.uor(post_beacon_blinded_blocks_v2)
|
||||
@@ -3987,6 +4210,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.uor(post_validator_sync_committee_subscriptions)
|
||||
.uor(post_validator_prepare_beacon_proposer)
|
||||
.uor(post_validator_register_validator)
|
||||
.uor(post_validator_liveness_epoch)
|
||||
.uor(post_lighthouse_liveness)
|
||||
.uor(post_lighthouse_database_reconstruct)
|
||||
.uor(post_lighthouse_block_rewards)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use crate::state_id::StateId;
|
||||
use beacon_chain::{
|
||||
beacon_proposer_cache::{compute_proposer_duties_from_head, ensure_state_is_in_epoch},
|
||||
BeaconChain, BeaconChainError, BeaconChainTypes, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||
};
|
||||
use eth2::types::{self as api_types};
|
||||
use safe_arith::SafeArith;
|
||||
@@ -33,7 +33,7 @@ pub fn proposer_duties<T: BeaconChainTypes>(
|
||||
// will equal `current_epoch + 1`
|
||||
let tolerant_current_epoch = chain
|
||||
.slot_clock
|
||||
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
||||
.now_with_future_tolerance(chain.spec.maximum_gossip_clock_disparity())
|
||||
.ok_or_else(|| warp_utils::reject::custom_server_error("unable to read slot clock".into()))?
|
||||
.epoch(T::EthSpec::slots_per_epoch());
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use beacon_chain::sync_committee_verification::{
|
||||
};
|
||||
use beacon_chain::{
|
||||
validator_monitor::timestamp_now, BeaconChain, BeaconChainError, BeaconChainTypes,
|
||||
StateSkipConfig, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
StateSkipConfig,
|
||||
};
|
||||
use eth2::types::{self as api_types};
|
||||
use lighthouse_network::PubsubMessage;
|
||||
@@ -85,7 +85,7 @@ fn duties_from_state_load<T: BeaconChainTypes>(
|
||||
let current_epoch = chain.epoch()?;
|
||||
let tolerant_current_epoch = chain
|
||||
.slot_clock
|
||||
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
|
||||
.now_with_future_tolerance(chain.spec.maximum_gossip_clock_disparity())
|
||||
.ok_or(BeaconChainError::UnableToReadSlot)?
|
||||
.epoch(T::EthSpec::slots_per_epoch());
|
||||
|
||||
|
||||
@@ -7,12 +7,9 @@ use directory::DEFAULT_ROOT_DIR;
|
||||
use eth2::{BeaconNodeHttpClient, Timeouts};
|
||||
use lighthouse_network::{
|
||||
discv5::enr::{CombinedKey, EnrBuilder},
|
||||
libp2p::{
|
||||
core::connection::ConnectionId,
|
||||
swarm::{
|
||||
behaviour::{ConnectionEstablished, FromSwarm},
|
||||
NetworkBehaviour,
|
||||
},
|
||||
libp2p::swarm::{
|
||||
behaviour::{ConnectionEstablished, FromSwarm},
|
||||
ConnectionId, NetworkBehaviour,
|
||||
},
|
||||
rpc::methods::{MetaData, MetaDataV2},
|
||||
types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield, SyncState},
|
||||
@@ -167,7 +164,7 @@ pub async fn create_api_server_on_port<T: BeaconChainTypes>(
|
||||
local_addr: EXTERNAL_ADDR.parse().unwrap(),
|
||||
send_back_addr: EXTERNAL_ADDR.parse().unwrap(),
|
||||
};
|
||||
let connection_id = ConnectionId::new(1);
|
||||
let connection_id = ConnectionId::new_unchecked(1);
|
||||
pm.on_swarm_event(FromSwarm::ConnectionEstablished(ConnectionEstablished {
|
||||
peer_id,
|
||||
connection_id,
|
||||
|
||||
@@ -182,6 +182,48 @@ pub async fn gossip_full_pass() {
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
}
|
||||
|
||||
// This test checks that a block that is valid from both a gossip and consensus perspective is accepted when using `broadcast_validation=gossip`.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn gossip_full_pass_ssz() {
|
||||
/* this test targets gossip-level validation */
|
||||
let validation_level: Option<BroadcastValidation> = Some(BroadcastValidation::Gossip);
|
||||
|
||||
// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
|
||||
// `validator_count // 32`.
|
||||
let validator_count = 64;
|
||||
let num_initial: u64 = 31;
|
||||
let tester = InteractiveTester::<E>::new(None, validator_count).await;
|
||||
|
||||
// Create some chain depth.
|
||||
tester.harness.advance_slot();
|
||||
tester
|
||||
.harness
|
||||
.extend_chain(
|
||||
num_initial as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
tester.harness.advance_slot();
|
||||
|
||||
let slot_a = Slot::new(num_initial);
|
||||
let slot_b = slot_a + 1;
|
||||
|
||||
let state_a = tester.harness.get_current_state();
|
||||
let (block, _): (SignedBeaconBlock<E>, _) = tester.harness.make_block(state_a, slot_b).await;
|
||||
|
||||
let response: Result<(), eth2::Error> = tester
|
||||
.client
|
||||
.post_beacon_blocks_v2_ssz(&block, validation_level)
|
||||
.await;
|
||||
|
||||
assert!(response.is_ok());
|
||||
assert!(tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
}
|
||||
|
||||
/// This test checks that a block that is **invalid** from a gossip perspective gets rejected when using `broadcast_validation=consensus`.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn consensus_invalid() {
|
||||
@@ -836,6 +878,49 @@ pub async fn blinded_gossip_full_pass() {
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
}
|
||||
|
||||
// This test checks that a block that is valid from both a gossip and consensus perspective is accepted when using `broadcast_validation=gossip`.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn blinded_gossip_full_pass_ssz() {
|
||||
/* this test targets gossip-level validation */
|
||||
let validation_level: Option<BroadcastValidation> = Some(BroadcastValidation::Gossip);
|
||||
|
||||
// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
|
||||
// `validator_count // 32`.
|
||||
let validator_count = 64;
|
||||
let num_initial: u64 = 31;
|
||||
let tester = InteractiveTester::<E>::new(None, validator_count).await;
|
||||
|
||||
// Create some chain depth.
|
||||
tester.harness.advance_slot();
|
||||
tester
|
||||
.harness
|
||||
.extend_chain(
|
||||
num_initial as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
tester.harness.advance_slot();
|
||||
|
||||
let slot_a = Slot::new(num_initial);
|
||||
let slot_b = slot_a + 1;
|
||||
|
||||
let state_a = tester.harness.get_current_state();
|
||||
let (block, _): (SignedBlindedBeaconBlock<E>, _) =
|
||||
tester.harness.make_blinded_block(state_a, slot_b).await;
|
||||
|
||||
let response: Result<(), eth2::Error> = tester
|
||||
.client
|
||||
.post_beacon_blinded_blocks_v2_ssz(&block, validation_level)
|
||||
.await;
|
||||
|
||||
assert!(response.is_ok());
|
||||
assert!(tester
|
||||
.harness
|
||||
.chain
|
||||
.block_is_known_to_fork_choice(&block.canonical_root()));
|
||||
}
|
||||
|
||||
/// This test checks that a block that is **invalid** from a gossip perspective gets rejected when using `broadcast_validation=consensus`.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
pub async fn blinded_consensus_invalid() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use beacon_chain::test_utils::RelativeSyncCommittee;
|
||||
use beacon_chain::{
|
||||
test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType},
|
||||
BeaconChain, StateSkipConfig, WhenSlotSkipped, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
BeaconChain, StateSkipConfig, WhenSlotSkipped,
|
||||
};
|
||||
use environment::null_logger;
|
||||
use eth2::{
|
||||
@@ -1251,6 +1251,22 @@ impl ApiTester {
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_post_beacon_blocks_ssz_valid(mut self) -> Self {
|
||||
let next_block = &self.next_block;
|
||||
|
||||
self.client
|
||||
.post_beacon_blocks_ssz(next_block)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
self.network_rx.network_recv.recv().await.is_some(),
|
||||
"valid blocks should be sent to network"
|
||||
);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_post_beacon_blocks_invalid(mut self) -> Self {
|
||||
let block = self
|
||||
.harness
|
||||
@@ -1278,6 +1294,29 @@ impl ApiTester {
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_post_beacon_blocks_ssz_invalid(mut self) -> Self {
|
||||
let block = self
|
||||
.harness
|
||||
.make_block_with_modifier(
|
||||
self.harness.get_current_state(),
|
||||
self.harness.get_current_slot(),
|
||||
|b| {
|
||||
*b.state_root_mut() = Hash256::zero();
|
||||
},
|
||||
)
|
||||
.await
|
||||
.0;
|
||||
|
||||
assert!(self.client.post_beacon_blocks_ssz(&block).await.is_err());
|
||||
|
||||
assert!(
|
||||
self.network_rx.network_recv.recv().await.is_some(),
|
||||
"gossip valid blocks should be sent to network"
|
||||
);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_beacon_blocks(self) -> Self {
|
||||
for block_id in self.interesting_block_ids() {
|
||||
let expected = block_id
|
||||
@@ -2282,7 +2321,9 @@ impl ApiTester {
|
||||
.unwrap();
|
||||
|
||||
self.chain.slot_clock.set_current_time(
|
||||
current_epoch_start - MAXIMUM_GOSSIP_CLOCK_DISPARITY - Duration::from_millis(1),
|
||||
current_epoch_start
|
||||
- self.chain.spec.maximum_gossip_clock_disparity()
|
||||
- Duration::from_millis(1),
|
||||
);
|
||||
|
||||
let dependent_root = self
|
||||
@@ -2319,9 +2360,9 @@ impl ApiTester {
|
||||
"should not get attester duties outside of tolerance"
|
||||
);
|
||||
|
||||
self.chain
|
||||
.slot_clock
|
||||
.set_current_time(current_epoch_start - MAXIMUM_GOSSIP_CLOCK_DISPARITY);
|
||||
self.chain.slot_clock.set_current_time(
|
||||
current_epoch_start - self.chain.spec.maximum_gossip_clock_disparity(),
|
||||
);
|
||||
|
||||
self.client
|
||||
.get_validator_duties_proposer(current_epoch)
|
||||
@@ -2554,6 +2595,66 @@ impl ApiTester {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_blinded_block_production_ssz<Payload: AbstractExecPayload<E>>(&self) {
|
||||
let fork = self.chain.canonical_head.cached_head().head_fork();
|
||||
let genesis_validators_root = self.chain.genesis_validators_root;
|
||||
|
||||
for _ in 0..E::slots_per_epoch() * 3 {
|
||||
let slot = self.chain.slot().unwrap();
|
||||
let epoch = self.chain.epoch().unwrap();
|
||||
|
||||
let proposer_pubkey_bytes = self
|
||||
.client
|
||||
.get_validator_duties_proposer(epoch)
|
||||
.await
|
||||
.unwrap()
|
||||
.data
|
||||
.into_iter()
|
||||
.find(|duty| duty.slot == slot)
|
||||
.map(|duty| duty.pubkey)
|
||||
.unwrap();
|
||||
let proposer_pubkey = (&proposer_pubkey_bytes).try_into().unwrap();
|
||||
|
||||
let sk = self
|
||||
.validator_keypairs()
|
||||
.iter()
|
||||
.find(|kp| kp.pk == proposer_pubkey)
|
||||
.map(|kp| kp.sk.clone())
|
||||
.unwrap();
|
||||
|
||||
let randao_reveal = {
|
||||
let domain = self.chain.spec.get_domain(
|
||||
epoch,
|
||||
Domain::Randao,
|
||||
&fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = epoch.signing_root(domain);
|
||||
sk.sign(message).into()
|
||||
};
|
||||
|
||||
let block = self
|
||||
.client
|
||||
.get_validator_blinded_blocks::<E, Payload>(slot, &randao_reveal, None)
|
||||
.await
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
let signed_block = block.sign(&sk, &fork, genesis_validators_root, &self.chain.spec);
|
||||
|
||||
self.client
|
||||
.post_beacon_blinded_blocks_ssz(&signed_block)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// This converts the generic `Payload` to a concrete type for comparison.
|
||||
let head_block = SignedBeaconBlock::from(signed_block.clone());
|
||||
assert_eq!(head_block, signed_block);
|
||||
|
||||
self.chain.slot_clock.set_slot(slot.as_u64() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_blinded_block_production_no_verify_randao<Payload: AbstractExecPayload<E>>(
|
||||
self,
|
||||
) -> Self {
|
||||
@@ -2997,6 +3098,69 @@ impl ApiTester {
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn test_post_validator_liveness_epoch(self) -> Self {
|
||||
let epoch = self.chain.epoch().unwrap();
|
||||
let head_state = self.chain.head_beacon_state_cloned();
|
||||
let indices = (0..head_state.validators().len())
|
||||
.map(|i| i as u64)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Construct the expected response
|
||||
let expected: Vec<StandardLivenessResponseData> = head_state
|
||||
.validators()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, _)| StandardLivenessResponseData {
|
||||
index: index as u64,
|
||||
is_live: false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let result = self
|
||||
.client
|
||||
.post_validator_liveness_epoch(epoch, indices.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
assert_eq!(result, expected);
|
||||
|
||||
// Attest to the current slot
|
||||
self.client
|
||||
.post_beacon_pool_attestations(self.attestations.as_slice())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = self
|
||||
.client
|
||||
.post_validator_liveness_epoch(epoch, indices.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
let committees = head_state
|
||||
.get_beacon_committees_at_slot(self.chain.slot().unwrap())
|
||||
.unwrap();
|
||||
let attesting_validators: Vec<usize> = committees
|
||||
.into_iter()
|
||||
.flat_map(|committee| committee.committee.iter().cloned())
|
||||
.collect();
|
||||
// All attesters should now be considered live
|
||||
let expected = expected
|
||||
.into_iter()
|
||||
.map(|mut a| {
|
||||
if attesting_validators.contains(&(a.index as usize)) {
|
||||
a.is_live = true;
|
||||
}
|
||||
a
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(result, expected);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
// Helper function for tests that require a valid RANDAO signature.
|
||||
async fn get_test_randao(&self, slot: Slot, epoch: Epoch) -> (u64, SignatureBytes) {
|
||||
let fork = self.chain.canonical_head.cached_head().head_fork();
|
||||
@@ -4409,6 +4573,22 @@ async fn post_beacon_blocks_valid() {
|
||||
ApiTester::new().await.test_post_beacon_blocks_valid().await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn post_beacon_blocks_ssz_valid() {
|
||||
ApiTester::new()
|
||||
.await
|
||||
.test_post_beacon_blocks_ssz_valid()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_post_beacon_blocks_ssz_invalid() {
|
||||
ApiTester::new()
|
||||
.await
|
||||
.test_post_beacon_blocks_ssz_invalid()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn post_beacon_blocks_invalid() {
|
||||
ApiTester::new()
|
||||
@@ -4605,6 +4785,14 @@ async fn blinded_block_production_full_payload_premerge() {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn blinded_block_production_ssz_full_payload_premerge() {
|
||||
ApiTester::new()
|
||||
.await
|
||||
.test_blinded_block_production_ssz::<FullPayload<_>>()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn blinded_block_production_with_skip_slots_full_payload_premerge() {
|
||||
ApiTester::new()
|
||||
@@ -4614,6 +4802,15 @@ async fn blinded_block_production_with_skip_slots_full_payload_premerge() {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn blinded_block_production_ssz_with_skip_slots_full_payload_premerge() {
|
||||
ApiTester::new()
|
||||
.await
|
||||
.skip_slots(E::slots_per_epoch() * 2)
|
||||
.test_blinded_block_production_ssz::<FullPayload<_>>()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn blinded_block_production_no_verify_randao_full_payload_premerge() {
|
||||
ApiTester::new()
|
||||
@@ -4891,6 +5088,14 @@ async fn builder_works_post_capella() {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn post_validator_liveness_epoch() {
|
||||
ApiTester::new()
|
||||
.await
|
||||
.test_post_validator_liveness_epoch()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn lighthouse_endpoints() {
|
||||
ApiTester::new()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::Context;
|
||||
use beacon_chain::BeaconChainTypes;
|
||||
use lighthouse_metrics::{Encoder, TextEncoder};
|
||||
use lighthouse_metrics::TextEncoder;
|
||||
use lighthouse_network::prometheus_client::encoding::text::encode;
|
||||
use malloc_utils::scrape_allocator_metrics;
|
||||
|
||||
@@ -9,7 +9,7 @@ pub use lighthouse_metrics::*;
|
||||
pub fn gather_prometheus_metrics<T: BeaconChainTypes>(
|
||||
ctx: &Context<T>,
|
||||
) -> std::result::Result<String, String> {
|
||||
let mut buffer = vec![];
|
||||
let mut buffer = String::new();
|
||||
let encoder = TextEncoder::new();
|
||||
|
||||
// There are two categories of metrics:
|
||||
@@ -50,7 +50,7 @@ pub fn gather_prometheus_metrics<T: BeaconChainTypes>(
|
||||
}
|
||||
|
||||
encoder
|
||||
.encode(&lighthouse_metrics::gather(), &mut buffer)
|
||||
.encode_utf8(&lighthouse_metrics::gather(), &mut buffer)
|
||||
.unwrap();
|
||||
// encode gossipsub metrics also if they exist
|
||||
if let Some(registry) = ctx.gossipsub_registry.as_ref() {
|
||||
@@ -59,5 +59,5 @@ pub fn gather_prometheus_metrics<T: BeaconChainTypes>(
|
||||
}
|
||||
}
|
||||
|
||||
String::from_utf8(buffer).map_err(|e| format!("Failed to encode prometheus info: {:?}", e))
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ authors = ["Sigma Prime <contact@sigmaprime.io>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
discv5 = { version = "0.3.0", features = ["libp2p"]}
|
||||
discv5 = { version = "0.3.1", features = ["libp2p"] }
|
||||
unsigned-varint = { version = "0.6.0", features = ["codec"] }
|
||||
types = { path = "../../consensus/types" }
|
||||
ssz_types = "0.5.4"
|
||||
@@ -40,15 +40,15 @@ directory = { path = "../../common/directory" }
|
||||
regex = "1.5.5"
|
||||
strum = { version = "0.24.0", features = ["derive"] }
|
||||
superstruct = "0.5.0"
|
||||
prometheus-client = "0.18.0"
|
||||
prometheus-client = "0.21.0"
|
||||
unused_port = { path = "../../common/unused_port" }
|
||||
delay_map = "0.3.0"
|
||||
void = "1"
|
||||
|
||||
[dependencies.libp2p]
|
||||
version = "0.50.0"
|
||||
version = "0.52"
|
||||
default-features = false
|
||||
features = ["websocket", "identify", "mplex", "yamux", "noise", "gossipsub", "dns", "tcp", "tokio", "plaintext", "secp256k1", "macros", "ecdsa"]
|
||||
features = ["websocket", "identify", "yamux", "noise", "gossipsub", "dns", "tcp", "tokio", "plaintext", "secp256k1", "macros", "ecdsa"]
|
||||
|
||||
[dev-dependencies]
|
||||
slog-term = "2.6.0"
|
||||
|
||||
@@ -6,10 +6,7 @@ use directory::{
|
||||
DEFAULT_BEACON_NODE_DIR, DEFAULT_HARDCODED_NETWORK, DEFAULT_NETWORK_DIR, DEFAULT_ROOT_DIR,
|
||||
};
|
||||
use discv5::{Discv5Config, Discv5ConfigBuilder};
|
||||
use libp2p::gossipsub::{
|
||||
FastMessageId, GossipsubConfig, GossipsubConfigBuilder, GossipsubMessage, MessageId,
|
||||
RawGossipsubMessage, ValidationMode,
|
||||
};
|
||||
use libp2p::gossipsub;
|
||||
use libp2p::Multiaddr;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
@@ -19,11 +16,6 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use types::{ForkContext, ForkName};
|
||||
|
||||
/// The maximum transmit size of gossip messages in bytes pre-merge.
|
||||
const GOSSIP_MAX_SIZE: usize = 1_048_576; // 1M
|
||||
/// The maximum transmit size of gossip messages in bytes post-merge.
|
||||
const GOSSIP_MAX_SIZE_POST_MERGE: usize = 10 * 1_048_576; // 10M
|
||||
|
||||
/// The cache time is set to accommodate the circulation time of an attestation.
|
||||
///
|
||||
/// The p2p spec declares that we accept attestations within the following range:
|
||||
@@ -38,20 +30,20 @@ const GOSSIP_MAX_SIZE_POST_MERGE: usize = 10 * 1_048_576; // 10M
|
||||
/// another 500ms for "fudge factor".
|
||||
pub const DUPLICATE_CACHE_TIME: Duration = Duration::from_secs(33 * 12 + 1);
|
||||
|
||||
// We treat uncompressed messages as invalid and never use the INVALID_SNAPPY_DOMAIN as in the
|
||||
// specification. We leave it here for posterity.
|
||||
// const MESSAGE_DOMAIN_INVALID_SNAPPY: [u8; 4] = [0, 0, 0, 0];
|
||||
const MESSAGE_DOMAIN_VALID_SNAPPY: [u8; 4] = [1, 0, 0, 0];
|
||||
|
||||
/// The maximum size of gossip messages.
|
||||
pub fn gossip_max_size(is_merge_enabled: bool) -> usize {
|
||||
pub fn gossip_max_size(is_merge_enabled: bool, gossip_max_size: usize) -> usize {
|
||||
if is_merge_enabled {
|
||||
GOSSIP_MAX_SIZE_POST_MERGE
|
||||
gossip_max_size
|
||||
} else {
|
||||
GOSSIP_MAX_SIZE
|
||||
gossip_max_size / 10
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GossipsubConfigParams {
|
||||
pub message_domain_valid_snappy: [u8; 4],
|
||||
pub gossip_max_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
/// Network configuration for lighthouse.
|
||||
@@ -83,7 +75,7 @@ pub struct Config {
|
||||
|
||||
/// Gossipsub configuration parameters.
|
||||
#[serde(skip)]
|
||||
pub gs_config: GossipsubConfig,
|
||||
pub gs_config: gossipsub::Config,
|
||||
|
||||
/// Discv5 configuration parameters.
|
||||
#[serde(skip)]
|
||||
@@ -265,7 +257,7 @@ impl Default for Config {
|
||||
|
||||
// Note: Using the default config here. Use `gossipsub_config` function for getting
|
||||
// Lighthouse specific configuration for gossipsub.
|
||||
let gs_config = GossipsubConfigBuilder::default()
|
||||
let gs_config = gossipsub::ConfigBuilder::default()
|
||||
.build()
|
||||
.expect("valid gossipsub configuration");
|
||||
|
||||
@@ -416,16 +408,20 @@ impl From<u8> for NetworkLoad {
|
||||
}
|
||||
|
||||
/// Return a Lighthouse specific `GossipsubConfig` where the `message_id_fn` depends on the current fork.
|
||||
pub fn gossipsub_config(network_load: u8, fork_context: Arc<ForkContext>) -> GossipsubConfig {
|
||||
pub fn gossipsub_config(
|
||||
network_load: u8,
|
||||
fork_context: Arc<ForkContext>,
|
||||
gossipsub_config_params: GossipsubConfigParams,
|
||||
) -> gossipsub::Config {
|
||||
// The function used to generate a gossipsub message id
|
||||
// We use the first 8 bytes of SHA256(topic, data) for content addressing
|
||||
let fast_gossip_message_id = |message: &RawGossipsubMessage| {
|
||||
let fast_gossip_message_id = |message: &gossipsub::RawMessage| {
|
||||
let data = [message.topic.as_str().as_bytes(), &message.data].concat();
|
||||
FastMessageId::from(&Sha256::digest(data)[..8])
|
||||
gossipsub::FastMessageId::from(&Sha256::digest(data)[..8])
|
||||
};
|
||||
fn prefix(
|
||||
prefix: [u8; 4],
|
||||
message: &GossipsubMessage,
|
||||
message: &gossipsub::Message,
|
||||
fork_context: Arc<ForkContext>,
|
||||
) -> Vec<u8> {
|
||||
let topic_bytes = message.topic.as_str().as_bytes();
|
||||
@@ -451,20 +447,23 @@ pub fn gossipsub_config(network_load: u8, fork_context: Arc<ForkContext>) -> Gos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let message_domain_valid_snappy = gossipsub_config_params.message_domain_valid_snappy;
|
||||
let is_merge_enabled = fork_context.fork_exists(ForkName::Merge);
|
||||
let gossip_message_id = move |message: &GossipsubMessage| {
|
||||
MessageId::from(
|
||||
let gossip_message_id = move |message: &gossipsub::Message| {
|
||||
gossipsub::MessageId::from(
|
||||
&Sha256::digest(
|
||||
prefix(MESSAGE_DOMAIN_VALID_SNAPPY, message, fork_context.clone()).as_slice(),
|
||||
prefix(message_domain_valid_snappy, message, fork_context.clone()).as_slice(),
|
||||
)[..20],
|
||||
)
|
||||
};
|
||||
|
||||
let load = NetworkLoad::from(network_load);
|
||||
|
||||
GossipsubConfigBuilder::default()
|
||||
.max_transmit_size(gossip_max_size(is_merge_enabled))
|
||||
gossipsub::ConfigBuilder::default()
|
||||
.max_transmit_size(gossip_max_size(
|
||||
is_merge_enabled,
|
||||
gossipsub_config_params.gossip_max_size,
|
||||
))
|
||||
.heartbeat_interval(load.heartbeat_interval)
|
||||
.mesh_n(load.mesh_n)
|
||||
.mesh_n_low(load.mesh_n_low)
|
||||
@@ -476,7 +475,7 @@ pub fn gossipsub_config(network_load: u8, fork_context: Arc<ForkContext>) -> Gos
|
||||
.max_messages_per_rpc(Some(500)) // Responses to IWANT can be quite large
|
||||
.history_gossip(load.history_gossip)
|
||||
.validate_messages() // require validation before propagation
|
||||
.validation_mode(ValidationMode::Anonymous)
|
||||
.validation_mode(gossipsub::ValidationMode::Anonymous)
|
||||
.duplicate_cache_time(DUPLICATE_CACHE_TIME)
|
||||
.message_id_fn(gossip_message_id)
|
||||
.fast_message_id_fn(fast_gossip_message_id)
|
||||
|
||||
@@ -7,7 +7,7 @@ use super::ENR_FILENAME;
|
||||
use crate::types::{Enr, EnrAttestationBitfield, EnrSyncCommitteeBitfield};
|
||||
use crate::NetworkConfig;
|
||||
use discv5::enr::EnrKey;
|
||||
use libp2p::core::identity::Keypair;
|
||||
use libp2p::identity::Keypair;
|
||||
use slog::{debug, warn};
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_types::BitVector;
|
||||
@@ -133,7 +133,7 @@ pub fn build_or_load_enr<T: EthSpec>(
|
||||
// Build the local ENR.
|
||||
// Note: Discovery should update the ENR record's IP to the external IP as seen by the
|
||||
// majority of our peers, if the CLI doesn't expressly forbid it.
|
||||
let enr_key = CombinedKey::from_libp2p(&local_key)?;
|
||||
let enr_key = CombinedKey::from_libp2p(local_key)?;
|
||||
let mut local_enr = build_enr::<T>(&enr_key, config, enr_fork_id)?;
|
||||
|
||||
use_or_load_enr(&enr_key, &mut local_enr, config, log)?;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
//! ENR extension trait to support libp2p integration.
|
||||
|
||||
use crate::{Enr, Multiaddr, PeerId};
|
||||
use discv5::enr::{CombinedKey, CombinedPublicKey};
|
||||
use libp2p::{
|
||||
core::{identity::Keypair, identity::PublicKey, multiaddr::Protocol},
|
||||
identity::secp256k1,
|
||||
};
|
||||
use libp2p::core::multiaddr::Protocol;
|
||||
use libp2p::identity::{ed25519, secp256k1, KeyType, Keypair, PublicKey};
|
||||
use tiny_keccak::{Hasher, Keccak};
|
||||
|
||||
/// Extend ENR for libp2p types.
|
||||
@@ -38,7 +37,8 @@ pub trait CombinedKeyPublicExt {
|
||||
/// Extend ENR CombinedKey for conversion to libp2p keys.
|
||||
pub trait CombinedKeyExt {
|
||||
/// Converts a libp2p key into an ENR combined key.
|
||||
fn from_libp2p(key: &libp2p::core::identity::Keypair) -> Result<CombinedKey, &'static str>;
|
||||
fn from_libp2p(key: Keypair) -> Result<CombinedKey, &'static str>;
|
||||
|
||||
/// Converts a [`secp256k1::Keypair`] into and Enr [`CombinedKey`].
|
||||
fn from_secp256k1(key: &secp256k1::Keypair) -> CombinedKey;
|
||||
}
|
||||
@@ -93,14 +93,14 @@ impl EnrExt for Enr {
|
||||
if let Some(udp) = self.udp4() {
|
||||
let mut multiaddr: Multiaddr = ip.into();
|
||||
multiaddr.push(Protocol::Udp(udp));
|
||||
multiaddr.push(Protocol::P2p(peer_id.into()));
|
||||
multiaddr.push(Protocol::P2p(peer_id));
|
||||
multiaddrs.push(multiaddr);
|
||||
}
|
||||
|
||||
if let Some(tcp) = self.tcp4() {
|
||||
let mut multiaddr: Multiaddr = ip.into();
|
||||
multiaddr.push(Protocol::Tcp(tcp));
|
||||
multiaddr.push(Protocol::P2p(peer_id.into()));
|
||||
multiaddr.push(Protocol::P2p(peer_id));
|
||||
multiaddrs.push(multiaddr);
|
||||
}
|
||||
}
|
||||
@@ -108,14 +108,14 @@ impl EnrExt for Enr {
|
||||
if let Some(udp6) = self.udp6() {
|
||||
let mut multiaddr: Multiaddr = ip6.into();
|
||||
multiaddr.push(Protocol::Udp(udp6));
|
||||
multiaddr.push(Protocol::P2p(peer_id.into()));
|
||||
multiaddr.push(Protocol::P2p(peer_id));
|
||||
multiaddrs.push(multiaddr);
|
||||
}
|
||||
|
||||
if let Some(tcp6) = self.tcp6() {
|
||||
let mut multiaddr: Multiaddr = ip6.into();
|
||||
multiaddr.push(Protocol::Tcp(tcp6));
|
||||
multiaddr.push(Protocol::P2p(peer_id.into()));
|
||||
multiaddr.push(Protocol::P2p(peer_id));
|
||||
multiaddrs.push(multiaddr);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ impl EnrExt for Enr {
|
||||
if let Some(tcp) = self.tcp4() {
|
||||
let mut multiaddr: Multiaddr = ip.into();
|
||||
multiaddr.push(Protocol::Tcp(tcp));
|
||||
multiaddr.push(Protocol::P2p(peer_id.into()));
|
||||
multiaddr.push(Protocol::P2p(peer_id));
|
||||
multiaddrs.push(multiaddr);
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ impl EnrExt for Enr {
|
||||
if let Some(tcp6) = self.tcp6() {
|
||||
let mut multiaddr: Multiaddr = ip6.into();
|
||||
multiaddr.push(Protocol::Tcp(tcp6));
|
||||
multiaddr.push(Protocol::P2p(peer_id.into()));
|
||||
multiaddr.push(Protocol::P2p(peer_id));
|
||||
multiaddrs.push(multiaddr);
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ impl EnrExt for Enr {
|
||||
if let Some(udp) = self.udp4() {
|
||||
let mut multiaddr: Multiaddr = ip.into();
|
||||
multiaddr.push(Protocol::Udp(udp));
|
||||
multiaddr.push(Protocol::P2p(peer_id.into()));
|
||||
multiaddr.push(Protocol::P2p(peer_id));
|
||||
multiaddrs.push(multiaddr);
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ impl EnrExt for Enr {
|
||||
if let Some(udp6) = self.udp6() {
|
||||
let mut multiaddr: Multiaddr = ip6.into();
|
||||
multiaddr.push(Protocol::Udp(udp6));
|
||||
multiaddr.push(Protocol::P2p(peer_id.into()));
|
||||
multiaddr.push(Protocol::P2p(peer_id));
|
||||
multiaddrs.push(multiaddr);
|
||||
}
|
||||
}
|
||||
@@ -204,18 +204,16 @@ impl CombinedKeyPublicExt for CombinedPublicKey {
|
||||
match self {
|
||||
Self::Secp256k1(pk) => {
|
||||
let pk_bytes = pk.to_sec1_bytes();
|
||||
let libp2p_pk = libp2p::core::PublicKey::Secp256k1(
|
||||
libp2p::core::identity::secp256k1::PublicKey::decode(&pk_bytes)
|
||||
.expect("valid public key"),
|
||||
);
|
||||
let libp2p_pk: PublicKey = secp256k1::PublicKey::try_from_bytes(&pk_bytes)
|
||||
.expect("valid public key")
|
||||
.into();
|
||||
PeerId::from_public_key(&libp2p_pk)
|
||||
}
|
||||
Self::Ed25519(pk) => {
|
||||
let pk_bytes = pk.to_bytes();
|
||||
let libp2p_pk = libp2p::core::PublicKey::Ed25519(
|
||||
libp2p::core::identity::ed25519::PublicKey::decode(&pk_bytes)
|
||||
.expect("valid public key"),
|
||||
);
|
||||
let libp2p_pk: PublicKey = ed25519::PublicKey::try_from_bytes(&pk_bytes)
|
||||
.expect("valid public key")
|
||||
.into();
|
||||
PeerId::from_public_key(&libp2p_pk)
|
||||
}
|
||||
}
|
||||
@@ -223,18 +221,25 @@ impl CombinedKeyPublicExt for CombinedPublicKey {
|
||||
}
|
||||
|
||||
impl CombinedKeyExt for CombinedKey {
|
||||
fn from_libp2p(key: &libp2p::core::identity::Keypair) -> Result<CombinedKey, &'static str> {
|
||||
match key {
|
||||
Keypair::Secp256k1(key) => Ok(CombinedKey::from_secp256k1(key)),
|
||||
Keypair::Ed25519(key) => {
|
||||
fn from_libp2p(key: Keypair) -> Result<CombinedKey, &'static str> {
|
||||
match key.key_type() {
|
||||
KeyType::Secp256k1 => {
|
||||
let key = key.try_into_secp256k1().expect("right key type");
|
||||
let secret =
|
||||
discv5::enr::k256::ecdsa::SigningKey::from_slice(&key.secret().to_bytes())
|
||||
.expect("libp2p key must be valid");
|
||||
Ok(CombinedKey::Secp256k1(secret))
|
||||
}
|
||||
KeyType::Ed25519 => {
|
||||
let key = key.try_into_ed25519().expect("right key type");
|
||||
let ed_keypair = discv5::enr::ed25519_dalek::SigningKey::from_bytes(
|
||||
&(key.encode()[..32])
|
||||
&(key.to_bytes()[..32])
|
||||
.try_into()
|
||||
.expect("libp2p key must be valid"),
|
||||
);
|
||||
Ok(CombinedKey::from(ed_keypair))
|
||||
}
|
||||
Keypair::Ecdsa(_) => Err("Ecdsa keypairs not supported"),
|
||||
_ => Err("Unsupported keypair kind"),
|
||||
}
|
||||
}
|
||||
fn from_secp256k1(key: &secp256k1::Keypair) -> Self {
|
||||
@@ -251,37 +256,46 @@ pub fn peer_id_to_node_id(peer_id: &PeerId) -> Result<discv5::enr::NodeId, Strin
|
||||
// if generated from a PublicKey with Identity multihash.
|
||||
let pk_bytes = &peer_id.to_bytes()[2..];
|
||||
|
||||
match PublicKey::from_protobuf_encoding(pk_bytes).map_err(|e| {
|
||||
let public_key = PublicKey::try_decode_protobuf(pk_bytes).map_err(|e| {
|
||||
format!(
|
||||
" Cannot parse libp2p public key public key from peer id: {}",
|
||||
e
|
||||
)
|
||||
})? {
|
||||
PublicKey::Secp256k1(pk) => {
|
||||
let uncompressed_key_bytes = &pk.encode_uncompressed()[1..];
|
||||
})?;
|
||||
|
||||
match public_key.key_type() {
|
||||
KeyType::Secp256k1 => {
|
||||
let pk = public_key
|
||||
.clone()
|
||||
.try_into_secp256k1()
|
||||
.expect("right key type");
|
||||
let uncompressed_key_bytes = &pk.to_bytes_uncompressed()[1..];
|
||||
let mut output = [0_u8; 32];
|
||||
let mut hasher = Keccak::v256();
|
||||
hasher.update(uncompressed_key_bytes);
|
||||
hasher.finalize(&mut output);
|
||||
Ok(discv5::enr::NodeId::parse(&output).expect("Must be correct length"))
|
||||
}
|
||||
PublicKey::Ed25519(pk) => {
|
||||
let uncompressed_key_bytes = pk.encode();
|
||||
KeyType::Ed25519 => {
|
||||
let pk = public_key
|
||||
.clone()
|
||||
.try_into_ed25519()
|
||||
.expect("right key type");
|
||||
let uncompressed_key_bytes = pk.to_bytes();
|
||||
let mut output = [0_u8; 32];
|
||||
let mut hasher = Keccak::v256();
|
||||
hasher.update(&uncompressed_key_bytes);
|
||||
hasher.finalize(&mut output);
|
||||
Ok(discv5::enr::NodeId::parse(&output).expect("Must be correct length"))
|
||||
}
|
||||
PublicKey::Ecdsa(_) => Err(format!(
|
||||
"Unsupported public key (Ecdsa) from peer {}",
|
||||
peer_id
|
||||
)),
|
||||
|
||||
_ => Err(format!("Unsupported public key from peer {}", peer_id)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -290,9 +304,9 @@ mod tests {
|
||||
let sk_bytes = hex::decode(sk_hex).unwrap();
|
||||
let secret_key = discv5::enr::k256::ecdsa::SigningKey::from_slice(&sk_bytes).unwrap();
|
||||
|
||||
let libp2p_sk = libp2p::identity::secp256k1::SecretKey::from_bytes(sk_bytes).unwrap();
|
||||
let secp256k1_kp: libp2p::identity::secp256k1::Keypair = libp2p_sk.into();
|
||||
let libp2p_kp = Keypair::Secp256k1(secp256k1_kp);
|
||||
let libp2p_sk = secp256k1::SecretKey::try_from_bytes(sk_bytes).unwrap();
|
||||
let secp256k1_kp: secp256k1::Keypair = libp2p_sk.into();
|
||||
let libp2p_kp: Keypair = secp256k1_kp.into();
|
||||
let peer_id = libp2p_kp.public().to_peer_id();
|
||||
|
||||
let enr = discv5::enr::EnrBuilder::new("v4")
|
||||
@@ -311,9 +325,9 @@ mod tests {
|
||||
&sk_bytes.clone().try_into().unwrap(),
|
||||
);
|
||||
|
||||
let libp2p_sk = libp2p::identity::ed25519::SecretKey::from_bytes(sk_bytes).unwrap();
|
||||
let secp256k1_kp: libp2p::identity::ed25519::Keypair = libp2p_sk.into();
|
||||
let libp2p_kp = Keypair::Ed25519(secp256k1_kp);
|
||||
let libp2p_sk = ed25519::SecretKey::try_from_bytes(sk_bytes).unwrap();
|
||||
let secp256k1_kp: ed25519::Keypair = libp2p_sk.into();
|
||||
let libp2p_kp: Keypair = secp256k1_kp.into();
|
||||
let peer_id = libp2p_kp.public().to_peer_id();
|
||||
|
||||
let enr = discv5::enr::EnrBuilder::new("v4")
|
||||
|
||||
@@ -16,19 +16,20 @@ pub use enr::{
|
||||
Eth2Enr,
|
||||
};
|
||||
pub use enr_ext::{peer_id_to_node_id, CombinedKeyExt, EnrExt};
|
||||
pub use libp2p::core::identity::{Keypair, PublicKey};
|
||||
pub use libp2p::identity::{Keypair, PublicKey};
|
||||
|
||||
use enr::{ATTESTATION_BITFIELD_ENR_KEY, ETH2_ENR_KEY, SYNC_COMMITTEE_BITFIELD_ENR_KEY};
|
||||
use futures::prelude::*;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use libp2p::multiaddr::Protocol;
|
||||
use libp2p::swarm::behaviour::{DialFailure, FromSwarm};
|
||||
use libp2p::swarm::AddressScore;
|
||||
use libp2p::swarm::THandlerInEvent;
|
||||
pub use libp2p::{
|
||||
core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId},
|
||||
core::{ConnectedPoint, Multiaddr},
|
||||
identity::PeerId,
|
||||
swarm::{
|
||||
dummy::ConnectionHandler, DialError, NetworkBehaviour, NetworkBehaviourAction as NBAction,
|
||||
NotifyHandler, PollParameters, SubstreamProtocol,
|
||||
dummy::ConnectionHandler, ConnectionId, DialError, NetworkBehaviour, NotifyHandler,
|
||||
PollParameters, SubstreamProtocol, ToSwarm,
|
||||
},
|
||||
};
|
||||
use lru::LruCache;
|
||||
@@ -191,7 +192,7 @@ pub struct Discovery<TSpec: EthSpec> {
|
||||
impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
/// NOTE: Creating discovery requires running within a tokio execution environment.
|
||||
pub async fn new(
|
||||
local_key: &Keypair,
|
||||
local_key: Keypair,
|
||||
config: &NetworkConfig,
|
||||
network_globals: Arc<NetworkGlobals<TSpec>>,
|
||||
log: &slog::Logger,
|
||||
@@ -925,22 +926,51 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
// Discovery is not a real NetworkBehaviour...
|
||||
type ConnectionHandler = ConnectionHandler;
|
||||
type OutEvent = DiscoveredPeers;
|
||||
type ToSwarm = DiscoveredPeers;
|
||||
|
||||
fn new_handler(&mut self) -> Self::ConnectionHandler {
|
||||
ConnectionHandler
|
||||
fn handle_established_inbound_connection(
|
||||
&mut self,
|
||||
_connection_id: ConnectionId,
|
||||
_peer: PeerId,
|
||||
_local_addr: &Multiaddr,
|
||||
_remote_addr: &Multiaddr,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
// TODO: we might want to check discovery's banned ips here in the future.
|
||||
Ok(ConnectionHandler)
|
||||
}
|
||||
|
||||
// Handles the libp2p request to obtain multiaddrs for peer_id's in order to dial them.
|
||||
fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec<Multiaddr> {
|
||||
if let Some(enr) = self.enr_of_peer(peer_id) {
|
||||
fn handle_established_outbound_connection(
|
||||
&mut self,
|
||||
_connection_id: ConnectionId,
|
||||
_peer: PeerId,
|
||||
_addr: &Multiaddr,
|
||||
_role_override: libp2p::core::Endpoint,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
Ok(ConnectionHandler)
|
||||
}
|
||||
|
||||
fn on_connection_handler_event(
|
||||
&mut self,
|
||||
_peer_id: PeerId,
|
||||
_connection_id: ConnectionId,
|
||||
_event: void::Void,
|
||||
) {
|
||||
}
|
||||
|
||||
fn handle_pending_outbound_connection(
|
||||
&mut self,
|
||||
_connection_id: ConnectionId,
|
||||
maybe_peer: Option<PeerId>,
|
||||
_addresses: &[Multiaddr],
|
||||
_effective_role: libp2p::core::Endpoint,
|
||||
) -> Result<Vec<Multiaddr>, libp2p::swarm::ConnectionDenied> {
|
||||
if let Some(enr) = maybe_peer.and_then(|peer_id| self.enr_of_peer(&peer_id)) {
|
||||
// ENR's may have multiple Multiaddrs. The multi-addr associated with the UDP
|
||||
// port is removed, which is assumed to be associated with the discv5 protocol (and
|
||||
// therefore irrelevant for other libp2p components).
|
||||
enr.multiaddr_tcp()
|
||||
Ok(enr.multiaddr_tcp())
|
||||
} else {
|
||||
// PeerId is not known
|
||||
Vec::new()
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,7 +979,7 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
&mut self,
|
||||
cx: &mut Context,
|
||||
_: &mut impl PollParameters,
|
||||
) -> Poll<NBAction<Self::OutEvent, Self::ConnectionHandler>> {
|
||||
) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
|
||||
if !self.started {
|
||||
return Poll::Pending;
|
||||
}
|
||||
@@ -960,7 +990,7 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
// Drive the queries and return any results from completed queries
|
||||
if let Some(peers) = self.poll_queries(cx) {
|
||||
// return the result to the peer manager
|
||||
return Poll::Ready(NBAction::GenerateEvent(DiscoveredPeers { peers }));
|
||||
return Poll::Ready(ToSwarm::GenerateEvent(DiscoveredPeers { peers }));
|
||||
}
|
||||
|
||||
// Process the server event stream
|
||||
@@ -1034,10 +1064,7 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
if let Some(address) = addr {
|
||||
// NOTE: This doesn't actually track the external TCP port. More sophisticated NAT handling
|
||||
// should handle this.
|
||||
return Poll::Ready(NBAction::ReportObservedAddr {
|
||||
address,
|
||||
score: AddressScore::Finite(1),
|
||||
});
|
||||
return Poll::Ready(ToSwarm::NewExternalAddrCandidate(address));
|
||||
}
|
||||
}
|
||||
Discv5Event::EnrAdded { .. }
|
||||
@@ -1065,8 +1092,9 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
|
||||
| FromSwarm::ExpiredListenAddr(_)
|
||||
| FromSwarm::ListenerError(_)
|
||||
| FromSwarm::ListenerClosed(_)
|
||||
| FromSwarm::NewExternalAddr(_)
|
||||
| FromSwarm::ExpiredExternalAddr(_) => {
|
||||
| FromSwarm::NewExternalAddrCandidate(_)
|
||||
| FromSwarm::ExternalAddrExpired(_)
|
||||
| FromSwarm::ExternalAddrConfirmed(_) => {
|
||||
// Ignore events not relevant to discovery
|
||||
}
|
||||
}
|
||||
@@ -1077,10 +1105,8 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
fn on_dial_failure(&mut self, peer_id: Option<PeerId>, error: &DialError) {
|
||||
if let Some(peer_id) = peer_id {
|
||||
match error {
|
||||
DialError::Banned
|
||||
| DialError::LocalPeerId
|
||||
| DialError::InvalidPeerId(_)
|
||||
| DialError::ConnectionIo(_)
|
||||
DialError::LocalPeerId { .. }
|
||||
| DialError::Denied { .. }
|
||||
| DialError::NoAddresses
|
||||
| DialError::Transport(_)
|
||||
| DialError::WrongPeerId { .. } => {
|
||||
@@ -1088,9 +1114,7 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
||||
debug!(self.log, "Marking peer disconnected in DHT"; "peer_id" => %peer_id);
|
||||
self.disconnect_peer(&peer_id);
|
||||
}
|
||||
DialError::ConnectionLimit(_)
|
||||
| DialError::DialPeerConditionFalse(_)
|
||||
| DialError::Aborted => {}
|
||||
DialError::DialPeerConditionFalse(_) | DialError::Aborted => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1139,8 +1163,8 @@ mod tests {
|
||||
false,
|
||||
&log,
|
||||
);
|
||||
let keypair = Keypair::Secp256k1(keypair);
|
||||
Discovery::new(&keypair, &config, Arc::new(globals), &log)
|
||||
let keypair = keypair.into();
|
||||
Discovery::new(keypair, &config, Arc::new(globals), &log)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ use std::{
|
||||
use strum::IntoEnumIterator;
|
||||
use types::{EthSpec, SyncSubnetId};
|
||||
|
||||
pub use libp2p::core::{identity::Keypair, Multiaddr};
|
||||
pub use libp2p::core::Multiaddr;
|
||||
pub use libp2p::identity::Keypair;
|
||||
|
||||
#[allow(clippy::mutable_key_type)] // PeerId in hashmaps are no longer permitted by clippy
|
||||
pub mod peerdb;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
//! Implementation of [`NetworkBehaviour`] for the [`PeerManager`].
|
||||
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures::StreamExt;
|
||||
use libp2p::core::ConnectedPoint;
|
||||
use libp2p::identity::PeerId;
|
||||
use libp2p::swarm::behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm};
|
||||
use libp2p::swarm::dial_opts::{DialOpts, PeerCondition};
|
||||
use libp2p::swarm::dummy::ConnectionHandler;
|
||||
use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters};
|
||||
use libp2p::PeerId;
|
||||
use libp2p::swarm::{ConnectionId, NetworkBehaviour, PollParameters, ToSwarm};
|
||||
use slog::{debug, error};
|
||||
use types::EthSpec;
|
||||
|
||||
@@ -19,20 +21,24 @@ use super::{ConnectingType, PeerManager, PeerManagerEvent, ReportSource};
|
||||
|
||||
impl<TSpec: EthSpec> NetworkBehaviour for PeerManager<TSpec> {
|
||||
type ConnectionHandler = ConnectionHandler;
|
||||
|
||||
type OutEvent = PeerManagerEvent;
|
||||
type ToSwarm = PeerManagerEvent;
|
||||
|
||||
/* Required trait members */
|
||||
|
||||
fn new_handler(&mut self) -> Self::ConnectionHandler {
|
||||
ConnectionHandler
|
||||
fn on_connection_handler_event(
|
||||
&mut self,
|
||||
_peer_id: PeerId,
|
||||
_connection_id: ConnectionId,
|
||||
_event: libp2p::swarm::THandlerOutEvent<Self>,
|
||||
) {
|
||||
// no events from the dummy handler
|
||||
}
|
||||
|
||||
fn poll(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
_params: &mut impl PollParameters,
|
||||
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ConnectionHandler>> {
|
||||
) -> Poll<ToSwarm<Self::ToSwarm, void::Void>> {
|
||||
// perform the heartbeat when necessary
|
||||
while self.heartbeat.poll_tick(cx).is_ready() {
|
||||
self.heartbeat();
|
||||
@@ -84,19 +90,17 @@ impl<TSpec: EthSpec> NetworkBehaviour for PeerManager<TSpec> {
|
||||
}
|
||||
|
||||
if !self.events.is_empty() {
|
||||
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(self.events.remove(0)));
|
||||
return Poll::Ready(ToSwarm::GenerateEvent(self.events.remove(0)));
|
||||
} else {
|
||||
self.events.shrink_to_fit();
|
||||
}
|
||||
|
||||
if let Some((peer_id, maybe_enr)) = self.peers_to_dial.pop_first() {
|
||||
self.inject_peer_connection(&peer_id, ConnectingType::Dialing, maybe_enr);
|
||||
let handler = self.new_handler();
|
||||
return Poll::Ready(NetworkBehaviourAction::Dial {
|
||||
return Poll::Ready(ToSwarm::Dial {
|
||||
opts: DialOpts::peer_id(peer_id)
|
||||
.condition(PeerCondition::Disconnected)
|
||||
.build(),
|
||||
handler,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,13 +114,31 @@ impl<TSpec: EthSpec> NetworkBehaviour for PeerManager<TSpec> {
|
||||
endpoint,
|
||||
other_established,
|
||||
..
|
||||
}) => self.on_connection_established(peer_id, endpoint, other_established),
|
||||
}) => {
|
||||
// NOTE: We still need to handle the [`ConnectionEstablished`] because the
|
||||
// [`NetworkBehaviour::handle_established_inbound_connection`] and
|
||||
// [`NetworkBehaviour::handle_established_outbound_connection`] are fallible. This
|
||||
// means another behaviour can kill the connection early, and we can't assume a
|
||||
// peer as connected until this event is received.
|
||||
self.on_connection_established(peer_id, endpoint, other_established)
|
||||
}
|
||||
FromSwarm::ConnectionClosed(ConnectionClosed {
|
||||
peer_id,
|
||||
remaining_established,
|
||||
..
|
||||
}) => self.on_connection_closed(peer_id, remaining_established),
|
||||
FromSwarm::DialFailure(DialFailure { peer_id, .. }) => self.on_dial_failure(peer_id),
|
||||
FromSwarm::DialFailure(DialFailure {
|
||||
peer_id,
|
||||
error,
|
||||
connection_id: _,
|
||||
}) => {
|
||||
debug!(self.log, "Failed to dial peer"; "peer_id"=> ?peer_id, "error" => %error);
|
||||
self.on_dial_failure(peer_id);
|
||||
}
|
||||
FromSwarm::ExternalAddrConfirmed(_) => {
|
||||
// TODO: we likely want to check this against our assumed external tcp
|
||||
// address
|
||||
}
|
||||
FromSwarm::AddressChange(_)
|
||||
| FromSwarm::ListenFailure(_)
|
||||
| FromSwarm::NewListener(_)
|
||||
@@ -124,13 +146,35 @@ impl<TSpec: EthSpec> NetworkBehaviour for PeerManager<TSpec> {
|
||||
| FromSwarm::ExpiredListenAddr(_)
|
||||
| FromSwarm::ListenerError(_)
|
||||
| FromSwarm::ListenerClosed(_)
|
||||
| FromSwarm::NewExternalAddr(_)
|
||||
| FromSwarm::ExpiredExternalAddr(_) => {
|
||||
| FromSwarm::NewExternalAddrCandidate(_)
|
||||
| FromSwarm::ExternalAddrExpired(_) => {
|
||||
// The rest of the events we ignore since they are handled in their associated
|
||||
// `SwarmEvent`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_established_inbound_connection(
|
||||
&mut self,
|
||||
_connection_id: ConnectionId,
|
||||
_peer: PeerId,
|
||||
_local_addr: &libp2p::Multiaddr,
|
||||
_remote_addr: &libp2p::Multiaddr,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
// TODO: we might want to check if we accept this peer or not in the future.
|
||||
Ok(ConnectionHandler)
|
||||
}
|
||||
|
||||
fn handle_established_outbound_connection(
|
||||
&mut self,
|
||||
_connection_id: ConnectionId,
|
||||
_peer: PeerId,
|
||||
_addr: &libp2p::Multiaddr,
|
||||
_role_override: libp2p::core::Endpoint,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
// TODO: we might want to check if we accept this peer or not in the future.
|
||||
Ok(ConnectionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> PeerManager<TSpec> {
|
||||
|
||||
@@ -220,9 +220,12 @@ mod tests {
|
||||
let snappy_protocol_id = ProtocolId::new(SupportedProtocol::StatusV1, Encoding::SSZSnappy);
|
||||
|
||||
let fork_context = Arc::new(fork_context(ForkName::Base));
|
||||
|
||||
let chain_spec = Spec::default_spec();
|
||||
|
||||
let mut snappy_outbound_codec = SSZSnappyOutboundCodec::<Spec>::new(
|
||||
snappy_protocol_id,
|
||||
max_rpc_size(&fork_context),
|
||||
max_rpc_size(&fork_context, chain_spec.max_chunk_size as usize),
|
||||
fork_context,
|
||||
);
|
||||
|
||||
@@ -254,9 +257,12 @@ mod tests {
|
||||
let snappy_protocol_id = ProtocolId::new(SupportedProtocol::StatusV1, Encoding::SSZSnappy);
|
||||
|
||||
let fork_context = Arc::new(fork_context(ForkName::Base));
|
||||
|
||||
let chain_spec = Spec::default_spec();
|
||||
|
||||
let mut snappy_outbound_codec = SSZSnappyOutboundCodec::<Spec>::new(
|
||||
snappy_protocol_id,
|
||||
max_rpc_size(&fork_context),
|
||||
max_rpc_size(&fork_context, chain_spec.max_chunk_size as usize),
|
||||
fork_context,
|
||||
);
|
||||
|
||||
@@ -282,7 +288,10 @@ mod tests {
|
||||
|
||||
// Response limits
|
||||
let fork_context = Arc::new(fork_context(ForkName::Base));
|
||||
let max_rpc_size = max_rpc_size(&fork_context);
|
||||
|
||||
let chain_spec = Spec::default_spec();
|
||||
|
||||
let max_rpc_size = max_rpc_size(&fork_context, chain_spec.max_chunk_size as usize);
|
||||
let limit = protocol_id.rpc_response_limits::<Spec>(&fork_context);
|
||||
let mut max = encode_len(limit.max + 1);
|
||||
let mut codec = SSZSnappyOutboundCodec::<Spec>::new(
|
||||
|
||||
@@ -678,8 +678,8 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
blob_sidecar::BlobIdentifier, BeaconBlock, BeaconBlockAltair, BeaconBlockBase,
|
||||
BeaconBlockMerge, EmptyBlock, Epoch, ForkContext, FullPayload, Hash256, Signature,
|
||||
SignedBeaconBlock, Slot,
|
||||
BeaconBlockMerge, ChainSpec, EmptyBlock, Epoch, ForkContext, FullPayload, Hash256,
|
||||
Signature, SignedBeaconBlock, Slot,
|
||||
};
|
||||
|
||||
use snap::write::FrameEncoder;
|
||||
@@ -728,7 +728,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Merge block with length < max_rpc_size.
|
||||
fn merge_block_small(fork_context: &ForkContext) -> SignedBeaconBlock<Spec> {
|
||||
fn merge_block_small(fork_context: &ForkContext, spec: &ChainSpec) -> SignedBeaconBlock<Spec> {
|
||||
let mut block: BeaconBlockMerge<_, FullPayload<Spec>> =
|
||||
BeaconBlockMerge::empty(&Spec::default_spec());
|
||||
let tx = VariableList::from(vec![0; 1024]);
|
||||
@@ -737,14 +737,14 @@ mod tests {
|
||||
block.body.execution_payload.execution_payload.transactions = txs;
|
||||
|
||||
let block = BeaconBlock::Merge(block);
|
||||
assert!(block.ssz_bytes_len() <= max_rpc_size(fork_context));
|
||||
assert!(block.ssz_bytes_len() <= max_rpc_size(fork_context, spec.max_chunk_size as usize));
|
||||
SignedBeaconBlock::from_block(block, Signature::empty())
|
||||
}
|
||||
|
||||
/// Merge block with length > MAX_RPC_SIZE.
|
||||
/// The max limit for a merge block is in the order of ~16GiB which wouldn't fit in memory.
|
||||
/// Hence, we generate a merge block just greater than `MAX_RPC_SIZE` to test rejection on the rpc layer.
|
||||
fn merge_block_large(fork_context: &ForkContext) -> SignedBeaconBlock<Spec> {
|
||||
fn merge_block_large(fork_context: &ForkContext, spec: &ChainSpec) -> SignedBeaconBlock<Spec> {
|
||||
let mut block: BeaconBlockMerge<_, FullPayload<Spec>> =
|
||||
BeaconBlockMerge::empty(&Spec::default_spec());
|
||||
let tx = VariableList::from(vec![0; 1024]);
|
||||
@@ -753,7 +753,7 @@ mod tests {
|
||||
block.body.execution_payload.execution_payload.transactions = txs;
|
||||
|
||||
let block = BeaconBlock::Merge(block);
|
||||
assert!(block.ssz_bytes_len() > max_rpc_size(fork_context));
|
||||
assert!(block.ssz_bytes_len() > max_rpc_size(fork_context, spec.max_chunk_size as usize));
|
||||
SignedBeaconBlock::from_block(block, Signature::empty())
|
||||
}
|
||||
|
||||
@@ -823,10 +823,11 @@ mod tests {
|
||||
protocol: SupportedProtocol,
|
||||
message: RPCCodedResponse<Spec>,
|
||||
fork_name: ForkName,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<BytesMut, RPCError> {
|
||||
let snappy_protocol_id = ProtocolId::new(protocol, Encoding::SSZSnappy);
|
||||
let fork_context = Arc::new(fork_context(fork_name));
|
||||
let max_packet_size = max_rpc_size(&fork_context);
|
||||
let max_packet_size = max_rpc_size(&fork_context, spec.max_chunk_size as usize);
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
let mut snappy_inbound_codec =
|
||||
@@ -869,10 +870,11 @@ mod tests {
|
||||
protocol: SupportedProtocol,
|
||||
message: &mut BytesMut,
|
||||
fork_name: ForkName,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Option<RPCResponse<Spec>>, RPCError> {
|
||||
let snappy_protocol_id = ProtocolId::new(protocol, Encoding::SSZSnappy);
|
||||
let fork_context = Arc::new(fork_context(fork_name));
|
||||
let max_packet_size = max_rpc_size(&fork_context);
|
||||
let max_packet_size = max_rpc_size(&fork_context, spec.max_chunk_size as usize);
|
||||
let mut snappy_outbound_codec =
|
||||
SSZSnappyOutboundCodec::<Spec>::new(snappy_protocol_id, max_packet_size, fork_context);
|
||||
// decode message just as snappy message
|
||||
@@ -884,15 +886,20 @@ mod tests {
|
||||
protocol: SupportedProtocol,
|
||||
message: RPCCodedResponse<Spec>,
|
||||
fork_name: ForkName,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Option<RPCResponse<Spec>>, RPCError> {
|
||||
let mut encoded = encode_response(protocol, message, fork_name)?;
|
||||
decode_response(protocol, &mut encoded, fork_name)
|
||||
let mut encoded = encode_response(protocol, message, fork_name, spec)?;
|
||||
decode_response(protocol, &mut encoded, fork_name, spec)
|
||||
}
|
||||
|
||||
/// Verifies that requests we send are encoded in a way that we would correctly decode too.
|
||||
fn encode_then_decode_request(req: OutboundRequest<Spec>, fork_name: ForkName) {
|
||||
fn encode_then_decode_request(
|
||||
req: OutboundRequest<Spec>,
|
||||
fork_name: ForkName,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let fork_context = Arc::new(fork_context(fork_name));
|
||||
let max_packet_size = max_rpc_size(&fork_context);
|
||||
let max_packet_size = max_rpc_size(&fork_context, spec.max_chunk_size as usize);
|
||||
let protocol = ProtocolId::new(req.versioned_protocol(), Encoding::SSZSnappy);
|
||||
// Encode a request we send
|
||||
let mut buf = BytesMut::new();
|
||||
@@ -943,11 +950,14 @@ mod tests {
|
||||
// Test RPCResponse encoding/decoding for V1 messages
|
||||
#[test]
|
||||
fn test_encode_then_decode_v1() {
|
||||
let chain_spec = Spec::default_spec();
|
||||
|
||||
assert_eq!(
|
||||
encode_then_decode_response(
|
||||
SupportedProtocol::StatusV1,
|
||||
RPCCodedResponse::Success(RPCResponse::Status(status_message())),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::Status(status_message())))
|
||||
);
|
||||
@@ -957,6 +967,7 @@ mod tests {
|
||||
SupportedProtocol::PingV1,
|
||||
RPCCodedResponse::Success(RPCResponse::Pong(ping_message())),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::Pong(ping_message())))
|
||||
);
|
||||
@@ -966,6 +977,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRangeV1,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRange(Arc::new(empty_base_block()))),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRange(Arc::new(
|
||||
empty_base_block()
|
||||
@@ -978,6 +990,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRangeV1,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRange(Arc::new(altair_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::SSZDecodeError(_)
|
||||
@@ -990,6 +1003,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV1,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRoot(Arc::new(empty_base_block()))),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRoot(
|
||||
Arc::new(empty_base_block())
|
||||
@@ -1002,6 +1016,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV1,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRoot(Arc::new(altair_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::SSZDecodeError(_)
|
||||
@@ -1014,6 +1029,7 @@ mod tests {
|
||||
SupportedProtocol::MetaDataV1,
|
||||
RPCCodedResponse::Success(RPCResponse::MetaData(metadata())),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::MetaData(metadata()))),
|
||||
);
|
||||
@@ -1024,6 +1040,7 @@ mod tests {
|
||||
SupportedProtocol::MetaDataV1,
|
||||
RPCCodedResponse::Success(RPCResponse::MetaData(metadata_v2())),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::MetaData(metadata()))),
|
||||
);
|
||||
@@ -1033,6 +1050,7 @@ mod tests {
|
||||
SupportedProtocol::BlobsByRangeV1,
|
||||
RPCCodedResponse::Success(RPCResponse::BlobsByRange(default_blob_sidecar())),
|
||||
ForkName::Deneb,
|
||||
&chain_spec
|
||||
),
|
||||
Ok(Some(RPCResponse::BlobsByRange(default_blob_sidecar()))),
|
||||
);
|
||||
@@ -1042,6 +1060,7 @@ mod tests {
|
||||
SupportedProtocol::BlobsByRootV1,
|
||||
RPCCodedResponse::Success(RPCResponse::SidecarByRoot(default_blob_sidecar())),
|
||||
ForkName::Deneb,
|
||||
&chain_spec
|
||||
),
|
||||
Ok(Some(RPCResponse::SidecarByRoot(default_blob_sidecar()))),
|
||||
);
|
||||
@@ -1050,11 +1069,14 @@ mod tests {
|
||||
// Test RPCResponse encoding/decoding for V1 messages
|
||||
#[test]
|
||||
fn test_encode_then_decode_v2() {
|
||||
let chain_spec = Spec::default_spec();
|
||||
|
||||
assert_eq!(
|
||||
encode_then_decode_response(
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRange(Arc::new(empty_base_block()))),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRange(Arc::new(
|
||||
empty_base_block()
|
||||
@@ -1069,6 +1091,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRange(Arc::new(empty_base_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRange(Arc::new(
|
||||
empty_base_block()
|
||||
@@ -1080,12 +1103,13 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRange(Arc::new(altair_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRange(Arc::new(altair_block()))))
|
||||
);
|
||||
|
||||
let merge_block_small = merge_block_small(&fork_context(ForkName::Merge));
|
||||
let merge_block_large = merge_block_large(&fork_context(ForkName::Merge));
|
||||
let merge_block_small = merge_block_small(&fork_context(ForkName::Merge), &chain_spec);
|
||||
let merge_block_large = merge_block_large(&fork_context(ForkName::Merge), &chain_spec);
|
||||
|
||||
assert_eq!(
|
||||
encode_then_decode_response(
|
||||
@@ -1094,6 +1118,7 @@ mod tests {
|
||||
merge_block_small.clone()
|
||||
))),
|
||||
ForkName::Merge,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRange(Arc::new(
|
||||
merge_block_small.clone()
|
||||
@@ -1110,6 +1135,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
&mut encoded,
|
||||
ForkName::Merge,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::InvalidData(_)
|
||||
@@ -1122,6 +1148,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRoot(Arc::new(empty_base_block()))),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRoot(
|
||||
Arc::new(empty_base_block())
|
||||
@@ -1136,6 +1163,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRoot(Arc::new(empty_base_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRoot(
|
||||
Arc::new(empty_base_block())
|
||||
@@ -1147,6 +1175,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRoot(Arc::new(altair_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRoot(Arc::new(altair_block()))))
|
||||
);
|
||||
@@ -1158,6 +1187,7 @@ mod tests {
|
||||
merge_block_small.clone()
|
||||
))),
|
||||
ForkName::Merge,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::BlocksByRoot(Arc::new(merge_block_small))))
|
||||
);
|
||||
@@ -1172,6 +1202,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV2,
|
||||
&mut encoded,
|
||||
ForkName::Merge,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::InvalidData(_)
|
||||
@@ -1185,6 +1216,7 @@ mod tests {
|
||||
SupportedProtocol::MetaDataV2,
|
||||
RPCCodedResponse::Success(RPCResponse::MetaData(metadata())),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::MetaData(metadata_v2())))
|
||||
);
|
||||
@@ -1194,6 +1226,7 @@ mod tests {
|
||||
SupportedProtocol::MetaDataV2,
|
||||
RPCCodedResponse::Success(RPCResponse::MetaData(metadata_v2())),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(Some(RPCResponse::MetaData(metadata_v2())))
|
||||
);
|
||||
@@ -1204,11 +1237,14 @@ mod tests {
|
||||
fn test_context_bytes_v2() {
|
||||
let fork_context = fork_context(ForkName::Altair);
|
||||
|
||||
let chain_spec = Spec::default_spec();
|
||||
|
||||
// Removing context bytes for v2 messages should error
|
||||
let mut encoded_bytes = encode_response(
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRange(Arc::new(empty_base_block()))),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1218,7 +1254,8 @@ mod tests {
|
||||
decode_response(
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
&mut encoded_bytes,
|
||||
ForkName::Base
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::ErrorResponse(RPCResponseErrorCode::InvalidRequest, _),
|
||||
@@ -1228,6 +1265,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRoot(Arc::new(empty_base_block()))),
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1237,7 +1275,8 @@ mod tests {
|
||||
decode_response(
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
&mut encoded_bytes,
|
||||
ForkName::Base
|
||||
ForkName::Base,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::ErrorResponse(RPCResponseErrorCode::InvalidRequest, _),
|
||||
@@ -1248,6 +1287,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRange(Arc::new(empty_base_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1260,7 +1300,8 @@ mod tests {
|
||||
decode_response(
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
&mut wrong_fork_bytes,
|
||||
ForkName::Altair
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::SSZDecodeError(_),
|
||||
@@ -1271,6 +1312,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRoot(Arc::new(altair_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1282,7 +1324,8 @@ mod tests {
|
||||
decode_response(
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
&mut wrong_fork_bytes,
|
||||
ForkName::Altair
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::SSZDecodeError(_),
|
||||
@@ -1296,6 +1339,7 @@ mod tests {
|
||||
SupportedProtocol::MetaDataV2,
|
||||
RPCCodedResponse::Success(RPCResponse::MetaData(metadata())),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -1303,7 +1347,8 @@ mod tests {
|
||||
assert!(decode_response(
|
||||
SupportedProtocol::MetaDataV2,
|
||||
&mut encoded_bytes,
|
||||
ForkName::Altair
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.is_err());
|
||||
|
||||
@@ -1312,6 +1357,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRoot(Arc::new(empty_base_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1323,7 +1369,8 @@ mod tests {
|
||||
decode_response(
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
&mut wrong_fork_bytes,
|
||||
ForkName::Altair
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::ErrorResponse(RPCResponseErrorCode::InvalidRequest, _),
|
||||
@@ -1334,6 +1381,7 @@ mod tests {
|
||||
SupportedProtocol::BlocksByRootV2,
|
||||
RPCCodedResponse::Success(RPCResponse::BlocksByRoot(Arc::new(empty_base_block()))),
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1343,7 +1391,8 @@ mod tests {
|
||||
decode_response(
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
&mut part,
|
||||
ForkName::Altair
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
),
|
||||
Ok(None)
|
||||
)
|
||||
@@ -1364,9 +1413,12 @@ mod tests {
|
||||
OutboundRequest::BlobsByRoot(blbroot_request()),
|
||||
OutboundRequest::MetaData(MetadataRequest::new_v2()),
|
||||
];
|
||||
|
||||
let chain_spec = Spec::default_spec();
|
||||
|
||||
for req in requests.iter() {
|
||||
for fork_name in ForkName::list_all() {
|
||||
encode_then_decode_request(req.clone(), fork_name);
|
||||
encode_then_decode_request(req.clone(), fork_name, &chain_spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1420,9 +1472,16 @@ mod tests {
|
||||
assert_eq!(writer.get_ref().len(), 42);
|
||||
dst.extend_from_slice(writer.get_ref());
|
||||
|
||||
let chain_spec = Spec::default_spec();
|
||||
// 10 (for stream identifier) + 80 + 42 = 132 > `max_compressed_len`. Hence, decoding should fail with `InvalidData`.
|
||||
assert!(matches!(
|
||||
decode_response(SupportedProtocol::StatusV1, &mut dst, ForkName::Base).unwrap_err(),
|
||||
decode_response(
|
||||
SupportedProtocol::StatusV1,
|
||||
&mut dst,
|
||||
ForkName::Base,
|
||||
&chain_spec
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::InvalidData(_)
|
||||
));
|
||||
}
|
||||
@@ -1477,12 +1536,15 @@ mod tests {
|
||||
assert_eq!(writer.get_ref().len(), 8103);
|
||||
dst.extend_from_slice(writer.get_ref());
|
||||
|
||||
let chain_spec = Spec::default_spec();
|
||||
|
||||
// 10 (for stream identifier) + 176156 + 8103 = 184269 > `max_compressed_len`. Hence, decoding should fail with `InvalidData`.
|
||||
assert!(matches!(
|
||||
decode_response(
|
||||
SupportedProtocol::BlocksByRangeV2,
|
||||
&mut dst,
|
||||
ForkName::Altair
|
||||
ForkName::Altair,
|
||||
&chain_spec,
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::InvalidData(_)
|
||||
@@ -1510,8 +1572,12 @@ mod tests {
|
||||
let mut uvi_codec: Uvi<usize> = Uvi::default();
|
||||
let mut dst = BytesMut::with_capacity(1024);
|
||||
|
||||
let chain_spec = Spec::default_spec();
|
||||
|
||||
// Insert length-prefix
|
||||
uvi_codec.encode(MAX_RPC_SIZE + 1, &mut dst).unwrap();
|
||||
uvi_codec
|
||||
.encode(chain_spec.max_chunk_size as usize + 1, &mut dst)
|
||||
.unwrap();
|
||||
|
||||
// Insert snappy stream identifier
|
||||
dst.extend_from_slice(stream_identifier);
|
||||
@@ -1523,7 +1589,13 @@ mod tests {
|
||||
dst.extend_from_slice(writer.get_ref());
|
||||
|
||||
assert!(matches!(
|
||||
decode_response(SupportedProtocol::StatusV1, &mut dst, ForkName::Base).unwrap_err(),
|
||||
decode_response(
|
||||
SupportedProtocol::StatusV1,
|
||||
&mut dst,
|
||||
ForkName::Base,
|
||||
&chain_spec
|
||||
)
|
||||
.unwrap_err(),
|
||||
RPCError::InvalidData(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -3,21 +3,19 @@
|
||||
|
||||
use super::methods::{GoodbyeReason, RPCCodedResponse, RPCResponseErrorCode};
|
||||
use super::outbound::OutboundRequestContainer;
|
||||
use super::protocol::{max_rpc_size, InboundRequest, Protocol, RPCError, RPCProtocol};
|
||||
use super::protocol::{InboundOutput, InboundRequest, Protocol, RPCError, RPCProtocol};
|
||||
use super::{RPCReceived, RPCSend, ReqId};
|
||||
use crate::rpc::outbound::{OutboundFramed, OutboundRequest};
|
||||
use crate::rpc::protocol::InboundFramed;
|
||||
use fnv::FnvHashMap;
|
||||
use futures::prelude::*;
|
||||
use futures::{Sink, SinkExt};
|
||||
use libp2p::core::upgrade::{
|
||||
InboundUpgrade, NegotiationError, OutboundUpgrade, ProtocolError, UpgradeError,
|
||||
};
|
||||
use libp2p::swarm::handler::{
|
||||
ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive,
|
||||
ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError,
|
||||
FullyNegotiatedInbound, FullyNegotiatedOutbound, KeepAlive, StreamUpgradeError,
|
||||
SubstreamProtocol,
|
||||
};
|
||||
use libp2p::swarm::NegotiatedSubstream;
|
||||
use libp2p::swarm::Stream;
|
||||
use slog::{crit, debug, trace, warn};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
@@ -31,9 +29,6 @@ use tokio::time::{sleep_until, Instant as TInstant, Sleep};
|
||||
use tokio_util::time::{delay_queue, DelayQueue};
|
||||
use types::{EthSpec, ForkContext};
|
||||
|
||||
/// The time (in seconds) before a substream that is awaiting a response from the user times out.
|
||||
pub const RESPONSE_TIMEOUT: u64 = 10;
|
||||
|
||||
/// The number of times to retry an outbound upgrade in the case of IO errors.
|
||||
const IO_ERROR_RETRIES: u8 = 3;
|
||||
|
||||
@@ -53,7 +48,7 @@ impl SubstreamId {
|
||||
}
|
||||
}
|
||||
|
||||
type InboundSubstream<TSpec> = InboundFramed<NegotiatedSubstream, TSpec>;
|
||||
type InboundSubstream<TSpec> = InboundFramed<Stream, TSpec>;
|
||||
|
||||
/// Events the handler emits to the behaviour.
|
||||
pub type HandlerEvent<Id, T> = Result<RPCReceived<Id, T>, HandlerErr<Id>>;
|
||||
@@ -137,6 +132,9 @@ where
|
||||
|
||||
/// Logger for handling RPC streams
|
||||
log: slog::Logger,
|
||||
|
||||
/// Timeout that will me used for inbound and outbound responses.
|
||||
resp_timeout: Duration,
|
||||
}
|
||||
|
||||
enum HandlerState {
|
||||
@@ -201,12 +199,12 @@ pub enum OutboundSubstreamState<TSpec: EthSpec> {
|
||||
/// handler because GOODBYE requests can be handled and responses dropped instantly.
|
||||
RequestPendingResponse {
|
||||
/// The framed negotiated substream.
|
||||
substream: Box<OutboundFramed<NegotiatedSubstream, TSpec>>,
|
||||
substream: Box<OutboundFramed<Stream, TSpec>>,
|
||||
/// Keeps track of the actual request sent.
|
||||
request: OutboundRequest<TSpec>,
|
||||
},
|
||||
/// Closing an outbound substream>
|
||||
Closing(Box<OutboundFramed<NegotiatedSubstream, TSpec>>),
|
||||
Closing(Box<OutboundFramed<Stream, TSpec>>),
|
||||
/// Temporary state during processing
|
||||
Poisoned,
|
||||
}
|
||||
@@ -219,6 +217,7 @@ where
|
||||
listen_protocol: SubstreamProtocol<RPCProtocol<TSpec>, ()>,
|
||||
fork_context: Arc<ForkContext>,
|
||||
log: &slog::Logger,
|
||||
resp_timeout: Duration,
|
||||
) -> Self {
|
||||
RPCHandler {
|
||||
listen_protocol,
|
||||
@@ -237,6 +236,7 @@ where
|
||||
fork_context,
|
||||
waker: None,
|
||||
log: log.clone(),
|
||||
resp_timeout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,8 +321,8 @@ where
|
||||
TSpec: EthSpec,
|
||||
Id: ReqId,
|
||||
{
|
||||
type InEvent = RPCSend<Id, TSpec>;
|
||||
type OutEvent = HandlerEvent<Id, TSpec>;
|
||||
type FromBehaviour = RPCSend<Id, TSpec>;
|
||||
type ToBehaviour = HandlerEvent<Id, TSpec>;
|
||||
type Error = RPCError;
|
||||
type InboundProtocol = RPCProtocol<TSpec>;
|
||||
type OutboundProtocol = OutboundRequestContainer<TSpec>;
|
||||
@@ -333,121 +333,7 @@ where
|
||||
self.listen_protocol.clone()
|
||||
}
|
||||
|
||||
fn inject_fully_negotiated_outbound(
|
||||
&mut self,
|
||||
out: <Self::OutboundProtocol as OutboundUpgrade<NegotiatedSubstream>>::Output,
|
||||
request_info: Self::OutboundOpenInfo,
|
||||
) {
|
||||
self.dial_negotiated -= 1;
|
||||
let (id, request) = request_info;
|
||||
let proto = request.versioned_protocol().protocol();
|
||||
|
||||
// accept outbound connections only if the handler is not deactivated
|
||||
if matches!(self.state, HandlerState::Deactivated) {
|
||||
self.events_out.push(Err(HandlerErr::Outbound {
|
||||
error: RPCError::Disconnected,
|
||||
proto,
|
||||
id,
|
||||
}));
|
||||
}
|
||||
|
||||
// add the stream to substreams if we expect a response, otherwise drop the stream.
|
||||
let expected_responses = request.expected_responses();
|
||||
if expected_responses > 0 {
|
||||
// new outbound request. Store the stream and tag the output.
|
||||
let delay_key = self.outbound_substreams_delay.insert(
|
||||
self.current_outbound_substream_id,
|
||||
Duration::from_secs(RESPONSE_TIMEOUT),
|
||||
);
|
||||
let awaiting_stream = OutboundSubstreamState::RequestPendingResponse {
|
||||
substream: Box::new(out),
|
||||
request,
|
||||
};
|
||||
let expected_responses = if expected_responses > 1 {
|
||||
// Currently enforced only for multiple responses
|
||||
Some(expected_responses)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if self
|
||||
.outbound_substreams
|
||||
.insert(
|
||||
self.current_outbound_substream_id,
|
||||
OutboundInfo {
|
||||
state: awaiting_stream,
|
||||
delay_key,
|
||||
proto,
|
||||
remaining_chunks: expected_responses,
|
||||
req_id: id,
|
||||
},
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
crit!(self.log, "Duplicate outbound substream id"; "id" => self.current_outbound_substream_id);
|
||||
}
|
||||
self.current_outbound_substream_id.0 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_fully_negotiated_inbound(
|
||||
&mut self,
|
||||
substream: <Self::InboundProtocol as InboundUpgrade<NegotiatedSubstream>>::Output,
|
||||
_info: Self::InboundOpenInfo,
|
||||
) {
|
||||
// only accept new peer requests when active
|
||||
if !matches!(self.state, HandlerState::Active) {
|
||||
return;
|
||||
}
|
||||
|
||||
let (req, substream) = substream;
|
||||
let expected_responses = req.expected_responses();
|
||||
|
||||
// store requests that expect responses
|
||||
if expected_responses > 0 {
|
||||
if self.inbound_substreams.len() < MAX_INBOUND_SUBSTREAMS {
|
||||
// Store the stream and tag the output.
|
||||
let delay_key = self.inbound_substreams_delay.insert(
|
||||
self.current_inbound_substream_id,
|
||||
Duration::from_secs(RESPONSE_TIMEOUT),
|
||||
);
|
||||
let awaiting_stream = InboundState::Idle(substream);
|
||||
self.inbound_substreams.insert(
|
||||
self.current_inbound_substream_id,
|
||||
InboundInfo {
|
||||
state: awaiting_stream,
|
||||
pending_items: VecDeque::with_capacity(std::cmp::min(
|
||||
expected_responses,
|
||||
128,
|
||||
) as usize),
|
||||
delay_key: Some(delay_key),
|
||||
protocol: req.versioned_protocol().protocol(),
|
||||
request_start_time: Instant::now(),
|
||||
remaining_chunks: expected_responses,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.events_out.push(Err(HandlerErr::Inbound {
|
||||
id: self.current_inbound_substream_id,
|
||||
proto: req.versioned_protocol().protocol(),
|
||||
error: RPCError::HandlerRejected,
|
||||
}));
|
||||
return self.shutdown(None);
|
||||
}
|
||||
}
|
||||
|
||||
// If we received a goodbye, shutdown the connection.
|
||||
if let InboundRequest::Goodbye(_) = req {
|
||||
self.shutdown(None);
|
||||
}
|
||||
|
||||
self.events_out.push(Ok(RPCReceived::Request(
|
||||
self.current_inbound_substream_id,
|
||||
req,
|
||||
)));
|
||||
self.current_inbound_substream_id.0 += 1;
|
||||
}
|
||||
|
||||
fn inject_event(&mut self, rpc_event: Self::InEvent) {
|
||||
fn on_behaviour_event(&mut self, rpc_event: Self::FromBehaviour) {
|
||||
match rpc_event {
|
||||
RPCSend::Request(id, req) => self.send_request(id, req),
|
||||
RPCSend::Response(inbound_id, response) => self.send_response(inbound_id, response),
|
||||
@@ -459,56 +345,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_dial_upgrade_error(
|
||||
&mut self,
|
||||
request_info: Self::OutboundOpenInfo,
|
||||
error: ConnectionHandlerUpgrErr<
|
||||
<Self::OutboundProtocol as OutboundUpgrade<NegotiatedSubstream>>::Error,
|
||||
>,
|
||||
) {
|
||||
let (id, req) = request_info;
|
||||
if let ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Apply(RPCError::IoError(_))) = error
|
||||
{
|
||||
self.outbound_io_error_retries += 1;
|
||||
if self.outbound_io_error_retries < IO_ERROR_RETRIES {
|
||||
self.send_request(id, req);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This dialing is now considered failed
|
||||
self.dial_negotiated -= 1;
|
||||
|
||||
self.outbound_io_error_retries = 0;
|
||||
// map the error
|
||||
let error = match error {
|
||||
ConnectionHandlerUpgrErr::Timer => RPCError::InternalError("Timer failed"),
|
||||
ConnectionHandlerUpgrErr::Timeout => RPCError::NegotiationTimeout,
|
||||
ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Apply(e)) => e,
|
||||
ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Select(NegotiationError::Failed)) => {
|
||||
RPCError::UnsupportedProtocol
|
||||
}
|
||||
ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Select(
|
||||
NegotiationError::ProtocolError(e),
|
||||
)) => match e {
|
||||
ProtocolError::IoError(io_err) => RPCError::IoError(io_err.to_string()),
|
||||
ProtocolError::InvalidProtocol => {
|
||||
RPCError::InternalError("Protocol was deemed invalid")
|
||||
}
|
||||
ProtocolError::InvalidMessage | ProtocolError::TooManyProtocols => {
|
||||
// Peer is sending invalid data during the negotiation phase, not
|
||||
// participating in the protocol
|
||||
RPCError::InvalidData("Invalid message during negotiation".to_string())
|
||||
}
|
||||
},
|
||||
};
|
||||
self.events_out.push(Err(HandlerErr::Outbound {
|
||||
error,
|
||||
proto: req.versioned_protocol().protocol(),
|
||||
id,
|
||||
}));
|
||||
}
|
||||
|
||||
fn connection_keep_alive(&self) -> KeepAlive {
|
||||
// Check that we don't have outbound items pending for dialing, nor dialing, nor
|
||||
// established. Also check that there are no established inbound substreams.
|
||||
@@ -541,7 +377,7 @@ where
|
||||
ConnectionHandlerEvent<
|
||||
Self::OutboundProtocol,
|
||||
Self::OutboundOpenInfo,
|
||||
Self::OutEvent,
|
||||
Self::ToBehaviour,
|
||||
Self::Error,
|
||||
>,
|
||||
> {
|
||||
@@ -554,7 +390,9 @@ where
|
||||
}
|
||||
// return any events that need to be reported
|
||||
if !self.events_out.is_empty() {
|
||||
return Poll::Ready(ConnectionHandlerEvent::Custom(self.events_out.remove(0)));
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
|
||||
self.events_out.remove(0),
|
||||
));
|
||||
} else {
|
||||
self.events_out.shrink_to_fit();
|
||||
}
|
||||
@@ -618,7 +456,9 @@ where
|
||||
error: RPCError::StreamTimeout,
|
||||
};
|
||||
// notify the user
|
||||
return Poll::Ready(ConnectionHandlerEvent::Custom(Err(outbound_err)));
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Err(
|
||||
outbound_err,
|
||||
)));
|
||||
} else {
|
||||
crit!(self.log, "timed out substream not in the books"; "stream_id" => outbound_id.get_ref());
|
||||
}
|
||||
@@ -720,7 +560,7 @@ where
|
||||
// Each chunk is allowed RESPONSE_TIMEOUT to be sent.
|
||||
if let Some(ref delay_key) = info.delay_key {
|
||||
self.inbound_substreams_delay
|
||||
.reset(delay_key, Duration::from_secs(RESPONSE_TIMEOUT));
|
||||
.reset(delay_key, self.resp_timeout);
|
||||
}
|
||||
|
||||
// The stream may be currently idle. Attempt to process more
|
||||
@@ -860,7 +700,7 @@ where
|
||||
};
|
||||
substream_entry.remaining_chunks = Some(remaining_chunks);
|
||||
self.outbound_substreams_delay
|
||||
.reset(delay_key, Duration::from_secs(RESPONSE_TIMEOUT));
|
||||
.reset(delay_key, self.resp_timeout);
|
||||
}
|
||||
} else {
|
||||
// either this is a single response request or this response closes the
|
||||
@@ -884,7 +724,7 @@ where
|
||||
}),
|
||||
};
|
||||
|
||||
return Poll::Ready(ConnectionHandlerEvent::Custom(received));
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(received));
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
// stream closed
|
||||
@@ -899,7 +739,7 @@ where
|
||||
// notify the application error
|
||||
if request.expected_responses() > 1 {
|
||||
// return an end of stream result
|
||||
return Poll::Ready(ConnectionHandlerEvent::Custom(Ok(
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Ok(
|
||||
RPCReceived::EndOfStream(request_id, request.stream_termination()),
|
||||
)));
|
||||
}
|
||||
@@ -910,7 +750,9 @@ where
|
||||
proto: request.versioned_protocol().protocol(),
|
||||
error: RPCError::IncompleteStream,
|
||||
};
|
||||
return Poll::Ready(ConnectionHandlerEvent::Custom(Err(outbound_err)));
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Err(
|
||||
outbound_err,
|
||||
)));
|
||||
}
|
||||
Poll::Pending => {
|
||||
entry.get_mut().state =
|
||||
@@ -926,7 +768,9 @@ where
|
||||
error: e,
|
||||
};
|
||||
entry.remove_entry();
|
||||
return Poll::Ready(ConnectionHandlerEvent::Custom(Err(outbound_err)));
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Err(
|
||||
outbound_err,
|
||||
)));
|
||||
}
|
||||
},
|
||||
OutboundSubstreamState::Closing(mut substream) => {
|
||||
@@ -947,7 +791,7 @@ where
|
||||
// termination to the application
|
||||
|
||||
if let Some(termination) = protocol.terminator() {
|
||||
return Poll::Ready(ConnectionHandlerEvent::Custom(Ok(
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Ok(
|
||||
RPCReceived::EndOfStream(request_id, termination),
|
||||
)));
|
||||
}
|
||||
@@ -974,7 +818,7 @@ where
|
||||
OutboundRequestContainer {
|
||||
req: req.clone(),
|
||||
fork_context: self.fork_context.clone(),
|
||||
max_rpc_size: max_rpc_size(&self.fork_context),
|
||||
max_rpc_size: self.listen_protocol().upgrade().max_rpc_size,
|
||||
},
|
||||
(),
|
||||
)
|
||||
@@ -996,6 +840,205 @@ where
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
fn on_connection_event(
|
||||
&mut self,
|
||||
event: ConnectionEvent<
|
||||
Self::InboundProtocol,
|
||||
Self::OutboundProtocol,
|
||||
Self::InboundOpenInfo,
|
||||
Self::OutboundOpenInfo,
|
||||
>,
|
||||
) {
|
||||
match event {
|
||||
ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound {
|
||||
protocol,
|
||||
info: _,
|
||||
}) => self.on_fully_negotiated_inbound(protocol),
|
||||
ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound {
|
||||
protocol,
|
||||
info,
|
||||
}) => self.on_fully_negotiated_outbound(protocol, info),
|
||||
ConnectionEvent::DialUpgradeError(DialUpgradeError { info, error }) => {
|
||||
self.on_dial_upgrade_error(info, error)
|
||||
}
|
||||
ConnectionEvent::ListenUpgradeError(libp2p::swarm::handler::ListenUpgradeError {
|
||||
info: _,
|
||||
error: _, /* RPCError */
|
||||
}) => {
|
||||
// This is going to be removed in the next libp2p release. I think its fine to do
|
||||
// nothing.
|
||||
}
|
||||
ConnectionEvent::LocalProtocolsChange(_) => {
|
||||
// This shouldn't effect this handler, we will still negotiate streams if we support
|
||||
// the protocol as usual.
|
||||
}
|
||||
ConnectionEvent::RemoteProtocolsChange(_) => {
|
||||
// This shouldn't effect this handler, we will still negotiate streams if we support
|
||||
// the protocol as usual.
|
||||
}
|
||||
ConnectionEvent::AddressChange(_) => {
|
||||
// We dont care about these changes as they have no bearing on our RPC internal
|
||||
// logic.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Id, TSpec: EthSpec> RPCHandler<Id, TSpec>
|
||||
where
|
||||
Id: ReqId,
|
||||
TSpec: EthSpec,
|
||||
{
|
||||
fn on_fully_negotiated_inbound(&mut self, substream: InboundOutput<Stream, TSpec>) {
|
||||
// only accept new peer requests when active
|
||||
if !matches!(self.state, HandlerState::Active) {
|
||||
return;
|
||||
}
|
||||
|
||||
let (req, substream) = substream;
|
||||
let expected_responses = req.expected_responses();
|
||||
|
||||
// store requests that expect responses
|
||||
if expected_responses > 0 {
|
||||
if self.inbound_substreams.len() < MAX_INBOUND_SUBSTREAMS {
|
||||
// Store the stream and tag the output.
|
||||
let delay_key = self
|
||||
.inbound_substreams_delay
|
||||
.insert(self.current_inbound_substream_id, self.resp_timeout);
|
||||
let awaiting_stream = InboundState::Idle(substream);
|
||||
self.inbound_substreams.insert(
|
||||
self.current_inbound_substream_id,
|
||||
InboundInfo {
|
||||
state: awaiting_stream,
|
||||
pending_items: VecDeque::with_capacity(std::cmp::min(
|
||||
expected_responses,
|
||||
128,
|
||||
) as usize),
|
||||
delay_key: Some(delay_key),
|
||||
protocol: req.versioned_protocol().protocol(),
|
||||
request_start_time: Instant::now(),
|
||||
remaining_chunks: expected_responses,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
self.events_out.push(Err(HandlerErr::Inbound {
|
||||
id: self.current_inbound_substream_id,
|
||||
proto: req.versioned_protocol().protocol(),
|
||||
error: RPCError::HandlerRejected,
|
||||
}));
|
||||
return self.shutdown(None);
|
||||
}
|
||||
}
|
||||
|
||||
// If we received a goodbye, shutdown the connection.
|
||||
if let InboundRequest::Goodbye(_) = req {
|
||||
self.shutdown(None);
|
||||
}
|
||||
|
||||
self.events_out.push(Ok(RPCReceived::Request(
|
||||
self.current_inbound_substream_id,
|
||||
req,
|
||||
)));
|
||||
self.current_inbound_substream_id.0 += 1;
|
||||
}
|
||||
|
||||
fn on_fully_negotiated_outbound(
|
||||
&mut self,
|
||||
substream: OutboundFramed<Stream, TSpec>,
|
||||
(id, request): (Id, OutboundRequest<TSpec>),
|
||||
) {
|
||||
self.dial_negotiated -= 1;
|
||||
// Reset any io-retries counter.
|
||||
self.outbound_io_error_retries = 0;
|
||||
|
||||
let proto = request.versioned_protocol().protocol();
|
||||
|
||||
// accept outbound connections only if the handler is not deactivated
|
||||
if matches!(self.state, HandlerState::Deactivated) {
|
||||
self.events_out.push(Err(HandlerErr::Outbound {
|
||||
error: RPCError::Disconnected,
|
||||
proto,
|
||||
id,
|
||||
}));
|
||||
}
|
||||
|
||||
// add the stream to substreams if we expect a response, otherwise drop the stream.
|
||||
let expected_responses = request.expected_responses();
|
||||
if expected_responses > 0 {
|
||||
// new outbound request. Store the stream and tag the output.
|
||||
let delay_key = self
|
||||
.outbound_substreams_delay
|
||||
.insert(self.current_outbound_substream_id, self.resp_timeout);
|
||||
let awaiting_stream = OutboundSubstreamState::RequestPendingResponse {
|
||||
substream: Box::new(substream),
|
||||
request,
|
||||
};
|
||||
let expected_responses = if expected_responses > 1 {
|
||||
// Currently enforced only for multiple responses
|
||||
Some(expected_responses)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if self
|
||||
.outbound_substreams
|
||||
.insert(
|
||||
self.current_outbound_substream_id,
|
||||
OutboundInfo {
|
||||
state: awaiting_stream,
|
||||
delay_key,
|
||||
proto,
|
||||
remaining_chunks: expected_responses,
|
||||
req_id: id,
|
||||
},
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
crit!(self.log, "Duplicate outbound substream id"; "id" => self.current_outbound_substream_id);
|
||||
}
|
||||
self.current_outbound_substream_id.0 += 1;
|
||||
}
|
||||
}
|
||||
fn on_dial_upgrade_error(
|
||||
&mut self,
|
||||
request_info: (Id, OutboundRequest<TSpec>),
|
||||
error: StreamUpgradeError<RPCError>,
|
||||
) {
|
||||
let (id, req) = request_info;
|
||||
|
||||
// map the error
|
||||
let error = match error {
|
||||
StreamUpgradeError::Timeout => RPCError::NegotiationTimeout,
|
||||
StreamUpgradeError::Apply(RPCError::IoError(e)) => {
|
||||
self.outbound_io_error_retries += 1;
|
||||
if self.outbound_io_error_retries < IO_ERROR_RETRIES {
|
||||
self.send_request(id, req);
|
||||
return;
|
||||
}
|
||||
RPCError::IoError(e)
|
||||
}
|
||||
StreamUpgradeError::NegotiationFailed => RPCError::UnsupportedProtocol,
|
||||
StreamUpgradeError::Io(io_err) => {
|
||||
self.outbound_io_error_retries += 1;
|
||||
if self.outbound_io_error_retries < IO_ERROR_RETRIES {
|
||||
self.send_request(id, req);
|
||||
return;
|
||||
}
|
||||
RPCError::IoError(io_err.to_string())
|
||||
}
|
||||
StreamUpgradeError::Apply(other) => other,
|
||||
};
|
||||
|
||||
// This dialing is now considered failed
|
||||
self.dial_negotiated -= 1;
|
||||
|
||||
self.outbound_io_error_retries = 0;
|
||||
self.events_out.push(Err(HandlerErr::Outbound {
|
||||
error,
|
||||
proto: req.versioned_protocol().protocol(),
|
||||
id,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl slog::Value for SubstreamId {
|
||||
|
||||
@@ -6,22 +6,23 @@
|
||||
|
||||
use futures::future::FutureExt;
|
||||
use handler::{HandlerEvent, RPCHandler};
|
||||
use libp2p::core::connection::ConnectionId;
|
||||
use libp2p::swarm::{
|
||||
handler::ConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler,
|
||||
PollParameters, SubstreamProtocol,
|
||||
handler::ConnectionHandler, ConnectionId, NetworkBehaviour, NotifyHandler, PollParameters,
|
||||
ToSwarm,
|
||||
};
|
||||
use libp2p::swarm::{FromSwarm, SubstreamProtocol, THandlerInEvent};
|
||||
use libp2p::PeerId;
|
||||
use rate_limiter::{RPCRateLimiter as RateLimiter, RateLimitedErr};
|
||||
use slog::{crit, debug, o};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
use types::{EthSpec, ForkContext};
|
||||
|
||||
pub(crate) use handler::HandlerErr;
|
||||
pub(crate) use methods::{MetaData, MetaDataV1, MetaDataV2, Ping, RPCCodedResponse, RPCResponse};
|
||||
pub(crate) use protocol::{InboundRequest, RPCProtocol};
|
||||
pub(crate) use protocol::InboundRequest;
|
||||
|
||||
pub use handler::SubstreamId;
|
||||
pub use methods::{
|
||||
@@ -32,6 +33,7 @@ pub(crate) use outbound::OutboundRequest;
|
||||
pub use protocol::{max_rpc_size, Protocol, RPCError};
|
||||
|
||||
use self::config::{InboundRateLimiterConfig, OutboundRateLimiterConfig};
|
||||
use self::protocol::RPCProtocol;
|
||||
use self::self_limiter::SelfRateLimiter;
|
||||
|
||||
pub(crate) mod codec;
|
||||
@@ -104,8 +106,13 @@ pub struct RPCMessage<Id, TSpec: EthSpec> {
|
||||
pub event: HandlerEvent<Id, TSpec>,
|
||||
}
|
||||
|
||||
type BehaviourAction<Id, TSpec> =
|
||||
NetworkBehaviourAction<RPCMessage<Id, TSpec>, RPCHandler<Id, TSpec>>;
|
||||
type BehaviourAction<Id, TSpec> = ToSwarm<RPCMessage<Id, TSpec>, RPCSend<Id, TSpec>>;
|
||||
|
||||
pub struct NetworkParams {
|
||||
pub max_chunk_size: usize,
|
||||
pub ttfb_timeout: Duration,
|
||||
pub resp_timeout: Duration,
|
||||
}
|
||||
|
||||
/// Implements the libp2p `NetworkBehaviour` trait and therefore manages network-level
|
||||
/// logic.
|
||||
@@ -120,6 +127,8 @@ pub struct RPC<Id: ReqId, TSpec: EthSpec> {
|
||||
enable_light_client_server: bool,
|
||||
/// Slog logger for RPC behaviour.
|
||||
log: slog::Logger,
|
||||
/// Networking constant values
|
||||
network_params: NetworkParams,
|
||||
}
|
||||
|
||||
impl<Id: ReqId, TSpec: EthSpec> RPC<Id, TSpec> {
|
||||
@@ -129,6 +138,7 @@ impl<Id: ReqId, TSpec: EthSpec> RPC<Id, TSpec> {
|
||||
inbound_rate_limiter_config: Option<InboundRateLimiterConfig>,
|
||||
outbound_rate_limiter_config: Option<OutboundRateLimiterConfig>,
|
||||
log: slog::Logger,
|
||||
network_params: NetworkParams,
|
||||
) -> Self {
|
||||
let log = log.new(o!("service" => "libp2p_rpc"));
|
||||
|
||||
@@ -149,6 +159,7 @@ impl<Id: ReqId, TSpec: EthSpec> RPC<Id, TSpec> {
|
||||
fork_context,
|
||||
enable_light_client_server,
|
||||
log,
|
||||
network_params,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +172,7 @@ impl<Id: ReqId, TSpec: EthSpec> RPC<Id, TSpec> {
|
||||
id: (ConnectionId, SubstreamId),
|
||||
event: RPCCodedResponse<TSpec>,
|
||||
) {
|
||||
self.events.push(NetworkBehaviourAction::NotifyHandler {
|
||||
self.events.push(ToSwarm::NotifyHandler {
|
||||
peer_id,
|
||||
handler: NotifyHandler::One(id.0),
|
||||
event: RPCSend::Response(id.1, event),
|
||||
@@ -181,7 +192,7 @@ impl<Id: ReqId, TSpec: EthSpec> RPC<Id, TSpec> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NetworkBehaviourAction::NotifyHandler {
|
||||
ToSwarm::NotifyHandler {
|
||||
peer_id,
|
||||
handler: NotifyHandler::Any,
|
||||
event: RPCSend::Request(request_id, req),
|
||||
@@ -194,7 +205,7 @@ impl<Id: ReqId, TSpec: EthSpec> RPC<Id, TSpec> {
|
||||
/// Lighthouse wishes to disconnect from this peer by sending a Goodbye message. This
|
||||
/// gracefully terminates the RPC behaviour with a goodbye message.
|
||||
pub fn shutdown(&mut self, peer_id: PeerId, id: Id, reason: GoodbyeReason) {
|
||||
self.events.push(NetworkBehaviourAction::NotifyHandler {
|
||||
self.events.push(ToSwarm::NotifyHandler {
|
||||
peer_id,
|
||||
handler: NotifyHandler::Any,
|
||||
event: RPCSend::Shutdown(id, reason),
|
||||
@@ -208,29 +219,95 @@ where
|
||||
Id: ReqId,
|
||||
{
|
||||
type ConnectionHandler = RPCHandler<Id, TSpec>;
|
||||
type OutEvent = RPCMessage<Id, TSpec>;
|
||||
type ToSwarm = RPCMessage<Id, TSpec>;
|
||||
|
||||
fn new_handler(&mut self) -> Self::ConnectionHandler {
|
||||
RPCHandler::new(
|
||||
SubstreamProtocol::new(
|
||||
RPCProtocol {
|
||||
fork_context: self.fork_context.clone(),
|
||||
max_rpc_size: max_rpc_size(&self.fork_context),
|
||||
enable_light_client_server: self.enable_light_client_server,
|
||||
phantom: PhantomData,
|
||||
},
|
||||
(),
|
||||
),
|
||||
fn handle_established_inbound_connection(
|
||||
&mut self,
|
||||
_connection_id: ConnectionId,
|
||||
peer_id: PeerId,
|
||||
_local_addr: &libp2p::Multiaddr,
|
||||
_remote_addr: &libp2p::Multiaddr,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
let protocol = SubstreamProtocol::new(
|
||||
RPCProtocol {
|
||||
fork_context: self.fork_context.clone(),
|
||||
max_rpc_size: max_rpc_size(&self.fork_context, self.network_params.max_chunk_size),
|
||||
enable_light_client_server: self.enable_light_client_server,
|
||||
phantom: PhantomData,
|
||||
ttfb_timeout: self.network_params.ttfb_timeout,
|
||||
},
|
||||
(),
|
||||
);
|
||||
// NOTE: this is needed because PeerIds have interior mutability.
|
||||
let peer_repr = peer_id.to_string();
|
||||
let log = self.log.new(slog::o!("peer_id" => peer_repr));
|
||||
let handler = RPCHandler::new(
|
||||
protocol,
|
||||
self.fork_context.clone(),
|
||||
&self.log,
|
||||
)
|
||||
&log,
|
||||
self.network_params.resp_timeout,
|
||||
);
|
||||
|
||||
Ok(handler)
|
||||
}
|
||||
|
||||
fn inject_event(
|
||||
fn handle_established_outbound_connection(
|
||||
&mut self,
|
||||
_connection_id: ConnectionId,
|
||||
peer_id: PeerId,
|
||||
_addr: &libp2p::Multiaddr,
|
||||
_role_override: libp2p::core::Endpoint,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
let protocol = SubstreamProtocol::new(
|
||||
RPCProtocol {
|
||||
fork_context: self.fork_context.clone(),
|
||||
max_rpc_size: max_rpc_size(&self.fork_context, self.network_params.max_chunk_size),
|
||||
enable_light_client_server: self.enable_light_client_server,
|
||||
phantom: PhantomData,
|
||||
ttfb_timeout: self.network_params.ttfb_timeout,
|
||||
},
|
||||
(),
|
||||
);
|
||||
|
||||
// NOTE: this is needed because PeerIds have interior mutability.
|
||||
let peer_repr = peer_id.to_string();
|
||||
let log = self.log.new(slog::o!("peer_id" => peer_repr));
|
||||
let handler = RPCHandler::new(
|
||||
protocol,
|
||||
self.fork_context.clone(),
|
||||
&log,
|
||||
self.network_params.resp_timeout,
|
||||
);
|
||||
|
||||
Ok(handler)
|
||||
}
|
||||
|
||||
fn on_swarm_event(&mut self, event: FromSwarm<Self::ConnectionHandler>) {
|
||||
match event {
|
||||
FromSwarm::ConnectionClosed(_)
|
||||
| FromSwarm::ConnectionEstablished(_)
|
||||
| FromSwarm::AddressChange(_)
|
||||
| FromSwarm::DialFailure(_)
|
||||
| FromSwarm::ListenFailure(_)
|
||||
| FromSwarm::NewListener(_)
|
||||
| FromSwarm::NewListenAddr(_)
|
||||
| FromSwarm::ExpiredListenAddr(_)
|
||||
| FromSwarm::ListenerError(_)
|
||||
| FromSwarm::ListenerClosed(_)
|
||||
| FromSwarm::NewExternalAddrCandidate(_)
|
||||
| FromSwarm::ExternalAddrExpired(_)
|
||||
| FromSwarm::ExternalAddrConfirmed(_) => {
|
||||
// Rpc Behaviour does not act on these swarm events. We use a comprehensive match
|
||||
// statement to ensure future events are dealt with appropriately.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_connection_handler_event(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
conn_id: ConnectionId,
|
||||
event: <Self::ConnectionHandler as ConnectionHandler>::OutEvent,
|
||||
event: <Self::ConnectionHandler as ConnectionHandler>::ToBehaviour,
|
||||
) {
|
||||
if let Ok(RPCReceived::Request(ref id, ref req)) = event {
|
||||
if let Some(limiter) = self.limiter.as_mut() {
|
||||
@@ -238,12 +315,11 @@ where
|
||||
match limiter.allows(&peer_id, req) {
|
||||
Ok(()) => {
|
||||
// send the event to the user
|
||||
self.events
|
||||
.push(NetworkBehaviourAction::GenerateEvent(RPCMessage {
|
||||
peer_id,
|
||||
conn_id,
|
||||
event,
|
||||
}))
|
||||
self.events.push(ToSwarm::GenerateEvent(RPCMessage {
|
||||
peer_id,
|
||||
conn_id,
|
||||
event,
|
||||
}))
|
||||
}
|
||||
Err(RateLimitedErr::TooLarge) => {
|
||||
// we set the batch sizes, so this is a coding/config err for most protocols
|
||||
@@ -283,20 +359,18 @@ where
|
||||
}
|
||||
} else {
|
||||
// No rate limiting, send the event to the user
|
||||
self.events
|
||||
.push(NetworkBehaviourAction::GenerateEvent(RPCMessage {
|
||||
peer_id,
|
||||
conn_id,
|
||||
event,
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
self.events
|
||||
.push(NetworkBehaviourAction::GenerateEvent(RPCMessage {
|
||||
self.events.push(ToSwarm::GenerateEvent(RPCMessage {
|
||||
peer_id,
|
||||
conn_id,
|
||||
event,
|
||||
}));
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
self.events.push(ToSwarm::GenerateEvent(RPCMessage {
|
||||
peer_id,
|
||||
conn_id,
|
||||
event,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +378,7 @@ where
|
||||
&mut self,
|
||||
cx: &mut Context,
|
||||
_: &mut impl PollParameters,
|
||||
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ConnectionHandler>> {
|
||||
) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
|
||||
// let the rate limiter prune.
|
||||
if let Some(limiter) = self.limiter.as_mut() {
|
||||
let _ = limiter.poll_unpin(cx);
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::rpc::{
|
||||
use futures::future::BoxFuture;
|
||||
use futures::prelude::{AsyncRead, AsyncWrite};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use libp2p::core::{InboundUpgrade, ProtocolName, UpgradeInfo};
|
||||
use libp2p::core::{InboundUpgrade, UpgradeInfo};
|
||||
use ssz::Encode;
|
||||
use ssz_types::VariableList;
|
||||
use std::io;
|
||||
@@ -72,7 +72,7 @@ lazy_static! {
|
||||
/// The `BeaconBlockMerge` block has an `ExecutionPayload` field which has a max size ~16 GiB for future proofing.
|
||||
/// We calculate the value from its fields instead of constructing the block and checking the length.
|
||||
/// Note: This is only the theoretical upper bound. We further bound the max size we receive over the network
|
||||
/// with `MAX_RPC_SIZE_POST_MERGE`.
|
||||
/// with `max_chunk_size`.
|
||||
pub static ref SIGNED_BEACON_BLOCK_MERGE_MAX: usize =
|
||||
// Size of a full altair block
|
||||
*SIGNED_BEACON_BLOCK_ALTAIR_MAX
|
||||
@@ -129,27 +129,19 @@ lazy_static! {
|
||||
.len();
|
||||
}
|
||||
|
||||
/// The maximum bytes that can be sent across the RPC pre-merge.
|
||||
pub(crate) const MAX_RPC_SIZE: usize = 1_048_576; // 1M
|
||||
/// The maximum bytes that can be sent across the RPC post-merge.
|
||||
pub(crate) const MAX_RPC_SIZE_POST_MERGE: usize = 10 * 1_048_576; // 10M
|
||||
pub(crate) const MAX_RPC_SIZE_POST_CAPELLA: usize = 10 * 1_048_576; // 10M
|
||||
pub(crate) const MAX_RPC_SIZE_POST_DENEB: usize = 10 * 1_048_576; // 10M
|
||||
/// The protocol prefix the RPC protocol id.
|
||||
const PROTOCOL_PREFIX: &str = "/eth2/beacon_chain/req";
|
||||
/// Time allowed for the first byte of a request to arrive before we time out (Time To First Byte).
|
||||
const TTFB_TIMEOUT: u64 = 5;
|
||||
/// The number of seconds to wait for the first bytes of a request once a protocol has been
|
||||
/// established before the stream is terminated.
|
||||
const REQUEST_TIMEOUT: u64 = 15;
|
||||
|
||||
/// Returns the maximum bytes that can be sent across the RPC.
|
||||
pub fn max_rpc_size(fork_context: &ForkContext) -> usize {
|
||||
pub fn max_rpc_size(fork_context: &ForkContext, max_chunk_size: usize) -> usize {
|
||||
match fork_context.current_fork() {
|
||||
ForkName::Altair | ForkName::Base => MAX_RPC_SIZE,
|
||||
ForkName::Merge => MAX_RPC_SIZE_POST_MERGE,
|
||||
ForkName::Capella => MAX_RPC_SIZE_POST_CAPELLA,
|
||||
ForkName::Deneb => MAX_RPC_SIZE_POST_DENEB,
|
||||
ForkName::Altair | ForkName::Base => max_chunk_size / 10,
|
||||
ForkName::Merge => max_chunk_size,
|
||||
ForkName::Capella => max_chunk_size,
|
||||
ForkName::Deneb => max_chunk_size,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +310,7 @@ pub struct RPCProtocol<TSpec: EthSpec> {
|
||||
pub max_rpc_size: usize,
|
||||
pub enable_light_client_server: bool,
|
||||
pub phantom: PhantomData<TSpec>,
|
||||
pub ttfb_timeout: Duration,
|
||||
}
|
||||
|
||||
impl<TSpec: EthSpec> UpgradeInfo for RPCProtocol<TSpec> {
|
||||
@@ -376,6 +369,12 @@ pub struct ProtocolId {
|
||||
protocol_id: String,
|
||||
}
|
||||
|
||||
impl AsRef<str> for ProtocolId {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.protocol_id.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolId {
|
||||
/// Returns min and max size for messages of given protocol id requests.
|
||||
pub fn rpc_request_limits(&self) -> RpcLimits {
|
||||
@@ -488,12 +487,6 @@ pub fn rpc_blob_limits<T: EthSpec>() -> RpcLimits {
|
||||
)
|
||||
}
|
||||
|
||||
impl ProtocolName for ProtocolId {
|
||||
fn protocol_name(&self) -> &[u8] {
|
||||
self.protocol_id.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
/* Inbound upgrade */
|
||||
|
||||
// The inbound protocol reads the request, decodes it and returns the stream to the protocol
|
||||
@@ -528,7 +521,7 @@ where
|
||||
}
|
||||
};
|
||||
let mut timed_socket = TimeoutStream::new(socket);
|
||||
timed_socket.set_read_timeout(Some(Duration::from_secs(TTFB_TIMEOUT)));
|
||||
timed_socket.set_read_timeout(Some(self.ttfb_timeout));
|
||||
|
||||
let socket = Framed::new(Box::pin(timed_socket), codec);
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ impl<Id: ReqId, TSpec: EthSpec> SelfRateLimiter<Id, TSpec> {
|
||||
}
|
||||
|
||||
/// Checks if the rate limiter allows the request. If it's allowed, returns the
|
||||
/// [`NetworkBehaviourAction`] that should be emitted. When not allowed, the request is delayed
|
||||
/// [`ToSwarm`] that should be emitted. When not allowed, the request is delayed
|
||||
/// until it can be sent.
|
||||
pub fn allows(
|
||||
&mut self,
|
||||
@@ -95,7 +95,7 @@ impl<Id: ReqId, TSpec: EthSpec> SelfRateLimiter<Id, TSpec> {
|
||||
}
|
||||
|
||||
/// Auxiliary function to deal with self rate limiting outcomes. If the rate limiter allows the
|
||||
/// request, the [`NetworkBehaviourAction`] that should be emitted is returned. If the request
|
||||
/// request, the [`ToSwarm`] that should be emitted is returned. If the request
|
||||
/// should be delayed, it's returned with the duration to wait.
|
||||
fn try_send_request(
|
||||
limiter: &mut RateLimiter,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use libp2p::core::connection::ConnectionId;
|
||||
use libp2p::swarm::ConnectionId;
|
||||
use types::light_client_bootstrap::LightClientBootstrap;
|
||||
use types::{BlobSidecar, EthSpec, SignedBeaconBlock};
|
||||
|
||||
|
||||
@@ -3,21 +3,27 @@ use crate::peer_manager::PeerManager;
|
||||
use crate::rpc::{ReqId, RPC};
|
||||
use crate::types::SnappyTransform;
|
||||
|
||||
use libp2p::gossipsub::subscription_filter::{
|
||||
MaxCountSubscriptionFilter, WhitelistSubscriptionFilter,
|
||||
};
|
||||
use libp2p::gossipsub::Gossipsub as BaseGossipsub;
|
||||
use libp2p::identify::Behaviour as Identify;
|
||||
use libp2p::gossipsub;
|
||||
use libp2p::identify;
|
||||
use libp2p::swarm::NetworkBehaviour;
|
||||
use types::EthSpec;
|
||||
|
||||
use super::api_types::RequestId;
|
||||
|
||||
pub type SubscriptionFilter = MaxCountSubscriptionFilter<WhitelistSubscriptionFilter>;
|
||||
pub type Gossipsub = BaseGossipsub<SnappyTransform, SubscriptionFilter>;
|
||||
pub type SubscriptionFilter =
|
||||
gossipsub::MaxCountSubscriptionFilter<gossipsub::WhitelistSubscriptionFilter>;
|
||||
pub type Gossipsub = gossipsub::Behaviour<SnappyTransform, SubscriptionFilter>;
|
||||
|
||||
#[derive(NetworkBehaviour)]
|
||||
pub(crate) struct Behaviour<AppReqId: ReqId, TSpec: EthSpec> {
|
||||
pub(crate) struct Behaviour<AppReqId, TSpec>
|
||||
where
|
||||
AppReqId: ReqId,
|
||||
TSpec: EthSpec,
|
||||
{
|
||||
/// Peers banned.
|
||||
pub banned_peers: libp2p::allow_block_list::Behaviour<libp2p::allow_block_list::BlockedPeers>,
|
||||
/// Keep track of active and pending connections to enforce hard limits.
|
||||
pub connection_limits: libp2p::connection_limits::Behaviour,
|
||||
/// The routing pub-sub mechanism for eth2.
|
||||
pub gossipsub: Gossipsub,
|
||||
/// The Eth2 RPC specified in the wire-0 protocol.
|
||||
@@ -27,7 +33,7 @@ pub(crate) struct Behaviour<AppReqId: ReqId, TSpec: EthSpec> {
|
||||
/// Keep regular connection to peers and disconnect if absent.
|
||||
// NOTE: The id protocol is used for initial interop. This will be removed by mainnet.
|
||||
/// Provides IP addresses and peer information.
|
||||
pub identify: Identify,
|
||||
pub identify: identify::Behaviour,
|
||||
/// The peer manager that keeps track of peer's reputation and status.
|
||||
pub peer_manager: PeerManager<TSpec>,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::types::{GossipEncoding, GossipKind, GossipTopic};
|
||||
use crate::{error, TopicHash};
|
||||
use libp2p::gossipsub::{
|
||||
GossipsubConfig, IdentTopic as Topic, PeerScoreParams, PeerScoreThresholds, TopicScoreParams,
|
||||
Config as GossipsubConfig, IdentTopic as Topic, PeerScoreParams, PeerScoreThresholds,
|
||||
TopicScoreParams,
|
||||
};
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use self::behaviour::Behaviour;
|
||||
use self::gossip_cache::GossipCache;
|
||||
use crate::config::{gossipsub_config, NetworkLoad};
|
||||
use crate::config::{gossipsub_config, GossipsubConfigParams, NetworkLoad};
|
||||
use crate::discovery::{
|
||||
subnet_predicate, DiscoveredPeers, Discovery, FIND_NODE_QUERY_CLOSEST_PEERS,
|
||||
};
|
||||
@@ -24,15 +24,12 @@ use api_types::{PeerRequestId, Request, RequestId, Response};
|
||||
use futures::stream::StreamExt;
|
||||
use gossipsub_scoring_parameters::{lighthouse_gossip_thresholds, PeerScoreSettings};
|
||||
use libp2p::bandwidth::BandwidthSinks;
|
||||
use libp2p::gossipsub::error::PublishError;
|
||||
use libp2p::gossipsub::metrics::Config as GossipsubMetricsConfig;
|
||||
use libp2p::gossipsub::subscription_filter::MaxCountSubscriptionFilter;
|
||||
use libp2p::gossipsub::{
|
||||
GossipsubEvent, IdentTopic as Topic, MessageAcceptance, MessageAuthenticity, MessageId,
|
||||
self, IdentTopic as Topic, MessageAcceptance, MessageAuthenticity, MessageId, PublishError,
|
||||
};
|
||||
use libp2p::identify::{Behaviour as Identify, Config as IdentifyConfig, Event as IdentifyEvent};
|
||||
use libp2p::identify;
|
||||
use libp2p::multiaddr::{Multiaddr, Protocol as MProtocol};
|
||||
use libp2p::swarm::{ConnectionLimits, Swarm, SwarmBuilder, SwarmEvent};
|
||||
use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent};
|
||||
use libp2p::PeerId;
|
||||
use slog::{crit, debug, info, o, trace, warn};
|
||||
use std::path::PathBuf;
|
||||
@@ -67,10 +64,6 @@ pub enum NetworkEvent<AppReqId: ReqId, TSpec: EthSpec> {
|
||||
PeerConnectedIncoming(PeerId),
|
||||
/// A peer has disconnected.
|
||||
PeerDisconnected(PeerId),
|
||||
/// The peer needs to be banned.
|
||||
PeerBanned(PeerId),
|
||||
/// The peer has been unbanned.
|
||||
PeerUnbanned(PeerId),
|
||||
/// An RPC Request that was sent failed.
|
||||
RPCFailed {
|
||||
/// The id of the failed request.
|
||||
@@ -232,7 +225,7 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
let update_gossipsub_scores = tokio::time::interval(params.decay_interval);
|
||||
|
||||
let possible_fork_digests = ctx.fork_context.all_fork_digests();
|
||||
let filter = MaxCountSubscriptionFilter {
|
||||
let filter = gossipsub::MaxCountSubscriptionFilter {
|
||||
filter: utils::create_whitelist_filter(
|
||||
possible_fork_digests,
|
||||
ctx.chain_spec.attestation_subnet_count,
|
||||
@@ -244,12 +237,20 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
max_subscriptions_per_request: 160,
|
||||
};
|
||||
|
||||
config.gs_config = gossipsub_config(config.network_load, ctx.fork_context.clone());
|
||||
let gossipsub_config_params = GossipsubConfigParams {
|
||||
message_domain_valid_snappy: ctx.chain_spec.message_domain_valid_snappy,
|
||||
gossip_max_size: ctx.chain_spec.gossip_max_size as usize,
|
||||
};
|
||||
config.gs_config = gossipsub_config(
|
||||
config.network_load,
|
||||
ctx.fork_context.clone(),
|
||||
gossipsub_config_params,
|
||||
);
|
||||
|
||||
// If metrics are enabled for gossipsub build the configuration
|
||||
let gossipsub_metrics = ctx
|
||||
.gossipsub_registry
|
||||
.map(|registry| (registry, GossipsubMetricsConfig::default()));
|
||||
.map(|registry| (registry, Default::default()));
|
||||
|
||||
let snappy_transform = SnappyTransform::new(config.gs_config.max_transmit_size());
|
||||
let mut gossipsub = Gossipsub::new_with_subscription_filter_and_transform(
|
||||
@@ -268,36 +269,48 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
(gossipsub, update_gossipsub_scores)
|
||||
};
|
||||
|
||||
let network_params = NetworkParams {
|
||||
max_chunk_size: ctx.chain_spec.max_chunk_size as usize,
|
||||
ttfb_timeout: ctx.chain_spec.ttfb_timeout(),
|
||||
resp_timeout: ctx.chain_spec.resp_timeout(),
|
||||
};
|
||||
let eth2_rpc = RPC::new(
|
||||
ctx.fork_context.clone(),
|
||||
config.enable_light_client_server,
|
||||
config.inbound_rate_limiter_config.clone(),
|
||||
config.outbound_rate_limiter_config.clone(),
|
||||
log.clone(),
|
||||
network_params,
|
||||
);
|
||||
|
||||
let discovery = {
|
||||
// Build and start the discovery sub-behaviour
|
||||
let mut discovery =
|
||||
Discovery::new(&local_keypair, &config, network_globals.clone(), &log).await?;
|
||||
let mut discovery = Discovery::new(
|
||||
local_keypair.clone(),
|
||||
&config,
|
||||
network_globals.clone(),
|
||||
&log,
|
||||
)
|
||||
.await?;
|
||||
// start searching for peers
|
||||
discovery.discover_peers(FIND_NODE_QUERY_CLOSEST_PEERS);
|
||||
discovery
|
||||
};
|
||||
|
||||
let identify = {
|
||||
let local_public_key = local_keypair.public();
|
||||
let identify_config = if config.private {
|
||||
IdentifyConfig::new(
|
||||
identify::Config::new(
|
||||
"".into(),
|
||||
local_keypair.public(), // Still send legitimate public key
|
||||
local_public_key, // Still send legitimate public key
|
||||
)
|
||||
.with_cache_size(0)
|
||||
} else {
|
||||
IdentifyConfig::new("eth2/1.0.0".into(), local_keypair.public())
|
||||
identify::Config::new("eth2/1.0.0".into(), local_public_key)
|
||||
.with_agent_version(lighthouse_version::version_with_platform())
|
||||
.with_cache_size(0)
|
||||
};
|
||||
Identify::new(identify_config)
|
||||
identify::Behaviour::new(identify_config)
|
||||
};
|
||||
|
||||
let peer_manager = {
|
||||
@@ -310,13 +323,38 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
PeerManager::new(peer_manager_cfg, network_globals.clone(), &log)?
|
||||
};
|
||||
|
||||
let connection_limits = {
|
||||
let limits = libp2p::connection_limits::ConnectionLimits::default()
|
||||
.with_max_pending_incoming(Some(5))
|
||||
.with_max_pending_outgoing(Some(16))
|
||||
.with_max_established_incoming(Some(
|
||||
(config.target_peers as f32
|
||||
* (1.0 + PEER_EXCESS_FACTOR - MIN_OUTBOUND_ONLY_FACTOR))
|
||||
.ceil() as u32,
|
||||
))
|
||||
.with_max_established_outgoing(Some(
|
||||
(config.target_peers as f32 * (1.0 + PEER_EXCESS_FACTOR)).ceil() as u32,
|
||||
))
|
||||
.with_max_established(Some(
|
||||
(config.target_peers as f32 * (1.0 + PEER_EXCESS_FACTOR + PRIORITY_PEER_EXCESS))
|
||||
.ceil() as u32,
|
||||
))
|
||||
.with_max_established_per_peer(Some(MAX_CONNECTIONS_PER_PEER));
|
||||
|
||||
libp2p::connection_limits::Behaviour::new(limits)
|
||||
};
|
||||
|
||||
let banned_peers = libp2p::allow_block_list::Behaviour::default();
|
||||
|
||||
let behaviour = {
|
||||
Behaviour {
|
||||
banned_peers,
|
||||
gossipsub,
|
||||
eth2_rpc,
|
||||
discovery,
|
||||
identify,
|
||||
peer_manager,
|
||||
connection_limits,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -334,22 +372,6 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
}
|
||||
|
||||
// sets up the libp2p connection limits
|
||||
let limits = ConnectionLimits::default()
|
||||
.with_max_pending_incoming(Some(5))
|
||||
.with_max_pending_outgoing(Some(16))
|
||||
.with_max_established_incoming(Some(
|
||||
(config.target_peers as f32
|
||||
* (1.0 + PEER_EXCESS_FACTOR - MIN_OUTBOUND_ONLY_FACTOR))
|
||||
.ceil() as u32,
|
||||
))
|
||||
.with_max_established_outgoing(Some(
|
||||
(config.target_peers as f32 * (1.0 + PEER_EXCESS_FACTOR)).ceil() as u32,
|
||||
))
|
||||
.with_max_established(Some(
|
||||
(config.target_peers as f32 * (1.0 + PEER_EXCESS_FACTOR + PRIORITY_PEER_EXCESS))
|
||||
.ceil() as u32,
|
||||
))
|
||||
.with_max_established_per_peer(Some(MAX_CONNECTIONS_PER_PEER));
|
||||
|
||||
(
|
||||
SwarmBuilder::with_executor(
|
||||
@@ -359,8 +381,7 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
Executor(executor),
|
||||
)
|
||||
.notify_handler_buffer_size(std::num::NonZeroUsize::new(7).expect("Not zero"))
|
||||
.connection_event_buffer_size(64)
|
||||
.connection_limits(limits)
|
||||
.per_connection_event_buffer_size(4)
|
||||
.build(),
|
||||
bandwidth,
|
||||
)
|
||||
@@ -401,7 +422,7 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
match self.swarm.listen_on(listen_multiaddr.clone()) {
|
||||
Ok(_) => {
|
||||
let mut log_address = listen_multiaddr;
|
||||
log_address.push(MProtocol::P2p(enr.peer_id().into()));
|
||||
log_address.push(MProtocol::P2p(enr.peer_id()));
|
||||
info!(self.log, "Listening established"; "address" => %log_address);
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -498,7 +519,7 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
&mut self.swarm.behaviour_mut().discovery
|
||||
}
|
||||
/// Provides IP addresses and peer information.
|
||||
pub fn identify_mut(&mut self) -> &mut Identify {
|
||||
pub fn identify_mut(&mut self) -> &mut identify::Behaviour {
|
||||
&mut self.swarm.behaviour_mut().identify
|
||||
}
|
||||
/// The peer manager that keeps track of peer's reputation and status.
|
||||
@@ -519,7 +540,7 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
&self.swarm.behaviour().discovery
|
||||
}
|
||||
/// Provides IP addresses and peer information.
|
||||
pub fn identify(&self) -> &Identify {
|
||||
pub fn identify(&self) -> &identify::Behaviour {
|
||||
&self.swarm.behaviour().identify
|
||||
}
|
||||
/// The peer manager that keeps track of peer's reputation and status.
|
||||
@@ -1056,9 +1077,12 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
/* Sub-behaviour event handling functions */
|
||||
|
||||
/// Handle a gossipsub event.
|
||||
fn inject_gs_event(&mut self, event: GossipsubEvent) -> Option<NetworkEvent<AppReqId, TSpec>> {
|
||||
fn inject_gs_event(
|
||||
&mut self,
|
||||
event: gossipsub::Event,
|
||||
) -> Option<NetworkEvent<AppReqId, TSpec>> {
|
||||
match event {
|
||||
GossipsubEvent::Message {
|
||||
gossipsub::Event::Message {
|
||||
propagation_source,
|
||||
message_id: id,
|
||||
message: gs_msg,
|
||||
@@ -1088,7 +1112,7 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
}
|
||||
}
|
||||
}
|
||||
GossipsubEvent::Subscribed { peer_id, topic } => {
|
||||
gossipsub::Event::Subscribed { peer_id, topic } => {
|
||||
if let Ok(topic) = GossipTopic::decode(topic.as_str()) {
|
||||
if let Some(subnet_id) = topic.subnet_id() {
|
||||
self.network_globals
|
||||
@@ -1129,7 +1153,7 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
}
|
||||
}
|
||||
}
|
||||
GossipsubEvent::Unsubscribed { peer_id, topic } => {
|
||||
gossipsub::Event::Unsubscribed { peer_id, topic } => {
|
||||
if let Some(subnet_id) = subnet_from_topic_hash(&topic) {
|
||||
self.network_globals
|
||||
.peers
|
||||
@@ -1137,7 +1161,7 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
.remove_subscription(&peer_id, &subnet_id);
|
||||
}
|
||||
}
|
||||
GossipsubEvent::GossipsubNotSupported { peer_id } => {
|
||||
gossipsub::Event::GossipsubNotSupported { peer_id } => {
|
||||
debug!(self.log, "Peer does not support gossipsub"; "peer_id" => %peer_id);
|
||||
self.peer_manager_mut().report_peer(
|
||||
&peer_id,
|
||||
@@ -1372,10 +1396,10 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
/// Handle an identify event.
|
||||
fn inject_identify_event(
|
||||
&mut self,
|
||||
event: IdentifyEvent,
|
||||
event: identify::Event,
|
||||
) -> Option<NetworkEvent<AppReqId, TSpec>> {
|
||||
match event {
|
||||
IdentifyEvent::Received { peer_id, mut info } => {
|
||||
identify::Event::Received { peer_id, mut info } => {
|
||||
if info.listen_addrs.len() > MAX_IDENTIFY_ADDRESSES {
|
||||
debug!(
|
||||
self.log,
|
||||
@@ -1386,9 +1410,9 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
// send peer info to the peer manager.
|
||||
self.peer_manager_mut().identify(&peer_id, &info);
|
||||
}
|
||||
IdentifyEvent::Sent { .. } => {}
|
||||
IdentifyEvent::Error { .. } => {}
|
||||
IdentifyEvent::Pushed { .. } => {}
|
||||
identify::Event::Sent { .. } => {}
|
||||
identify::Event::Error { .. } => {}
|
||||
identify::Event::Pushed { .. } => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -1409,14 +1433,17 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
Some(NetworkEvent::PeerDisconnected(peer_id))
|
||||
}
|
||||
PeerManagerEvent::Banned(peer_id, associated_ips) => {
|
||||
self.swarm.ban_peer_id(peer_id);
|
||||
self.swarm.behaviour_mut().banned_peers.block_peer(peer_id);
|
||||
self.discovery_mut().ban_peer(&peer_id, associated_ips);
|
||||
Some(NetworkEvent::PeerBanned(peer_id))
|
||||
None
|
||||
}
|
||||
PeerManagerEvent::UnBanned(peer_id, associated_ips) => {
|
||||
self.swarm.unban_peer_id(peer_id);
|
||||
self.swarm
|
||||
.behaviour_mut()
|
||||
.banned_peers
|
||||
.unblock_peer(peer_id);
|
||||
self.discovery_mut().unban_peer(&peer_id, associated_ips);
|
||||
Some(NetworkEvent::PeerUnbanned(peer_id))
|
||||
None
|
||||
}
|
||||
PeerManagerEvent::Status(peer_id) => {
|
||||
// it's time to status. We don't keep a beacon chain reference here, so we inform
|
||||
@@ -1463,17 +1490,20 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
let maybe_event = match swarm_event {
|
||||
SwarmEvent::Behaviour(behaviour_event) => match behaviour_event {
|
||||
// Handle sub-behaviour events.
|
||||
BehaviourEvent::BannedPeers(void) => void::unreachable(void),
|
||||
BehaviourEvent::Gossipsub(ge) => self.inject_gs_event(ge),
|
||||
BehaviourEvent::Eth2Rpc(re) => self.inject_rpc_event(re),
|
||||
BehaviourEvent::Discovery(de) => self.inject_discovery_event(de),
|
||||
BehaviourEvent::Identify(ie) => self.inject_identify_event(ie),
|
||||
BehaviourEvent::PeerManager(pe) => self.inject_pm_event(pe),
|
||||
BehaviourEvent::ConnectionLimits(le) => void::unreachable(le),
|
||||
},
|
||||
SwarmEvent::ConnectionEstablished { .. } => None,
|
||||
SwarmEvent::ConnectionClosed { .. } => None,
|
||||
SwarmEvent::IncomingConnection {
|
||||
local_addr,
|
||||
send_back_addr,
|
||||
connection_id: _,
|
||||
} => {
|
||||
trace!(self.log, "Incoming connection"; "our_addr" => %local_addr, "from" => %send_back_addr);
|
||||
None
|
||||
@@ -1482,19 +1512,41 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
local_addr,
|
||||
send_back_addr,
|
||||
error,
|
||||
connection_id: _,
|
||||
} => {
|
||||
debug!(self.log, "Failed incoming connection"; "our_addr" => %local_addr, "from" => %send_back_addr, "error" => %error);
|
||||
let error_repr = match error {
|
||||
libp2p::swarm::ListenError::Aborted => {
|
||||
"Incoming connection aborted".to_string()
|
||||
}
|
||||
libp2p::swarm::ListenError::WrongPeerId { obtained, endpoint } => {
|
||||
format!("Wrong peer id, obtained {obtained}, endpoint {endpoint:?}")
|
||||
}
|
||||
libp2p::swarm::ListenError::LocalPeerId { endpoint } => {
|
||||
format!("Dialing local peer id {endpoint:?}")
|
||||
}
|
||||
libp2p::swarm::ListenError::Denied { cause } => {
|
||||
format!("Connection was denied with cause {cause}")
|
||||
}
|
||||
libp2p::swarm::ListenError::Transport(t) => match t {
|
||||
libp2p::TransportError::MultiaddrNotSupported(m) => {
|
||||
format!("Transport error: Multiaddr not supported: {m}")
|
||||
}
|
||||
libp2p::TransportError::Other(e) => {
|
||||
format!("Transport error: other: {e}")
|
||||
}
|
||||
},
|
||||
};
|
||||
debug!(self.log, "Failed incoming connection"; "our_addr" => %local_addr, "from" => %send_back_addr, "error" => error_repr);
|
||||
None
|
||||
}
|
||||
SwarmEvent::OutgoingConnectionError { peer_id, error } => {
|
||||
debug!(self.log, "Failed to dial address"; "peer_id" => ?peer_id, "error" => %error);
|
||||
None
|
||||
}
|
||||
SwarmEvent::BannedPeer {
|
||||
peer_id,
|
||||
endpoint: _,
|
||||
SwarmEvent::OutgoingConnectionError {
|
||||
peer_id: _,
|
||||
error: _,
|
||||
connection_id: _,
|
||||
} => {
|
||||
debug!(self.log, "Banned peer connection rejected"; "peer_id" => %peer_id);
|
||||
// The Behaviour event is more general than the swarm event here. It includes
|
||||
// connection failures. So we use that log for now, in the peer manager
|
||||
// behaviour implementation.
|
||||
None
|
||||
}
|
||||
SwarmEvent::NewListenAddr { address, .. } => {
|
||||
@@ -1523,7 +1575,13 @@ impl<AppReqId: ReqId, TSpec: EthSpec> Network<AppReqId, TSpec> {
|
||||
None
|
||||
}
|
||||
}
|
||||
SwarmEvent::Dialing(_) => None,
|
||||
SwarmEvent::Dialing {
|
||||
peer_id,
|
||||
connection_id: _,
|
||||
} => {
|
||||
debug!(self.log, "Swarm Dialing"; "peer_id" => ?peer_id);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ev) = maybe_event {
|
||||
|
||||
@@ -4,13 +4,11 @@ use crate::types::{
|
||||
error, EnrAttestationBitfield, EnrSyncCommitteeBitfield, GossipEncoding, GossipKind,
|
||||
};
|
||||
use crate::{GossipTopic, NetworkConfig};
|
||||
use libp2p::bandwidth::{BandwidthLogging, BandwidthSinks};
|
||||
use libp2p::core::{
|
||||
identity::Keypair, multiaddr::Multiaddr, muxing::StreamMuxerBox, transport::Boxed,
|
||||
};
|
||||
use libp2p::gossipsub::subscription_filter::WhitelistSubscriptionFilter;
|
||||
use libp2p::gossipsub::IdentTopic as Topic;
|
||||
use libp2p::{core, noise, PeerId, Transport};
|
||||
use libp2p::bandwidth::BandwidthSinks;
|
||||
use libp2p::core::{multiaddr::Multiaddr, muxing::StreamMuxerBox, transport::Boxed};
|
||||
use libp2p::gossipsub;
|
||||
use libp2p::identity::{secp256k1, Keypair};
|
||||
use libp2p::{core, noise, yamux, PeerId, Transport, TransportExt};
|
||||
use prometheus_client::registry::Registry;
|
||||
use slog::{debug, warn};
|
||||
use ssz::Decode;
|
||||
@@ -52,30 +50,19 @@ pub fn build_transport(
|
||||
transport.or_transport(libp2p::websocket::WsConfig::new(trans_clone))
|
||||
};
|
||||
|
||||
let (transport, bandwidth) = BandwidthLogging::new(transport);
|
||||
|
||||
// mplex config
|
||||
let mut mplex_config = libp2p::mplex::MplexConfig::new();
|
||||
mplex_config.set_max_buffer_size(256);
|
||||
mplex_config.set_max_buffer_behaviour(libp2p::mplex::MaxBufferBehaviour::Block);
|
||||
|
||||
// yamux config
|
||||
let mut yamux_config = libp2p::yamux::YamuxConfig::default();
|
||||
yamux_config.set_window_update_mode(libp2p::yamux::WindowUpdateMode::on_read());
|
||||
let mut yamux_config = yamux::Config::default();
|
||||
yamux_config.set_window_update_mode(yamux::WindowUpdateMode::on_read());
|
||||
let (transport, bandwidth) = transport
|
||||
.upgrade(core::upgrade::Version::V1)
|
||||
.authenticate(generate_noise_config(&local_private_key))
|
||||
.multiplex(yamux_config)
|
||||
.timeout(Duration::from_secs(10))
|
||||
.boxed()
|
||||
.with_bandwidth_logging();
|
||||
|
||||
// Authentication
|
||||
Ok((
|
||||
transport
|
||||
.upgrade(core::upgrade::Version::V1)
|
||||
.authenticate(generate_noise_config(&local_private_key))
|
||||
.multiplex(core::upgrade::SelectUpgrade::new(
|
||||
yamux_config,
|
||||
mplex_config,
|
||||
))
|
||||
.timeout(Duration::from_secs(10))
|
||||
.boxed(),
|
||||
bandwidth,
|
||||
))
|
||||
Ok((transport, bandwidth))
|
||||
}
|
||||
|
||||
// Useful helper functions for debugging. Currently not used in the client.
|
||||
@@ -94,10 +81,10 @@ fn keypair_from_hex(hex_bytes: &str) -> error::Result<Keypair> {
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn keypair_from_bytes(mut bytes: Vec<u8>) -> error::Result<Keypair> {
|
||||
libp2p::core::identity::secp256k1::SecretKey::from_bytes(&mut bytes)
|
||||
secp256k1::SecretKey::try_from_bytes(&mut bytes)
|
||||
.map(|secret| {
|
||||
let keypair: libp2p::core::identity::secp256k1::Keypair = secret.into();
|
||||
Keypair::Secp256k1(keypair)
|
||||
let keypair: secp256k1::Keypair = secret.into();
|
||||
keypair.into()
|
||||
})
|
||||
.map_err(|e| format!("Unable to parse p2p secret key: {:?}", e).into())
|
||||
}
|
||||
@@ -115,12 +102,10 @@ pub fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair {
|
||||
Err(_) => debug!(log, "Could not read network key file"),
|
||||
Ok(_) => {
|
||||
// only accept secp256k1 keys for now
|
||||
if let Ok(secret_key) =
|
||||
libp2p::core::identity::secp256k1::SecretKey::from_bytes(&mut key_bytes)
|
||||
{
|
||||
let kp: libp2p::core::identity::secp256k1::Keypair = secret_key.into();
|
||||
if let Ok(secret_key) = secp256k1::SecretKey::try_from_bytes(&mut key_bytes) {
|
||||
let kp: secp256k1::Keypair = secret_key.into();
|
||||
debug!(log, "Loaded network key from disk.");
|
||||
return Keypair::Secp256k1(kp);
|
||||
return kp.into();
|
||||
} else {
|
||||
debug!(log, "Network key file is not a valid secp256k1 key");
|
||||
}
|
||||
@@ -129,34 +114,27 @@ pub fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair {
|
||||
}
|
||||
|
||||
// if a key could not be loaded from disk, generate a new one and save it
|
||||
let local_private_key = Keypair::generate_secp256k1();
|
||||
if let Keypair::Secp256k1(key) = local_private_key.clone() {
|
||||
let _ = std::fs::create_dir_all(&config.network_dir);
|
||||
match File::create(network_key_f.clone())
|
||||
.and_then(|mut f| f.write_all(&key.secret().to_bytes()))
|
||||
{
|
||||
Ok(_) => {
|
||||
debug!(log, "New network key generated and written to disk");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
log,
|
||||
"Could not write node key to file: {:?}. error: {}", network_key_f, e
|
||||
);
|
||||
}
|
||||
let local_private_key = secp256k1::Keypair::generate();
|
||||
let _ = std::fs::create_dir_all(&config.network_dir);
|
||||
match File::create(network_key_f.clone())
|
||||
.and_then(|mut f| f.write_all(&local_private_key.secret().to_bytes()))
|
||||
{
|
||||
Ok(_) => {
|
||||
debug!(log, "New network key generated and written to disk");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
log,
|
||||
"Could not write node key to file: {:?}. error: {}", network_key_f, e
|
||||
);
|
||||
}
|
||||
}
|
||||
local_private_key
|
||||
local_private_key.into()
|
||||
}
|
||||
|
||||
/// Generate authenticated XX Noise config from identity keys
|
||||
fn generate_noise_config(
|
||||
identity_keypair: &Keypair,
|
||||
) -> noise::NoiseAuthenticated<noise::XX, noise::X25519Spec, ()> {
|
||||
let static_dh_keys = noise::Keypair::<noise::X25519Spec>::new()
|
||||
.into_authentic(identity_keypair)
|
||||
.expect("signing can fail only once during starting a node");
|
||||
noise::NoiseConfig::xx(static_dh_keys).into_authenticated()
|
||||
fn generate_noise_config(identity_keypair: &Keypair) -> noise::Config {
|
||||
noise::Config::new(identity_keypair).expect("signing can fail only once during starting a node")
|
||||
}
|
||||
|
||||
/// For a multiaddr that ends with a peer id, this strips this suffix. Rust-libp2p
|
||||
@@ -237,11 +215,11 @@ pub(crate) fn create_whitelist_filter(
|
||||
attestation_subnet_count: u64,
|
||||
sync_committee_subnet_count: u64,
|
||||
blob_sidecar_subnet_count: u64,
|
||||
) -> WhitelistSubscriptionFilter {
|
||||
) -> gossipsub::WhitelistSubscriptionFilter {
|
||||
let mut possible_hashes = HashSet::new();
|
||||
for fork_digest in possible_fork_digests {
|
||||
let mut add = |kind| {
|
||||
let topic: Topic =
|
||||
let topic: gossipsub::IdentTopic =
|
||||
GossipTopic::new(kind, GossipEncoding::SSZSnappy, fork_digest).into();
|
||||
possible_hashes.insert(topic.hash());
|
||||
};
|
||||
@@ -266,7 +244,7 @@ pub(crate) fn create_whitelist_filter(
|
||||
add(BlobSidecar(id));
|
||||
}
|
||||
}
|
||||
WhitelistSubscriptionFilter(possible_hashes)
|
||||
gossipsub::WhitelistSubscriptionFilter(possible_hashes)
|
||||
}
|
||||
|
||||
/// Persist metadata to disk
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::types::{GossipEncoding, GossipKind, GossipTopic};
|
||||
use crate::TopicHash;
|
||||
use libp2p::gossipsub::{DataTransform, GossipsubMessage, RawGossipsubMessage};
|
||||
use libp2p::gossipsub;
|
||||
use snap::raw::{decompress_len, Decoder, Encoder};
|
||||
use ssz::{Decode, Encode};
|
||||
use std::boxed::Box;
|
||||
@@ -58,12 +58,12 @@ impl SnappyTransform {
|
||||
}
|
||||
}
|
||||
|
||||
impl DataTransform for SnappyTransform {
|
||||
impl gossipsub::DataTransform for SnappyTransform {
|
||||
// Provides the snappy decompression from RawGossipsubMessages
|
||||
fn inbound_transform(
|
||||
&self,
|
||||
raw_message: RawGossipsubMessage,
|
||||
) -> Result<GossipsubMessage, std::io::Error> {
|
||||
raw_message: gossipsub::RawMessage,
|
||||
) -> Result<gossipsub::Message, std::io::Error> {
|
||||
// check the length of the raw bytes
|
||||
let len = decompress_len(&raw_message.data)?;
|
||||
if len > self.max_size_per_message {
|
||||
@@ -77,7 +77,7 @@ impl DataTransform for SnappyTransform {
|
||||
let decompressed_data = decoder.decompress_vec(&raw_message.data)?;
|
||||
|
||||
// Build the GossipsubMessage struct
|
||||
Ok(GossipsubMessage {
|
||||
Ok(gossipsub::Message {
|
||||
source: raw_message.source,
|
||||
data: decompressed_data,
|
||||
sequence_number: raw_message.sequence_number,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![cfg(test)]
|
||||
use libp2p::gossipsub::GossipsubConfigBuilder;
|
||||
use libp2p::gossipsub;
|
||||
use lighthouse_network::service::Network as LibP2PService;
|
||||
use lighthouse_network::Enr;
|
||||
use lighthouse_network::EnrExt;
|
||||
@@ -84,7 +84,7 @@ pub fn build_config(port: u16, mut boot_nodes: Vec<Enr>) -> NetworkConfig {
|
||||
config.boot_nodes_enr.append(&mut boot_nodes);
|
||||
config.network_dir = path.into_path();
|
||||
// Reduce gossipsub heartbeat parameters
|
||||
config.gs_config = GossipsubConfigBuilder::from(config.gs_config)
|
||||
config.gs_config = gossipsub::ConfigBuilder::from(config.gs_config)
|
||||
.heartbeat_initial_delay(Duration::from_millis(500))
|
||||
.heartbeat_interval(Duration::from_millis(500))
|
||||
.build()
|
||||
@@ -97,6 +97,7 @@ pub async fn build_libp2p_instance(
|
||||
boot_nodes: Vec<Enr>,
|
||||
log: slog::Logger,
|
||||
fork_name: ForkName,
|
||||
spec: &ChainSpec,
|
||||
) -> Libp2pInstance {
|
||||
let port = unused_tcp4_port().unwrap();
|
||||
let config = build_config(port, boot_nodes);
|
||||
@@ -109,7 +110,7 @@ pub async fn build_libp2p_instance(
|
||||
config: &config,
|
||||
enr_fork_id: EnrForkId::default(),
|
||||
fork_context: Arc::new(fork_context(fork_name)),
|
||||
chain_spec: &ChainSpec::minimal(),
|
||||
chain_spec: spec,
|
||||
gossipsub_registry: None,
|
||||
};
|
||||
Libp2pInstance(
|
||||
@@ -133,12 +134,13 @@ pub async fn build_node_pair(
|
||||
rt: Weak<Runtime>,
|
||||
log: &slog::Logger,
|
||||
fork_name: ForkName,
|
||||
spec: &ChainSpec,
|
||||
) -> (Libp2pInstance, Libp2pInstance) {
|
||||
let sender_log = log.new(o!("who" => "sender"));
|
||||
let receiver_log = log.new(o!("who" => "receiver"));
|
||||
|
||||
let mut sender = build_libp2p_instance(rt.clone(), vec![], sender_log, fork_name).await;
|
||||
let mut receiver = build_libp2p_instance(rt, vec![], receiver_log, fork_name).await;
|
||||
let mut sender = build_libp2p_instance(rt.clone(), vec![], sender_log, fork_name, spec).await;
|
||||
let mut receiver = build_libp2p_instance(rt, vec![], receiver_log, fork_name, spec).await;
|
||||
|
||||
let receiver_multiaddr = receiver.local_enr().multiaddr()[1].clone();
|
||||
|
||||
@@ -183,10 +185,11 @@ pub async fn build_linear(
|
||||
log: slog::Logger,
|
||||
n: usize,
|
||||
fork_name: ForkName,
|
||||
spec: &ChainSpec,
|
||||
) -> Vec<Libp2pInstance> {
|
||||
let mut nodes = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
nodes.push(build_libp2p_instance(rt.clone(), vec![], log.clone(), fork_name).await);
|
||||
nodes.push(build_libp2p_instance(rt.clone(), vec![], log.clone(), fork_name, spec).await);
|
||||
}
|
||||
|
||||
let multiaddrs: Vec<Multiaddr> = nodes
|
||||
|
||||
@@ -9,9 +9,9 @@ use std::time::Duration;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::time::sleep;
|
||||
use types::{
|
||||
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockMerge, BlobSidecar, EmptyBlock,
|
||||
Epoch, EthSpec, ForkContext, ForkName, Hash256, MinimalEthSpec, Signature, SignedBeaconBlock,
|
||||
Slot,
|
||||
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockMerge, BlobSidecar, ChainSpec,
|
||||
EmptyBlock, Epoch, EthSpec, ForkContext, ForkName, Hash256, MinimalEthSpec, Signature,
|
||||
SignedBeaconBlock, Slot,
|
||||
};
|
||||
|
||||
mod common;
|
||||
@@ -19,30 +19,30 @@ mod common;
|
||||
type E = MinimalEthSpec;
|
||||
|
||||
/// Merge block with length < max_rpc_size.
|
||||
fn merge_block_small(fork_context: &ForkContext) -> BeaconBlock<E> {
|
||||
let mut block = BeaconBlockMerge::<E>::empty(&E::default_spec());
|
||||
fn merge_block_small(fork_context: &ForkContext, spec: &ChainSpec) -> BeaconBlock<E> {
|
||||
let mut block = BeaconBlockMerge::<E>::empty(spec);
|
||||
let tx = VariableList::from(vec![0; 1024]);
|
||||
let txs = VariableList::from(std::iter::repeat(tx).take(5000).collect::<Vec<_>>());
|
||||
|
||||
block.body.execution_payload.execution_payload.transactions = txs;
|
||||
|
||||
let block = BeaconBlock::Merge(block);
|
||||
assert!(block.ssz_bytes_len() <= max_rpc_size(fork_context));
|
||||
assert!(block.ssz_bytes_len() <= max_rpc_size(fork_context, spec.max_chunk_size as usize));
|
||||
block
|
||||
}
|
||||
|
||||
/// Merge block with length > MAX_RPC_SIZE.
|
||||
/// The max limit for a merge block is in the order of ~16GiB which wouldn't fit in memory.
|
||||
/// Hence, we generate a merge block just greater than `MAX_RPC_SIZE` to test rejection on the rpc layer.
|
||||
fn merge_block_large(fork_context: &ForkContext) -> BeaconBlock<E> {
|
||||
let mut block = BeaconBlockMerge::<E>::empty(&E::default_spec());
|
||||
fn merge_block_large(fork_context: &ForkContext, spec: &ChainSpec) -> BeaconBlock<E> {
|
||||
let mut block = BeaconBlockMerge::<E>::empty(spec);
|
||||
let tx = VariableList::from(vec![0; 1024]);
|
||||
let txs = VariableList::from(std::iter::repeat(tx).take(100000).collect::<Vec<_>>());
|
||||
|
||||
block.body.execution_payload.execution_payload.transactions = txs;
|
||||
|
||||
let block = BeaconBlock::Merge(block);
|
||||
assert!(block.ssz_bytes_len() > max_rpc_size(fork_context));
|
||||
assert!(block.ssz_bytes_len() > max_rpc_size(fork_context, spec.max_chunk_size as usize));
|
||||
block
|
||||
}
|
||||
|
||||
@@ -58,10 +58,12 @@ fn test_status_rpc() {
|
||||
|
||||
let log = common::build_log(log_level, enable_logging);
|
||||
|
||||
let spec = E::default_spec();
|
||||
|
||||
rt.block_on(async {
|
||||
// get sender/receiver
|
||||
let (mut sender, mut receiver) =
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base).await;
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base, &spec).await;
|
||||
|
||||
// Dummy STATUS RPC message
|
||||
let rpc_request = Request::Status(StatusMessage {
|
||||
@@ -150,10 +152,12 @@ fn test_blocks_by_range_chunked_rpc() {
|
||||
|
||||
let rt = Arc::new(Runtime::new().unwrap());
|
||||
|
||||
let spec = E::default_spec();
|
||||
|
||||
rt.block_on(async {
|
||||
// get sender/receiver
|
||||
let (mut sender, mut receiver) =
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Merge).await;
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Merge, &spec).await;
|
||||
|
||||
// BlocksByRange Request
|
||||
let rpc_request = Request::BlocksByRange(BlocksByRangeRequest::new(0, messages_to_send));
|
||||
@@ -169,7 +173,7 @@ fn test_blocks_by_range_chunked_rpc() {
|
||||
let signed_full_block = SignedBeaconBlock::from_block(full_block, Signature::empty());
|
||||
let rpc_response_altair = Response::BlocksByRange(Some(Arc::new(signed_full_block)));
|
||||
|
||||
let full_block = merge_block_small(&common::fork_context(ForkName::Merge));
|
||||
let full_block = merge_block_small(&common::fork_context(ForkName::Merge), &spec);
|
||||
let signed_full_block = SignedBeaconBlock::from_block(full_block, Signature::empty());
|
||||
let rpc_response_merge_small = Response::BlocksByRange(Some(Arc::new(signed_full_block)));
|
||||
|
||||
@@ -277,8 +281,9 @@ fn test_blobs_by_range_chunked_rpc() {
|
||||
|
||||
rt.block_on(async {
|
||||
// get sender/receiver
|
||||
let spec = E::default_spec();
|
||||
let (mut sender, mut receiver) =
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Deneb).await;
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Deneb, &spec).await;
|
||||
|
||||
// BlobsByRange Request
|
||||
let rpc_request = Request::BlobsByRange(BlobsByRangeRequest {
|
||||
@@ -379,16 +384,18 @@ fn test_blocks_by_range_over_limit() {
|
||||
|
||||
let rt = Arc::new(Runtime::new().unwrap());
|
||||
|
||||
let spec = E::default_spec();
|
||||
|
||||
rt.block_on(async {
|
||||
// get sender/receiver
|
||||
let (mut sender, mut receiver) =
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Merge).await;
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Merge, &spec).await;
|
||||
|
||||
// BlocksByRange Request
|
||||
let rpc_request = Request::BlocksByRange(BlocksByRangeRequest::new(0, messages_to_send));
|
||||
|
||||
// BlocksByRange Response
|
||||
let full_block = merge_block_large(&common::fork_context(ForkName::Merge));
|
||||
let full_block = merge_block_large(&common::fork_context(ForkName::Merge), &spec);
|
||||
let signed_full_block = SignedBeaconBlock::from_block(full_block, Signature::empty());
|
||||
let rpc_response_merge_large = Response::BlocksByRange(Some(Arc::new(signed_full_block)));
|
||||
|
||||
@@ -461,10 +468,12 @@ fn test_blocks_by_range_chunked_rpc_terminates_correctly() {
|
||||
|
||||
let rt = Arc::new(Runtime::new().unwrap());
|
||||
|
||||
let spec = E::default_spec();
|
||||
|
||||
rt.block_on(async {
|
||||
// get sender/receiver
|
||||
let (mut sender, mut receiver) =
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base).await;
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base, &spec).await;
|
||||
|
||||
// BlocksByRange Request
|
||||
let rpc_request = Request::BlocksByRange(BlocksByRangeRequest::new(0, messages_to_send));
|
||||
@@ -581,10 +590,12 @@ fn test_blocks_by_range_single_empty_rpc() {
|
||||
let log = common::build_log(log_level, enable_logging);
|
||||
let rt = Arc::new(Runtime::new().unwrap());
|
||||
|
||||
let spec = E::default_spec();
|
||||
|
||||
rt.block_on(async {
|
||||
// get sender/receiver
|
||||
let (mut sender, mut receiver) =
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base).await;
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base, &spec).await;
|
||||
|
||||
// BlocksByRange Request
|
||||
let rpc_request = Request::BlocksByRange(BlocksByRangeRequest::new(0, 10));
|
||||
@@ -685,7 +696,7 @@ fn test_blocks_by_root_chunked_rpc() {
|
||||
// get sender/receiver
|
||||
rt.block_on(async {
|
||||
let (mut sender, mut receiver) =
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Merge).await;
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Merge, &spec).await;
|
||||
|
||||
// BlocksByRoot Request
|
||||
let rpc_request =
|
||||
@@ -707,7 +718,7 @@ fn test_blocks_by_root_chunked_rpc() {
|
||||
let signed_full_block = SignedBeaconBlock::from_block(full_block, Signature::empty());
|
||||
let rpc_response_altair = Response::BlocksByRoot(Some(Arc::new(signed_full_block)));
|
||||
|
||||
let full_block = merge_block_small(&common::fork_context(ForkName::Merge));
|
||||
let full_block = merge_block_small(&common::fork_context(ForkName::Merge), &spec);
|
||||
let signed_full_block = SignedBeaconBlock::from_block(full_block, Signature::empty());
|
||||
let rpc_response_merge_small = Response::BlocksByRoot(Some(Arc::new(signed_full_block)));
|
||||
|
||||
@@ -812,7 +823,7 @@ fn test_blocks_by_root_chunked_rpc_terminates_correctly() {
|
||||
// get sender/receiver
|
||||
rt.block_on(async {
|
||||
let (mut sender, mut receiver) =
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base).await;
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base, &spec).await;
|
||||
|
||||
// BlocksByRoot Request
|
||||
let rpc_request =
|
||||
@@ -939,10 +950,13 @@ fn test_goodbye_rpc() {
|
||||
let log = common::build_log(log_level, enable_logging);
|
||||
|
||||
let rt = Arc::new(Runtime::new().unwrap());
|
||||
|
||||
let spec = E::default_spec();
|
||||
|
||||
// get sender/receiver
|
||||
rt.block_on(async {
|
||||
let (mut sender, mut receiver) =
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base).await;
|
||||
common::build_node_pair(Arc::downgrade(&rt), &log, ForkName::Base, &spec).await;
|
||||
|
||||
// build the sender future
|
||||
let sender_future = async {
|
||||
|
||||
@@ -51,4 +51,6 @@ parking_lot = "0.12.0"
|
||||
environment = { path = "../../lighthouse/environment" }
|
||||
|
||||
[features]
|
||||
# NOTE: This can be run via cargo build --bin lighthouse --features network/disable-backfill
|
||||
disable-backfill = []
|
||||
fork_from_env = ["beacon_chain/fork_from_env"]
|
||||
|
||||
@@ -1845,6 +1845,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
attestation_verification::verify_propagation_slot_range(
|
||||
seen_clock,
|
||||
failed_att.attestation(),
|
||||
&self.chain.spec,
|
||||
);
|
||||
|
||||
// Only penalize the peer if it would have been invalid at the moment we received
|
||||
@@ -2396,6 +2397,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
sync_committee_verification::verify_propagation_slot_range(
|
||||
seen_clock,
|
||||
&sync_committee_message_slot,
|
||||
&self.chain.spec,
|
||||
);
|
||||
hindsight_verification.is_err()
|
||||
};
|
||||
@@ -2708,6 +2710,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
|
||||
let is_timely = attestation_verification::verify_propagation_slot_range(
|
||||
&self.chain.slot_clock,
|
||||
attestation,
|
||||
&self.chain.spec,
|
||||
)
|
||||
.is_ok();
|
||||
|
||||
|
||||
@@ -671,14 +671,3 @@ impl<E: EthSpec> NetworkBeaconProcessor<TestBeaconChainType<E>> {
|
||||
(network_beacon_processor, beacon_processor_receive)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn queued_block_delay_is_sane() {
|
||||
assert!(
|
||||
beacon_processor::work_reprocessing_queue::ADDITIONAL_QUEUED_BLOCK_DELAY
|
||||
< beacon_chain::MAXIMUM_GOSSIP_CLOCK_DISPARITY
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![cfg(not(debug_assertions))] // Tests are too slow in debug.
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{
|
||||
@@ -10,7 +11,7 @@ use crate::{
|
||||
use beacon_chain::test_utils::{
|
||||
test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
|
||||
};
|
||||
use beacon_chain::{BeaconChain, ChainConfig, WhenSlotSkipped, MAXIMUM_GOSSIP_CLOCK_DISPARITY};
|
||||
use beacon_chain::{BeaconChain, ChainConfig, WhenSlotSkipped};
|
||||
use beacon_processor::{work_reprocessing_queue::*, *};
|
||||
use lighthouse_network::discovery::ConnectionId;
|
||||
use lighthouse_network::rpc::methods::BlobsByRangeRequest;
|
||||
@@ -221,7 +222,7 @@ impl TestRig {
|
||||
};
|
||||
let network_beacon_processor = Arc::new(network_beacon_processor);
|
||||
|
||||
BeaconProcessor {
|
||||
let beacon_processor = BeaconProcessor {
|
||||
network_globals,
|
||||
executor,
|
||||
max_workers: cmp::max(1, num_cpus::get()),
|
||||
@@ -235,8 +236,11 @@ impl TestRig {
|
||||
work_reprocessing_rx,
|
||||
Some(work_journal_tx),
|
||||
harness.chain.slot_clock.clone(),
|
||||
chain.spec.maximum_gossip_clock_disparity(),
|
||||
);
|
||||
|
||||
assert!(!beacon_processor.is_err());
|
||||
|
||||
Self {
|
||||
chain,
|
||||
next_block: Arc::new(next_block_tuple.0),
|
||||
@@ -559,7 +563,7 @@ async fn import_gossip_block_acceptably_early() {
|
||||
|
||||
rig.chain
|
||||
.slot_clock
|
||||
.set_current_time(slot_start - MAXIMUM_GOSSIP_CLOCK_DISPARITY);
|
||||
.set_current_time(slot_start - rig.chain.spec.maximum_gossip_clock_disparity());
|
||||
|
||||
assert_eq!(
|
||||
rig.chain.slot().unwrap(),
|
||||
@@ -614,9 +618,9 @@ async fn import_gossip_block_unacceptably_early() {
|
||||
.start_of(rig.next_block.slot())
|
||||
.unwrap();
|
||||
|
||||
rig.chain
|
||||
.slot_clock
|
||||
.set_current_time(slot_start - MAXIMUM_GOSSIP_CLOCK_DISPARITY - Duration::from_millis(1));
|
||||
rig.chain.slot_clock.set_current_time(
|
||||
slot_start - rig.chain.spec.maximum_gossip_clock_disparity() - Duration::from_millis(1),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rig.chain.slot().unwrap(),
|
||||
|
||||
@@ -232,6 +232,12 @@ impl<T: BeaconChainTypes> NetworkService<T> {
|
||||
// build the channels for external comms
|
||||
let (network_senders, network_recievers) = NetworkSenders::new();
|
||||
|
||||
#[cfg(feature = "disable-backfill")]
|
||||
warn!(
|
||||
network_log,
|
||||
"Backfill is disabled. DO NOT RUN IN PRODUCTION"
|
||||
);
|
||||
|
||||
// try and construct UPnP port mappings if required.
|
||||
if let Some(upnp_config) = crate::nat::UPnPConfig::from_config(config) {
|
||||
let upnp_log = network_log.new(o!("service" => "UPnP"));
|
||||
@@ -487,10 +493,8 @@ impl<T: BeaconChainTypes> NetworkService<T> {
|
||||
NetworkEvent::PeerConnectedOutgoing(peer_id) => {
|
||||
self.send_to_router(RouterMessage::StatusPeer(peer_id));
|
||||
}
|
||||
NetworkEvent::PeerConnectedIncoming(_)
|
||||
| NetworkEvent::PeerBanned(_)
|
||||
| NetworkEvent::PeerUnbanned(_) => {
|
||||
// No action required for these events.
|
||||
NetworkEvent::PeerConnectedIncoming(_) => {
|
||||
// No action required for this event.
|
||||
}
|
||||
NetworkEvent::PeerDisconnected(peer_id) => {
|
||||
self.send_to_router(RouterMessage::PeerDisconnected(peer_id));
|
||||
|
||||
@@ -50,7 +50,6 @@ use beacon_chain::block_verification_types::AsBlock;
|
||||
use beacon_chain::block_verification_types::RpcBlock;
|
||||
use beacon_chain::{
|
||||
AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, BlockError, EngineState,
|
||||
MAXIMUM_GOSSIP_CLOCK_DISPARITY,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use lighthouse_network::rpc::methods::MAX_REQUEST_BLOCKS;
|
||||
@@ -537,6 +536,7 @@ impl<T: BeaconChainTypes> SyncManager<T> {
|
||||
|
||||
// If we would otherwise be synced, first check if we need to perform or
|
||||
// complete a backfill sync.
|
||||
#[cfg(not(feature = "disable_backfill"))]
|
||||
if matches!(sync_state, SyncState::Synced) {
|
||||
// Determine if we need to start/resume/restart a backfill sync.
|
||||
match self.backfill_sync.start(&mut self.network) {
|
||||
@@ -561,6 +561,7 @@ impl<T: BeaconChainTypes> SyncManager<T> {
|
||||
}
|
||||
Some((RangeSyncType::Finalized, start_slot, target_slot)) => {
|
||||
// If there is a backfill sync in progress pause it.
|
||||
#[cfg(not(feature = "disable_backfill"))]
|
||||
self.backfill_sync.pause();
|
||||
|
||||
SyncState::SyncingFinalized {
|
||||
@@ -570,6 +571,7 @@ impl<T: BeaconChainTypes> SyncManager<T> {
|
||||
}
|
||||
Some((RangeSyncType::Head, start_slot, target_slot)) => {
|
||||
// If there is a backfill sync in progress pause it.
|
||||
#[cfg(not(feature = "disable_backfill"))]
|
||||
self.backfill_sync.pause();
|
||||
|
||||
SyncState::SyncingHead {
|
||||
@@ -815,14 +817,15 @@ impl<T: BeaconChainTypes> SyncManager<T> {
|
||||
}
|
||||
|
||||
fn should_delay_lookup(&mut self, slot: Slot) -> bool {
|
||||
let maximum_gossip_clock_disparity = self.chain.spec.maximum_gossip_clock_disparity();
|
||||
let earliest_slot = self
|
||||
.chain
|
||||
.slot_clock
|
||||
.now_with_past_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY);
|
||||
.now_with_past_tolerance(maximum_gossip_clock_disparity);
|
||||
let latest_slot = self
|
||||
.chain
|
||||
.slot_clock
|
||||
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY);
|
||||
.now_with_future_tolerance(maximum_gossip_clock_disparity);
|
||||
if let (Some(earliest_slot), Some(latest_slot)) = (earliest_slot, latest_slot) {
|
||||
let msg_for_current_slot = slot >= earliest_slot && slot <= latest_slot;
|
||||
let delay_threshold_unmet = self
|
||||
|
||||
@@ -618,8 +618,10 @@ pub fn get_config<E: EthSpec>(
|
||||
};
|
||||
}
|
||||
|
||||
client_config.chain.max_network_size =
|
||||
lighthouse_network::gossip_max_size(spec.bellatrix_fork_epoch.is_some());
|
||||
client_config.chain.max_network_size = lighthouse_network::gossip_max_size(
|
||||
spec.bellatrix_fork_epoch.is_some(),
|
||||
spec.gossip_max_size as usize,
|
||||
);
|
||||
|
||||
if cli_args.is_present("slasher") {
|
||||
let slasher_dir = if let Some(slasher_dir) = cli_args.value_of("slasher-dir") {
|
||||
|
||||
Reference in New Issue
Block a user