Skip to content

Commit

Permalink
Standard beacon api updates (#1831)
Browse files Browse the repository at this point in the history
## Issue Addressed

Resolves #1809 
Resolves #1824
Resolves #1818
Resolves #1828 (hopefully)

## Proposed Changes

- add `validator_index` to the proposer duties endpoint
- add the ability to query for historical proposer duties
- `StateId` deserialization now fails with a 400 warp rejection
- add the `validator_balances` endpoint
- update the `aggregate_and_proofs` endpoint to accept an array
- updates the attester duties endpoint from a `GET` to a `POST`
- reduces the number of times we query for proposer duties from once per slot per validator to only once per slot 


Co-authored-by: realbigsean <[email protected]>
Co-authored-by: Paul Hauner <[email protected]>
  • Loading branch information
3 people committed Nov 9, 2020
1 parent 556190f commit f8da151
Show file tree
Hide file tree
Showing 10 changed files with 722 additions and 322 deletions.
4 changes: 2 additions & 2 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,9 +984,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
///
/// - `VerifiedUnaggregatedAttestation`
/// - `VerifiedAggregatedAttestation`
pub fn apply_attestation_to_fork_choice<'a>(
pub fn apply_attestation_to_fork_choice(
&self,
verified: &'a impl SignatureVerifiedAttestation<T>,
verified: &impl SignatureVerifiedAttestation<T>,
) -> Result<(), Error> {
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_PROCESS_ATTESTATION_TIMES);

Expand Down
1 change: 1 addition & 0 deletions beacon_node/http_api/src/beacon_proposer_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl BeaconProposerCache {

Ok(ProposerData {
pubkey: PublicKeyBytes::from(pubkey),
validator_index: i as u64,
slot,
})
})
Expand Down
444 changes: 277 additions & 167 deletions beacon_node/http_api/src/lib.rs

Large diffs are not rendered by default.

103 changes: 88 additions & 15 deletions beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,73 @@ impl ApiTester {
self
}

pub async fn test_beacon_states_validator_balances(self) -> Self {
for state_id in self.interesting_state_ids() {
for validator_indices in self.interesting_validator_indices() {
let state_opt = self.get_state(state_id);
let validators: Vec<Validator> = match state_opt.as_ref() {
Some(state) => state.validators.clone().into(),
None => vec![],
};
let validator_index_ids = validator_indices
.iter()
.cloned()
.map(|i| ValidatorId::Index(i))
.collect::<Vec<ValidatorId>>();
let validator_pubkey_ids = validator_indices
.iter()
.cloned()
.map(|i| {
ValidatorId::PublicKey(
validators
.get(i as usize)
.map_or(PublicKeyBytes::empty(), |val| val.pubkey.clone()),
)
})
.collect::<Vec<ValidatorId>>();

let result_index_ids = self
.client
.get_beacon_states_validator_balances(
state_id,
Some(validator_index_ids.as_slice()),
)
.await
.unwrap()
.map(|res| res.data);
let result_pubkey_ids = self
.client
.get_beacon_states_validator_balances(
state_id,
Some(validator_pubkey_ids.as_slice()),
)
.await
.unwrap()
.map(|res| res.data);

let expected = state_opt.map(|state| {
let mut validators = Vec::with_capacity(validator_indices.len());

for i in validator_indices {
if i < state.balances.len() as u64 {
validators.push(ValidatorBalanceData {
index: i as u64,
balance: state.balances[i as usize],
});
}
}

validators
});

assert_eq!(result_index_ids, expected, "{:?}", state_id);
assert_eq!(result_pubkey_ids, expected, "{:?}", state_id);
}
}

self
}

