diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 4526b2b360..824b17e275 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -1143,6 +1143,49 @@ where Ok(single_attestation) } + pub async fn produce_signed_inclusion_list_for_slot( + &self, + slot: Slot, + validator_index: usize, + inclusion_list_committee_root: Hash256, + state: Cow<'_, BeaconState>, + ) -> Result, BeaconChainError> { + let epoch = slot.epoch(E::slots_per_epoch()); + let fork = self.spec.fork_at_epoch(epoch); + + let inclusion_list_transactions = self + .chain + .produce_inclusion_list(slot) + .await + .unwrap() + .unwrap(); + + let inclusion_list = InclusionList { + transactions: inclusion_list_transactions, + slot, + validator_index: validator_index as u64, + inclusion_list_committee_root, + }; + + let signature = { + let domain = self.spec.get_domain( + epoch, + Domain::InclusionListCommittee, + &fork, + state.genesis_validators_root(), + ); + + let message = inclusion_list.signing_root(domain); + + self.validator_keypairs[validator_index].sk.sign(message) + }; + + Ok(SignedInclusionList { + message: inclusion_list, + signature, + }) + } + /// Produces an "unaggregated" attestation for the given `slot` and `index` that attests to /// `beacon_block_root`. The provided `state` should match the `block.state_root` for the /// `block` identified by `beacon_block_root`. @@ -1501,6 +1544,30 @@ where .collect() } + pub async fn make_signed_inclusion_lists( + &self, + inclusion_list_committee: InclusionListCommittee, + state: &BeaconState, + slot: Slot, + ) -> Vec> { + let mut inclusion_lists = vec![]; + let il_committee_root = inclusion_list_committee.tree_hash_root(); + for validator_index in &inclusion_list_committee { + inclusion_lists.push( + self.produce_signed_inclusion_list_for_slot( + slot, + *validator_index as usize, + il_committee_root, + Cow::Borrowed(state), + ) + .await + .unwrap(), + ); + } + + inclusion_lists + } + /// A list of attestations for each committee for the given slot. /// /// The first layer of the Vec is organised per committee. For example, if the return value is diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index d727d2c159..ba022b155f 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -2,9 +2,12 @@ use super::Context; use crate::engine_api::{http::*, *}; use crate::json_structures::*; use crate::test_utils::{DEFAULT_CLIENT_VERSION, DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI}; +use crate::EthersTransaction; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value as JsonValue; +use ssz_types::VariableList; use std::sync::Arc; +use types::InclusionListTransactions; pub const GENERIC_ERROR_CODE: i64 = -1234; pub const BAD_PARAMS_ERROR_CODE: i64 = -32602; @@ -686,6 +689,35 @@ pub async fn handle_rpc( Ok(serde_json::to_value(response).unwrap()) } + ENGINE_GET_INCLUSION_LIST_V1 => { + // This is a real transaction hex encoded, but we don't care about the contents of the transaction. + let transaction: EthersTransaction = serde_json::from_str( + r#"{ + "blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2", + "blockNumber":"0x5daf3b", + "from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d", + "gas":"0xc350", + "gasPrice":"0x4a817c800", + "hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b", + "input":"0x68656c6c6f21", + "nonce":"0x15", + "to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb", + "transactionIndex":"0x41", + "value":"0xf3dbb76162000", + "v":"0x25", + "r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea", + "s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c" + }"#, + ) + .unwrap(); + let tx_list: InclusionListTransactions = + vec![VariableList::new(transaction.rlp().to_vec()) + .map_err(|e| format!("Failed to convert transaction to SSZ: {:?}", e)) + .unwrap()] + .into(); + + Ok(serde_json::to_value(tx_list).unwrap()) + } other => Err(( format!("The method {} does not exist/is not available", other), METHOD_NOT_FOUND_CODE, diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index bc3159e074..7f25b7ceeb 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -6056,6 +6056,21 @@ impl ApiTester { self } + pub async fn test_create_inclusion_lists(self) -> Self { + let state = self.chain.head_beacon_state_cloned(); + let slot = self.harness.chain.slot().unwrap(); + self.harness.extend_to_slot(slot).await; + let inclusion_list_committee = state + .get_inclusion_list_committee(slot + 1, &self.chain.spec) + .unwrap(); + + self.harness + .make_signed_inclusion_lists(inclusion_list_committee, &state, slot + 1) + .await; + + self + } + pub async fn test_get_events_electra(self) -> Self { let topics = vec![EventTopic::SingleAttestation]; let mut events_future = self @@ -7288,3 +7303,17 @@ async fn expected_withdrawals_valid_capella() { .test_get_expected_withdrawals_capella() .await; } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn create_signed_inclusion_lists() { + let mut config = ApiTesterConfig::default(); + config.spec.altair_fork_epoch = Some(Epoch::new(0)); + config.spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + config.spec.capella_fork_epoch = Some(Epoch::new(0)); + config.spec.deneb_fork_epoch = Some(Epoch::new(0)); + config.spec.electra_fork_epoch = Some(Epoch::new(0)); + ApiTester::new_from_config(config) + .await + .test_create_inclusion_lists() + .await; +} diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index f1adafd9ee..34485ef69c 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -845,7 +845,9 @@ impl BeaconState { let epoch = slot.epoch(E::slots_per_epoch()); let current_epoch = self.current_epoch(); let next_epoch = current_epoch.safe_add(1)?; - if epoch != current_epoch || epoch != next_epoch { + + // TODO(focil) review this logic + if epoch != current_epoch && epoch != next_epoch { return Err(Error::SlotOutOfBounds); }