Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
const HTTP_SYNC_AGGREGATOR_TIMEOUT_QUOTIENT: u32 = 24; // For DVT involving middleware only
// TODO(EIP-7732): Determine what this quotient should be
const HTTP_PTC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
const HTTP_INCLUSION_LIST_TIMEOUT_QUOTIENT: u32 = 4;
const HTTP_INCLUSION_LIST_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4;
const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4;
Expand All @@ -105,6 +106,7 @@ pub struct Timeouts {
pub sync_duties: Duration,
pub sync_aggregators: Duration,
pub ptc_duties: Duration,
pub inclusion_list: Duration,
pub inclusion_list_duties: Duration,
pub get_beacon_blocks_ssz: Duration,
pub get_debug_beacon_states: Duration,
Expand All @@ -128,6 +130,7 @@ impl Timeouts {
sync_duties: timeout,
sync_aggregators: timeout,
ptc_duties: timeout,
inclusion_list: timeout,
inclusion_list_duties: timeout,
get_beacon_blocks_ssz: timeout,
get_debug_beacon_states: timeout,
Expand All @@ -153,6 +156,7 @@ impl Timeouts {
sync_duties: base_timeout / HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT,
sync_aggregators: base_timeout / HTTP_SYNC_AGGREGATOR_TIMEOUT_QUOTIENT,
ptc_duties: base_timeout / HTTP_PTC_DUTIES_TIMEOUT_QUOTIENT,
inclusion_list: base_timeout / HTTP_INCLUSION_LIST_TIMEOUT_QUOTIENT,
inclusion_list_duties: base_timeout / HTTP_INCLUSION_LIST_DUTIES_TIMEOUT_QUOTIENT,
get_beacon_blocks_ssz: base_timeout / HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT,
get_debug_beacon_states: base_timeout / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT,
Expand Down Expand Up @@ -3851,6 +3855,66 @@ impl BeaconNodeHttpClient {
self.get_opt(path).await
}

/// `GET validator/inclusion_list`
pub async fn get_validator_inclusion_list(
&self,
slot: Slot,
) -> Result<GenericResponse<InclusionListTransactions>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("inclusion_list");

path.query_pairs_mut()
.append_pair("slot", &slot.to_string());

self.get_with_timeout(path, self.timeouts.inclusion_list)
.await
}

/// `POST validator/inclusion_list`
pub async fn post_validator_inclusion_list(
&self,
signed_inclusion_list: &SignedInclusionList,
fork_name: ForkName,
) -> Result<(), Error> {
let mut path = self.eth_path(V1)?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("inclusion_list");

// The request body is wrapped in a `data` object, per the beacon-APIs specs
let body = serde_json::json!({ "data": signed_inclusion_list });
self.post_generic_with_consensus_version(path, &body, None, fork_name)
.await?;

Ok(())
}

/// `POST validator/inclusion_list` (SSZ)
pub async fn post_validator_inclusion_list_ssz(
&self,
signed_inclusion_list: &SignedInclusionList,
fork_name: ForkName,
) -> Result<(), Error> {
let mut path = self.eth_path(V1)?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("inclusion_list");

let ssz_body = signed_inclusion_list.as_ssz_bytes();

self.post_generic_with_consensus_version_and_ssz_body(path, ssz_body, None, fork_name)
.await?;

Ok(())
}

/// `POST lighthouse/liveness`
pub async fn post_lighthouse_liveness(
&self,
Expand Down
7 changes: 7 additions & 0 deletions common/eth2/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,13 @@ impl TryFrom<Option<String>> for SkipRandaoVerification {
}
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct InclusionListTransactions {
#[serde(with = "ssz_types::serde_utils::prog_list_of_hex_prog_var_list")]
pub transactions: ProgressiveTransactions,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct ValidatorAttestationDataQuery {
pub slot: Slot,
Expand Down
13 changes: 13 additions & 0 deletions consensus/types/src/core/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ pub struct ChainSpec {
sync_message_due_gloas: Duration,
contribution_and_proof_due: Duration,
contribution_and_proof_due_gloas: Duration,
inclusion_list_due: Duration,

/*
* Reward and penalty quotients
Expand Down Expand Up @@ -985,6 +986,12 @@ impl ChainSpec {
}
}

/// Spec: `get_inclusion_list_due_ms`.
/// Get the duration into a slot in which an inclusion list production duty is due.
pub fn get_inclusion_list_due(&self) -> Duration {
self.inclusion_list_due
}

/// Calculate the duration into a slot for a given slot component
pub fn compute_slot_component_duration(
&self,
Expand Down Expand Up @@ -1101,6 +1108,9 @@ impl ChainSpec {
self.contribution_and_proof_due_gloas = self
.compute_slot_component_duration(self.contribution_due_bps_gloas)
.expect("invalid chain spec: cannot compute contribution_and_proof_due_gloas");
self.inclusion_list_due = self
.compute_slot_component_duration(self.inclusion_list_due_bps)
.expect("invalid chain spec: cannot compute inclusion_list_due");

self.attestation_subnet_prefix_bits = compute_attestation_subnet_prefix_bits(
self.attestation_subnet_count,
Expand Down Expand Up @@ -1246,6 +1256,7 @@ impl ChainSpec {
sync_message_due_gloas: Duration::from_millis(3000),
contribution_and_proof_due: Duration::from_millis(8000),
contribution_and_proof_due_gloas: Duration::from_millis(6000),
inclusion_list_due: Duration::from_millis(8000),

/*
* Reward and penalty quotients
Expand Down Expand Up @@ -1591,6 +1602,7 @@ impl ChainSpec {
sync_message_due: Duration::from_millis(1999),
sync_message_due_gloas: Duration::from_millis(1500),
contribution_and_proof_due: Duration::from_millis(4000),
inclusion_list_due: Duration::from_millis(4000),
contribution_and_proof_due_gloas: Duration::from_millis(3000),

// Networking Fulu
Expand Down Expand Up @@ -1697,6 +1709,7 @@ impl ChainSpec {
sync_message_due: Duration::from_millis(1666),
sync_message_due_gloas: Duration::from_millis(1250),
contribution_and_proof_due: Duration::from_millis(3333),
inclusion_list_due: Duration::from_millis(3333),
contribution_and_proof_due_gloas: Duration::from_millis(2500),

/*
Expand Down
110 changes: 108 additions & 2 deletions testing/validator_test_rig/src/mock_beacon_node.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use eth2::types::{GenericResponse, PublishBlockRequest, SyncingData};
use eth2::types::{GenericResponse, InclusionListTransactions, PublishBlockRequest, SyncingData};
use eth2::{BLOB_DATA_INCLUDED_HEADER, BeaconNodeHttpClient, CONSENSUS_VERSION_HEADER, Timeouts};
use mockito::{Matcher, Mock, Server, ServerGuard};
use regex::Regex;
Expand All @@ -13,7 +13,7 @@ use tracing::info;
use types::{
BeaconBlock, ChainSpec, ConfigAndPreset, EthSpec, ExecutionPayloadEnvelope, ForkName, Hash256,
PayloadAttestationData, PayloadAttestationMessage, SignedBlindedBeaconBlock,
SignedExecutionPayloadEnvelope, Slot,
SignedExecutionPayloadEnvelope, SignedInclusionList, Slot,
};

pub struct MockBeaconNode<E: EthSpec> {
Expand All @@ -24,6 +24,7 @@ pub struct MockBeaconNode<E: EthSpec> {
pub received_full_blocks: Arc<Mutex<Vec<PublishBlockRequest<E>>>>,
pub execution_payload_envelope: Arc<Mutex<Vec<SignedExecutionPayloadEnvelope<E>>>>,
pub payload_attestation_message: Arc<Mutex<Vec<PayloadAttestationMessage>>>,
pub received_inclusion_lists: Arc<Mutex<Vec<SignedInclusionList>>>,
}

impl<E: EthSpec> MockBeaconNode<E> {
Expand All @@ -42,6 +43,7 @@ impl<E: EthSpec> MockBeaconNode<E> {
received_full_blocks: Arc::new(Mutex::new(Vec::new())),
execution_payload_envelope: Arc::new(Mutex::new(Vec::new())),
payload_attestation_message: Arc::new(Mutex::new(Vec::new())),
received_inclusion_lists: Arc::new(Mutex::new(Vec::new())),
}
}

Expand Down Expand Up @@ -270,6 +272,38 @@ impl<E: EthSpec> MockBeaconNode<E> {
.create()
}

/// Mocks `GET /eth/v1/validator/inclusion_list`
pub fn mock_get_validator_inclusion_list(
&mut self,
transactions: &InclusionListTransactions,
slot: Slot,
) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/validator/inclusion_list$").unwrap();

let data = GenericResponse::from(transactions.clone());

self.server
.mock("GET", Matcher::Regex(path_pattern.to_string()))
.match_query(Matcher::UrlEncoded("slot".into(), slot.to_string()))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&data).unwrap())
.create()
}

/// Mocks `GET /eth/v1/validator/inclusion_list` returning error
pub fn mock_get_validator_inclusion_list_error(&mut self, slot: Slot) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/validator/inclusion_list$").unwrap();

self.server
.mock("GET", Matcher::Regex(path_pattern.to_string()))
.match_query(Matcher::UrlEncoded("slot".into(), slot.to_string()))
.with_status(500)
.with_header("content-type", "application/json")
.with_body(r#"{"message":"Internal server error"}"#)
.create()
}

/// Mocks the `post_beacon_blinded_blocks_v2_ssz` response with an optional `delay`.
pub fn mock_post_beacon_blinded_blocks_v2_ssz(&mut self, delay: Duration) -> Mock {
let path_pattern = Regex::new(r"^/eth/v2/beacon/blinded_blocks$").unwrap();
Expand Down Expand Up @@ -459,4 +493,76 @@ impl<E: EthSpec> MockBeaconNode<E> {
.with_body(r#"{"message":"Internal server error"}"#)
.create()
}

/// Mocks `POST /eth/v1/validator/inclusion_list` (JSON) to receive signed inclusion lists.
pub fn mock_post_validator_inclusion_list_json(&mut self, fork_name: ForkName) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/validator/inclusion_list$").unwrap();

let received_inclusion_lists = Arc::clone(&self.received_inclusion_lists);

self.server
.mock("POST", Matcher::Regex(path_pattern.to_string()))
.match_header("content-type", "application/json")
.match_header(CONSENSUS_VERSION_HEADER, fork_name.to_string().as_str())
.with_status(200)
.with_body_from_request(move |request| {
let body = request.body().expect("Failed to get request body");
// The request body is wrapped in a `data` object, per the beacon-APIs specs
let wrapper: GenericResponse<SignedInclusionList> = serde_json::from_slice(body)
.expect("Failed to deserialize SignedInclusionList from JSON");
received_inclusion_lists.lock().unwrap().push(wrapper.data);
vec![]
})
.create()
}

/// Mocks `POST /eth/v1/validator/inclusion_list` (JSON) returning error
pub fn mock_post_validator_inclusion_list_json_error(&mut self, fork_name: ForkName) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/validator/inclusion_list$").unwrap();

self.server
.mock("POST", Matcher::Regex(path_pattern.to_string()))
.match_header("content-type", "application/json")
.match_header(CONSENSUS_VERSION_HEADER, fork_name.to_string().as_str())
.with_status(500)
.with_body(r#"{"message":"Internal server error"}"#)
.create()
}

/// Mocks `POST /eth/v1/validator/inclusion_list` (SSZ) to receive signed inclusion lists.
pub fn mock_post_validator_inclusion_list_ssz(&mut self, fork_name: ForkName) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/validator/inclusion_list$").unwrap();

let received_inclusion_lists = Arc::clone(&self.received_inclusion_lists);

self.server
.mock("POST", Matcher::Regex(path_pattern.to_string()))
.match_header("content-type", "application/octet-stream")
.match_header(CONSENSUS_VERSION_HEADER, fork_name.to_string().as_str())
.with_status(200)
.with_body_from_request(move |request| {
let body = request.body().expect("Failed to get request body");
let inclusion_list = SignedInclusionList::from_ssz_bytes(body)
.expect("Failed to deserialize SignedInclusionList from SSZ");
received_inclusion_lists
.lock()
.unwrap()
.push(inclusion_list);
vec![]
})
.create()
}

/// Mocks `POST /eth/v1/validator/inclusion_list` (SSZ) returning error
pub fn mock_post_validator_inclusion_list_ssz_error(&mut self, fork_name: ForkName) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/validator/inclusion_list$").unwrap();

self.server
.mock("POST", Matcher::Regex(path_pattern.to_string()))
.match_header("content-type", "application/octet-stream")
.match_header(CONSENSUS_VERSION_HEADER, fork_name.to_string().as_str())
.with_status(500)
.with_body(r#"{"message":"Internal server error"}"#)
.create()
}
}
19 changes: 14 additions & 5 deletions testing/validator_test_rig/src/mock_validator_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ use futures::{Stream, stream};
use std::future::Future;
use std::sync::Arc;
use types::{
Address, Epoch, ExecutionPayloadEnvelope, Graffiti, MainnetEthSpec, PayloadAttestationData,
PayloadAttestationMessage, ProposerPreferences, SelectionProof, SignedAggregateAndProof,
SignedContributionAndProof, SignedExecutionPayloadEnvelope, SignedProposerPreferences,
SignedValidatorRegistrationData, SingleAttestation, Slot, SyncCommitteeMessage,
SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData,
Address, Epoch, ExecutionPayloadEnvelope, Graffiti, InclusionList, MainnetEthSpec,
PayloadAttestationData, PayloadAttestationMessage, ProposerPreferences, SelectionProof,
SignedAggregateAndProof, SignedContributionAndProof, SignedExecutionPayloadEnvelope,
SignedInclusionList, SignedProposerPreferences, SignedValidatorRegistrationData,
SingleAttestation, Slot, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId,
ValidatorRegistrationData,
};
use validator_store::{
AggregateToSign, AttestationToSign, ContributionToSign, DoppelgangerStatus,
Expand Down Expand Up @@ -187,4 +188,12 @@ impl ValidatorStore for MockValidatorStore {
fn proposal_data(&self, _pubkey: &PublicKeyBytes) -> Option<ProposalData> {
panic!("MockValidatorStore::proposal_data called without a hook")
}

async fn sign_inclusion_list(
&self,
_validator_pubkey: PublicKeyBytes,
_inclusion_list: InclusionList,
) -> Result<SignedInclusionList, StoreError<Self::Error>> {
panic!("MockValidatorStore::sign_inclusion_list called without a hook")
}
}
Loading
Loading