diff --git a/Cargo.lock b/Cargo.lock index 5da3ad0dd..1d3e53533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,12 @@ dependencies = [ "syn", ] +[[package]] +name = "assert_matches" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5" + [[package]] name = "async-tls" version = "0.7.0" @@ -3689,6 +3695,7 @@ dependencies = [ name = "rest_api" version = "0.1.2" dependencies = [ + "assert_matches", "beacon_chain", "bls", "eth2-libp2p", diff --git a/beacon_node/rest_api/Cargo.toml b/beacon_node/rest_api/Cargo.toml index e2dddb056..382f1c30c 100644 --- a/beacon_node/rest_api/Cargo.toml +++ b/beacon_node/rest_api/Cargo.toml @@ -38,6 +38,7 @@ operation_pool = { path = "../operation_pool" } rayon = "1.3.0" [dev-dependencies] +assert_matches = "1.3.0" remote_beacon_node = { path = "../../common/remote_beacon_node" } node_test_rig = { path = "../../testing/node_test_rig" } tree_hash = "0.1.0" diff --git a/beacon_node/rest_api/tests/test.rs b/beacon_node/rest_api/tests/test.rs index 8524bcf5d..de83d90d6 100644 --- a/beacon_node/rest_api/tests/test.rs +++ b/beacon_node/rest_api/tests/test.rs @@ -1,5 +1,8 @@ #![cfg(test)] +#[macro_use] +extern crate assert_matches; + use beacon_chain::{BeaconChain, BeaconChainTypes, StateSkipConfig}; use node_test_rig::{ environment::{Environment, EnvironmentBuilder}, @@ -1135,3 +1138,117 @@ fn attester_slashing() { assert_eq!(attester_slashings.len(), 1); assert_eq!(attester_slashing, attester_slashings[0]); } + +mod validator_attestation { + use super::*; + use http::StatusCode; + use node_test_rig::environment::Environment; + use remote_beacon_node::{Error::DidNotSucceed, HttpClient}; + use types::{Attestation, AttestationDuty, MinimalEthSpec}; + use url::Url; + + fn setup() -> ( + Environment, + LocalBeaconNode, + HttpClient, + Url, + AttestationDuty, + ) { + let mut env = build_env(); + let node = build_node(&mut env, testing_client_config()); + let remote_node = node.remote_node().expect("should produce remote node"); + let client = remote_node.http.clone(); + let socket_addr = node + .client + .http_listen_addr() + .expect("A remote beacon node must have a http server"); + let url = Url::parse(&format!( + "http://{}:{}/validator/attestation", + socket_addr.ip(), + socket_addr.port() + )) + .expect("should be valid endpoint"); + + // Find a validator that has duties in the current slot of the chain. + let mut validator_index = 0; + let beacon_chain = node + .client + .beacon_chain() + .expect("client should have beacon chain"); + let state = beacon_chain.head().expect("should get head").beacon_state; + let duties = loop { + let duties = state + .get_attestation_duties(validator_index, RelativeEpoch::Current) + .expect("should have attestation duties cache") + .expect("should have attestation duties"); + + if duties.slot == node.client.beacon_chain().unwrap().slot().unwrap() { + break duties; + } else { + validator_index += 1 + } + }; + + (env, node, client, url, duties) + } + + #[test] + fn requires_query_parameters() { + let (mut env, _node, client, url, _duties) = setup(); + + let attestation = env.runtime().block_on( + // query parameters are missing + client.json_get::>(url.clone(), vec![]), + ); + + assert_matches!( + attestation.expect_err("should not succeed"), + DidNotSucceed { status, body } => { + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(body, "URL query must be valid and contain at least one of the following keys: [\"slot\"]".to_owned()); + } + ); + } + + #[test] + fn requires_slot() { + let (mut env, _node, client, url, duties) = setup(); + + let attestation = env.runtime().block_on( + // `slot` is missing + client.json_get::>( + url.clone(), + vec![("committee_index".into(), format!("{}", duties.index))], + ), + ); + + assert_matches!( + attestation.expect_err("should not succeed"), + DidNotSucceed { status, body } => { + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(body, "URL query must be valid and contain at least one of the following keys: [\"slot\"]".to_owned()); + } + ); + } + + #[test] + fn requires_committee_index() { + let (mut env, _node, client, url, duties) = setup(); + + let attestation = env.runtime().block_on( + // `committee_index` is missing. + client.json_get::>( + url.clone(), + vec![("slot".into(), format!("{}", duties.slot))], + ), + ); + + assert_matches!( + attestation.expect_err("should not succeed"), + DidNotSucceed { status, body } => { + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!(body, "URL query must be valid and contain at least one of the following keys: [\"committee_index\"]".to_owned()); + } + ); + } +}