pub async fn test_beacon_states_validators(self) -> Self {
for state_id in self.interesting_state_ids() {
for statuses in self.interesting_validator_statuses() {
Expand Down Expand Up @@ -1235,7 +1302,7 @@ impl ApiTester {
if epoch > current_epoch + 1 {
assert_eq!(
self.client
.get_validator_duties_attester(epoch, Some(&indices))
.post_validator_duties_attester(epoch, indices.as_slice())
.await
.unwrap_err()
.status()
Expand All @@ -1247,7 +1314,7 @@ impl ApiTester {

let results = self
.client
.get_validator_duties_attester(epoch, Some(&indices))
.post_validator_duties_attester(epoch, indices.as_slice())
.await
.unwrap()
.data;
Expand Down Expand Up @@ -1336,7 +1403,11 @@ impl ApiTester {
.unwrap();
let pubkey = state.validators[index].pubkey.clone().into();

ProposerData { pubkey, slot }
ProposerData {
pubkey,
validator_index: index as u64,
slot,
}
})
.collect::<Vec<_>>();

Expand Down Expand Up @@ -1473,17 +1544,17 @@ impl ApiTester {
let fork = head.beacon_state.fork;
let genesis_validators_root = self.chain.genesis_validators_root;

let mut duties = vec![];
for i in 0..self.validator_keypairs.len() {
duties.push(
self.client
.get_validator_duties_attester(epoch, Some(&[i as u64]))
.await
.unwrap()
.data[0]
.clone(),
let duties = self
.client
.post_validator_duties_attester(
epoch,
(0..self.validator_keypairs.len() as u64)
.collect::<Vec<u64>>()
.as_slice(),
)
}
.await
.unwrap()
.data;

let (i, kp, duty, proof) = self
.validator_keypairs
Expand Down Expand Up @@ -1554,7 +1625,7 @@ impl ApiTester {
let aggregate = self.get_aggregate().await;

self.client
.post_validator_aggregate_and_proof::<E>(&aggregate)
.post_validator_aggregate_and_proof::<E>(&[aggregate])
.await
.unwrap();

Expand All @@ -1569,7 +1640,7 @@ impl ApiTester {
aggregate.message.aggregate.data.slot += 1;

self.client
.post_validator_aggregate_and_proof::<E>(&aggregate)
.post_validator_aggregate_and_proof::<E>(&[aggregate])
.await
.unwrap_err();

Expand Down Expand Up @@ -1700,6 +1771,8 @@ async fn beacon_get() {
.await
.test_beacon_states_validators()
.await
.test_beacon_states_validator_balances()
.await
.test_beacon_states_committees()
.await
.test_beacon_states_validator_id()
Expand Down
126 changes: 93 additions & 33 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub enum Error {
Reqwest(reqwest::Error),
/// The server returned an error message where the body was able to be parsed.
ServerMessage(ErrorMessage),
/// The server returned an error message with an array of errors.
ServerIndexedMessage(IndexedErrorMessage),
/// The server returned an error message where the body was unable to be parsed.
StatusCode(StatusCode),
/// The supplied URL is badly formatted. It should look something like `http://127.0.0.1:5052`.
Expand All @@ -50,6 +52,7 @@ impl Error {
match self {
Error::Reqwest(error) => error.status(),
Error::ServerMessage(msg) => StatusCode::try_from(msg.code).ok(),
Error::ServerIndexedMessage(msg) => StatusCode::try_from(msg.code).ok(),
Error::StatusCode(status) => Some(*status),
Error::InvalidUrl(_) => None,
Error::InvalidSecret(_) => None,
Expand Down Expand Up @@ -137,6 +140,26 @@ impl BeaconNodeHttpClient {
Ok(())
}

/// Perform a HTTP POST request, returning a JSON response.
async fn post_with_response<T: DeserializeOwned, U: IntoUrl, V: Serialize>(
&self,
url: U,
body: &V,
) -> Result<T, Error> {
let response = self
.client
.post(url)
.json(body)
.send()
.await
.map_err(Error::Reqwest)?;
ok_or_error(response)
.await?
.json()
.await
.map_err(Error::Reqwest)
}

/// `GET beacon/genesis`
///
/// ## Errors
Expand Down Expand Up @@ -210,6 +233,35 @@ impl BeaconNodeHttpClient {
self.get_opt(path).await
}

/// `GET beacon/states/{state_id}/validator_balances?id`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_states_validator_balances(
&self,
state_id: StateId,
ids: Option<&[ValidatorId]>,
) -> Result<Option<GenericResponse<Vec<ValidatorBalanceData>>>, Error> {
let mut path = self.eth_path()?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("beacon")
.push("states")
.push(&state_id.to_string())
.push("validator_balances");

if let Some(ids) = ids {
let id_string = ids
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(",");
path.query_pairs_mut().append_pair("id", &id_string);
}

self.get_opt(path).await
}

/// `GET beacon/states/{state_id}/validators?id,status`
///
/// Returns `Ok(None)` on a 404 error.
Expand Down Expand Up @@ -713,37 +765,6 @@ impl BeaconNodeHttpClient {
self.get(path).await
}

/// `GET validator/duties/attester/{epoch}?index`
///
/// ## Note
///
/// The `index` query parameter accepts a list of validator indices.
pub async fn get_validator_duties_attester(
&self,
epoch: Epoch,
index: Option<&[u64]>,
) -> Result<GenericResponse<Vec<AttesterData>>, Error> {
let mut path = self.eth_path()?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("duties")
.push("attester")
.push(&epoch.to_string());

if let Some(index) = index {
let string = index
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(",");
path.query_pairs_mut().append_pair("index", &string);
}

self.get(path).await
}

/// `GET validator/duties/proposer/{epoch}`
pub async fn get_validator_duties_proposer(
&self,
Expand Down Expand Up @@ -830,10 +851,28 @@ impl BeaconNodeHttpClient {
self.get_opt(path).await
}

/// `POST validator/duties/attester/{epoch}`
pub async fn post_validator_duties_attester(
&self,
epoch: Epoch,
indices: &[u64],
) -> Result<GenericResponse<Vec<AttesterData>>, Error> {
let mut path = self.eth_path()?;

path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("duties")
.push("attester")
.push(&epoch.to_string());

self.post_with_response(path, &indices).await
}

/// `POST validator/aggregate_and_proofs`
pub async fn post_validator_aggregate_and_proof<T: EthSpec>(
&self,
aggregate: &SignedAggregateAndProof<T>,
aggregates: &[SignedAggregateAndProof<T>],
) -> Result<(), Error> {
let mut path = self.eth_path()?;

Expand All @@ -842,7 +881,14 @@ impl BeaconNodeHttpClient {
.push("validator")
.push("aggregate_and_proofs");

self.post(path, aggregate).await?;
let response = self
.client
.post(path)
.json(aggregates)
.send()
.await
.map_err(Error::Reqwest)?;
ok_or_indexed_error(response).await?;

Ok(())
}
Expand Down Expand Up @@ -878,3 +924,17 @@ async fn ok_or_error(response: Response) -> Result<Response, Error> {
Err(Error::StatusCode(status))
}
}

/// Returns `Ok(response)` if the response is a `200 OK` response. Otherwise, creates an
/// appropriate indexed error message.
async fn ok_or_indexed_error(response: Response) -> Result<Response, Error> {
let status = response.status();

if status == StatusCode::OK {
Ok(response)
} else if let Ok(message) = response.json().await {
Err(Error::ServerIndexedMessage(message))
} else {
Err(Error::StatusCode(status))
}
}
Loading

0 comments on commit f8da151

Please sign in to comment.