Skip to content

Commit 276eda3

Browse files
authored
POST /eth/v2/beacon/pool/attestations bugfixes (#6867)
1 parent d47b3e3 commit 276eda3

File tree

15 files changed

+156
-59
lines changed

15 files changed

+156
-59
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon_node/beacon_chain/src/attestation_verification.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ impl<T: BeaconChainTypes> VerifiedUnaggregatedAttestation<'_, T> {
327327

328328
pub fn single_attestation(&self) -> Option<SingleAttestation> {
329329
Some(SingleAttestation {
330-
committee_index: self.attestation.committee_index()? as usize,
331-
attester_index: self.validator_index,
330+
committee_index: self.attestation.committee_index()?,
331+
attester_index: self.validator_index as u64,
332332
data: self.attestation.data().clone(),
333333
signature: self.attestation.signature().clone(),
334334
})

beacon_node/beacon_chain/src/test_utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,15 +1131,15 @@ where
11311131
.unwrap();
11321132

11331133
let single_attestation =
1134-
attestation.to_single_attestation_with_attester_index(attester_index)?;
1134+
attestation.to_single_attestation_with_attester_index(attester_index as u64)?;
11351135

11361136
let attestation: Attestation<E> = single_attestation.to_attestation(committee.committee)?;
11371137

11381138
assert_eq!(
11391139
single_attestation.committee_index,
1140-
attestation.committee_index().unwrap() as usize
1140+
attestation.committee_index().unwrap()
11411141
);
1142-
assert_eq!(single_attestation.attester_index, validator_index);
1142+
assert_eq!(single_attestation.attester_index, validator_index as u64);
11431143
Ok(single_attestation)
11441144
}
11451145

beacon_node/http_api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ rand = { workspace = true }
3232
safe_arith = { workspace = true }
3333
sensitive_url = { workspace = true }
3434
serde = { workspace = true }
35+
serde_json = { workspace = true }
3536
slog = { workspace = true }
3637
slot_clock = { workspace = true }
3738
state_processing = { workspace = true }

beacon_node/http_api/src/lib.rs

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ mod validator;
3030
mod validator_inclusion;
3131
mod validators;
3232
mod version;
33-
3433
use crate::light_client::{get_light_client_bootstrap, get_light_client_updates};
3534
use crate::produce_block::{produce_blinded_block_v2, produce_block_v2, produce_block_v3};
3635
use crate::version::fork_versioned_response;
@@ -63,6 +62,7 @@ pub use publish_blocks::{
6362
publish_blinded_block, publish_block, reconstruct_block, ProvenancedBlock,
6463
};
6564
use serde::{Deserialize, Serialize};
65+
use serde_json::Value;
6666
use slog::{crit, debug, error, info, warn, Logger};
6767
use slot_clock::SlotClock;
6868
use ssz::Encode;
@@ -83,14 +83,13 @@ use tokio_stream::{
8383
wrappers::{errors::BroadcastStreamRecvError, BroadcastStream},
8484
StreamExt,
8585
};
86-
use types::ChainSpec;
8786
use types::{
8887
fork_versioned_response::EmptyMetadata, Attestation, AttestationData, AttestationShufflingId,
89-
AttesterSlashing, BeaconStateError, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName,
90-
ForkVersionedResponse, Hash256, ProposerPreparationData, ProposerSlashing, RelativeEpoch,
91-
SignedAggregateAndProof, SignedBlindedBeaconBlock, SignedBlsToExecutionChange,
92-
SignedContributionAndProof, SignedValidatorRegistrationData, SignedVoluntaryExit,
93-
SingleAttestation, Slot, SyncCommitteeMessage, SyncContributionData,
88+
AttesterSlashing, BeaconStateError, ChainSpec, CommitteeCache, ConfigAndPreset, Epoch, EthSpec,
89+
ForkName, ForkVersionedResponse, Hash256, ProposerPreparationData, ProposerSlashing,
90+
RelativeEpoch, SignedAggregateAndProof, SignedBlindedBeaconBlock, SignedBlsToExecutionChange,
91+
SignedContributionAndProof, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot,
92+
SyncCommitteeMessage, SyncContributionData,
9493
};
9594
use validator::pubkey_to_validator_index;
9695
use version::{
@@ -1279,6 +1278,9 @@ pub fn serve<T: BeaconChainTypes>(
12791278
let consensus_version_header_filter =
12801279
warp::header::header::<ForkName>(CONSENSUS_VERSION_HEADER);
12811280

1281+
let optional_consensus_version_header_filter =
1282+
warp::header::optional::<ForkName>(CONSENSUS_VERSION_HEADER);
1283+
12821284
// POST beacon/blocks
12831285
let post_beacon_blocks = eth_v1
12841286
.and(warp::path("beacon"))
@@ -1829,20 +1831,19 @@ pub fn serve<T: BeaconChainTypes>(
18291831
.and(task_spawner_filter.clone())
18301832
.and(chain_filter.clone());
18311833

1832-
let beacon_pool_path_any = any_version
1834+
let beacon_pool_path_v2 = eth_v2
18331835
.and(warp::path("beacon"))
18341836
.and(warp::path("pool"))
18351837
.and(task_spawner_filter.clone())
18361838
.and(chain_filter.clone());
18371839

1838-
let beacon_pool_path_v2 = eth_v2
1840+
let beacon_pool_path_any = any_version
18391841
.and(warp::path("beacon"))
18401842
.and(warp::path("pool"))
18411843
.and(task_spawner_filter.clone())
18421844
.and(chain_filter.clone());
18431845

1844-
// POST beacon/pool/attestations
1845-
let post_beacon_pool_attestations = beacon_pool_path
1846+
let post_beacon_pool_attestations_v1 = beacon_pool_path
18461847
.clone()
18471848
.and(warp::path("attestations"))
18481849
.and(warp::path::end())
@@ -1851,9 +1852,6 @@ pub fn serve<T: BeaconChainTypes>(
18511852
.and(reprocess_send_filter.clone())
18521853
.and(log_filter.clone())
18531854
.then(
1854-
// V1 and V2 are identical except V2 has a consensus version header in the request.
1855-
// We only require this header for SSZ deserialization, which isn't supported for
1856-
// this endpoint presently.
18571855
|task_spawner: TaskSpawner<T::EthSpec>,
18581856
chain: Arc<BeaconChain<T>>,
18591857
attestations: Vec<Attestation<T::EthSpec>>,
@@ -1879,18 +1877,40 @@ pub fn serve<T: BeaconChainTypes>(
18791877
.clone()
18801878
.and(warp::path("attestations"))
18811879
.and(warp::path::end())
1882-
.and(warp_utils::json::json())
1880+
.and(warp_utils::json::json::<Value>())
1881+
.and(optional_consensus_version_header_filter)
18831882
.and(network_tx_filter.clone())
1884-
.and(reprocess_send_filter)
1883+
.and(reprocess_send_filter.clone())
18851884
.and(log_filter.clone())
18861885
.then(
18871886
|task_spawner: TaskSpawner<T::EthSpec>,
18881887
chain: Arc<BeaconChain<T>>,
1889-
attestations: Vec<SingleAttestation>,
1888+
payload: Value,
1889+
fork_name: Option<ForkName>,
18901890
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>,
18911891
reprocess_tx: Option<Sender<ReprocessQueueMessage>>,
18921892
log: Logger| async move {
1893-
let attestations = attestations.into_iter().map(Either::Right).collect();
1893+
let attestations =
1894+
match crate::publish_attestations::deserialize_attestation_payload::<T>(
1895+
payload, fork_name, &log,
1896+
) {
1897+
Ok(attestations) => attestations,
1898+
Err(err) => {
1899+
warn!(
1900+
log,
1901+
"Unable to deserialize attestation POST request";
1902+
"error" => ?err
1903+
);
1904+
return warp::reply::with_status(
1905+
warp::reply::json(
1906+
&"Unable to deserialize request body".to_string(),
1907+
),
1908+
eth2::StatusCode::BAD_REQUEST,
1909+
)
1910+
.into_response();
1911+
}
1912+
};
1913+
18941914
let result = crate::publish_attestations::publish_attestations(
18951915
task_spawner,
18961916
chain,
@@ -4765,7 +4785,7 @@ pub fn serve<T: BeaconChainTypes>(
47654785
.uor(post_beacon_blinded_blocks)
47664786
.uor(post_beacon_blocks_v2)
47674787
.uor(post_beacon_blinded_blocks_v2)
4768-
.uor(post_beacon_pool_attestations)
4788+
.uor(post_beacon_pool_attestations_v1)
47694789
.uor(post_beacon_pool_attestations_v2)
47704790
.uor(post_beacon_pool_attester_slashings)
47714791
.uor(post_beacon_pool_proposer_slashings)

beacon_node/http_api/src/publish_attestations.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use either::Either;
4444
use eth2::types::Failure;
4545
use lighthouse_network::PubsubMessage;
4646
use network::NetworkMessage;
47+
use serde_json::Value;
4748
use slog::{debug, error, warn, Logger};
4849
use std::borrow::Cow;
4950
use std::sync::Arc;
@@ -52,18 +53,19 @@ use tokio::sync::{
5253
mpsc::{Sender, UnboundedSender},
5354
oneshot,
5455
};
55-
use types::{Attestation, EthSpec, SingleAttestation};
56+
use types::{Attestation, EthSpec, ForkName, SingleAttestation};
5657

5758
// Error variants are only used in `Debug` and considered `dead_code` by the compiler.
5859
#[derive(Debug)]
59-
enum Error {
60+
pub enum Error {
6061
Validation(AttestationError),
6162
Publication,
6263
ForkChoice(#[allow(dead_code)] BeaconChainError),
6364
AggregationPool(#[allow(dead_code)] AttestationError),
6465
ReprocessDisabled,
6566
ReprocessFull,
6667
ReprocessTimeout,
68+
InvalidJson(#[allow(dead_code)] serde_json::Error),
6769
FailedConversion(#[allow(dead_code)] BeaconChainError),
6870
}
6971

@@ -74,6 +76,36 @@ enum PublishAttestationResult {
7476
Failure(Error),
7577
}
7678

79+
#[allow(clippy::type_complexity)]
80+
pub fn deserialize_attestation_payload<T: BeaconChainTypes>(
81+
payload: Value,
82+
fork_name: Option<ForkName>,
83+
log: &Logger,
84+
) -> Result<Vec<Either<Attestation<T::EthSpec>, SingleAttestation>>, Error> {
85+
if fork_name.is_some_and(|fork_name| fork_name.electra_enabled()) || fork_name.is_none() {
86+
if fork_name.is_none() {
87+
warn!(
88+
log,
89+
"No Consensus Version header specified.";
90+
);
91+
}
92+
93+
Ok(serde_json::from_value::<Vec<SingleAttestation>>(payload)
94+
.map_err(Error::InvalidJson)?
95+
.into_iter()
96+
.map(Either::Right)
97+
.collect())
98+
} else {
99+
Ok(
100+
serde_json::from_value::<Vec<Attestation<T::EthSpec>>>(payload)
101+
.map_err(Error::InvalidJson)?
102+
.into_iter()
103+
.map(Either::Left)
104+
.collect(),
105+
)
106+
}
107+
}
108+
77109
fn verify_and_publish_attestation<T: BeaconChainTypes>(
78110
chain: &Arc<BeaconChain<T>>,
79111
either_attestation: &Either<Attestation<T::EthSpec>, SingleAttestation>,
@@ -163,12 +195,12 @@ fn convert_to_attestation<'a, T: BeaconChainTypes>(
163195
|committee_cache, _| {
164196
let Some(committee) = committee_cache.get_beacon_committee(
165197
single_attestation.data.slot,
166-
single_attestation.committee_index as u64,
198+
single_attestation.committee_index,
167199
) else {
168200
return Err(BeaconChainError::AttestationError(
169201
types::AttestationError::NoCommitteeForSlotAndIndex {
170202
slot: single_attestation.data.slot,
171-
index: single_attestation.committee_index as u64,
203+
index: single_attestation.committee_index,
172204
},
173205
));
174206
};
@@ -199,7 +231,7 @@ pub async fn publish_attestations<T: BeaconChainTypes>(
199231
.iter()
200232
.map(|att| match att {
201233
Either::Left(att) => (att.data().slot, att.committee_index()),
202-
Either::Right(att) => (att.data.slot, Some(att.committee_index as u64)),
234+
Either::Right(att) => (att.data.slot, Some(att.committee_index)),
203235
})
204236
.collect::<Vec<_>>();
205237

beacon_node/http_api/tests/interactive_tests.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use beacon_chain::{
55
ChainConfig,
66
};
77
use beacon_processor::work_reprocessing_queue::ReprocessQueueMessage;
8+
use either::Either;
89
use eth2::types::ProduceBlockV3Response;
910
use eth2::types::{DepositContractData, StateId};
1011
use execution_layer::{ForkchoiceState, PayloadAttributes};
@@ -906,9 +907,11 @@ async fn queue_attestations_from_http() {
906907
.flat_map(|attestations| attestations.into_iter().map(|(att, _subnet)| att))
907908
.collect::<Vec<_>>();
908909

910+
let attestations = Either::Right(single_attestations);
911+
909912
tokio::spawn(async move {
910913
client
911-
.post_beacon_pool_attestations_v2(&single_attestations, fork_name)
914+
.post_beacon_pool_attestations_v2::<E>(attestations, fork_name)
912915
.await
913916
.expect("attestations should be processed successfully")
914917
})

beacon_node/http_api/tests/tests.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use beacon_chain::{
33
test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType},
44
BeaconChain, ChainConfig, StateSkipConfig, WhenSlotSkipped,
55
};
6+
use either::Either;
67
use eth2::{
78
mixin::{RequestAccept, ResponseForkName, ResponseOptional},
89
reqwest::RequestBuilder,
@@ -1810,12 +1811,25 @@ impl ApiTester {
18101811
self
18111812
}
18121813

1813-
pub async fn test_post_beacon_pool_attestations_valid_v1(mut self) -> Self {
1814+
pub async fn test_post_beacon_pool_attestations_valid(mut self) -> Self {
18141815
self.client
18151816
.post_beacon_pool_attestations_v1(self.attestations.as_slice())
18161817
.await
18171818
.unwrap();
18181819

1820+
let fork_name = self
1821+
.attestations
1822+
.first()
1823+
.map(|att| self.chain.spec.fork_name_at_slot::<E>(att.data().slot))
1824+
.unwrap();
1825+
1826+
let attestations = Either::Left(self.attestations.clone());
1827+
1828+
self.client
1829+
.post_beacon_pool_attestations_v2::<E>(attestations, fork_name)
1830+
.await
1831+
.unwrap();
1832+
18191833
assert!(
18201834
self.network_rx.network_recv.recv().await.is_some(),
18211835
"valid attestation should be sent to network"
@@ -1833,8 +1847,10 @@ impl ApiTester {
18331847
.first()
18341848
.map(|att| self.chain.spec.fork_name_at_slot::<E>(att.data.slot))
18351849
.unwrap();
1850+
1851+
let attestations = Either::Right(self.single_attestations.clone());
18361852
self.client
1837-
.post_beacon_pool_attestations_v2(self.single_attestations.as_slice(), fork_name)
1853+
.post_beacon_pool_attestations_v2::<E>(attestations, fork_name)
18381854
.await
18391855
.unwrap();
18401856
assert!(
@@ -1900,10 +1916,10 @@ impl ApiTester {
19001916
.first()
19011917
.map(|att| self.chain.spec.fork_name_at_slot::<E>(att.data().slot))
19021918
.unwrap();
1903-
1919+
let attestations = Either::Right(attestations);
19041920
let err_v2 = self
19051921
.client
1906-
.post_beacon_pool_attestations_v2(attestations.as_slice(), fork_name)
1922+
.post_beacon_pool_attestations_v2::<E>(attestations, fork_name)
19071923
.await
19081924
.unwrap_err();
19091925

@@ -6054,9 +6070,9 @@ impl ApiTester {
60546070
.chain
60556071
.spec
60566072
.fork_name_at_slot::<E>(self.chain.slot().unwrap());
6057-
6073+
let attestations = Either::Right(self.single_attestations.clone());
60586074
self.client
6059-
.post_beacon_pool_attestations_v2(&self.single_attestations, fork_name)
6075+
.post_beacon_pool_attestations_v2::<E>(attestations, fork_name)
60606076
.await
60616077
.unwrap();
60626078

@@ -6375,10 +6391,10 @@ async fn post_beacon_blocks_duplicate() {
63756391
}
63766392

63776393
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6378-
async fn beacon_pools_post_attestations_valid_v1() {
6394+
async fn beacon_pools_post_attestations_valid() {
63796395
ApiTester::new()
63806396
.await
6381-
.test_post_beacon_pool_attestations_valid_v1()
6397+
.test_post_beacon_pool_attestations_valid()
63826398
.await;
63836399
}
63846400

beacon_node/network/src/network_beacon_processor/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
103103
|committee_cache, _| {
104104
let Some(committee) = committee_cache.get_beacon_committee(
105105
single_attestation.data.slot,
106-
single_attestation.committee_index as u64,
106+
single_attestation.committee_index,
107107
) else {
108108
warn!(
109109
self.log,

0 commit comments

Comments
 (0)