Merge latest master in v0.2.0

This commit is contained in:
Age Manning
2020-04-08 16:46:37 +10:00
144 changed files with 2603 additions and 1308 deletions

View File

@@ -32,9 +32,12 @@ impl AggregateSignature {
/// Add (aggregate) a signature to the `AggregateSignature`.
pub fn add(&mut self, signature: &Signature) {
if !self.is_empty {
self.aggregate_signature.add(signature.as_raw())
if self.is_empty {
self.aggregate_signature = RawAggregateSignature::new();
self.is_empty = false;
}
self.aggregate_signature.add(signature.as_raw())
}
/// Add (aggregate) another `AggregateSignature`.

View File

@@ -22,6 +22,13 @@ impl FakeAggregatePublicKey {
Self::zero()
}
pub fn empty_signature() -> Self {
Self {
bytes: vec![0; BLS_PUBLIC_KEY_BYTE_SIZE],
point: G1Point::new(),
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
if bytes.len() != BLS_PUBLIC_KEY_BYTE_SIZE {
Err(DecodeError::InvalidByteLength {

View File

@@ -14,4 +14,4 @@ serde_json = "1.0"
types = { path = "../../types"}
eth2_ssz = { path = "../ssz"}
tree_hash = { path = "../tree_hash"}
ethabi = "9.0"
ethabi = "11.0"

View File

@@ -9,9 +9,9 @@ use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
const TAG: &str = "v0.10.1";
const TAG: &str = "v0.11.1";
// NOTE: the version of the unsafe contract lags the main tag, but the v0.9.2.1 code is compatible
// with the unmodified v0.10.1 contract
// with the unmodified v0.11.1 contract
const UNSAFE_TAG: &str = "v0.9.2.1";
fn spec_url() -> String {

View File

@@ -1,23 +1,40 @@
use ethabi::{Contract, Token};
use ssz::Encode;
use ssz::{Decode, DecodeError as SszDecodeError, Encode};
use tree_hash::TreeHash;
use types::DepositData;
use types::{DepositData, Hash256, PublicKeyBytes, SignatureBytes};
pub use ethabi::Error;
#[derive(Debug)]
pub enum DecodeError {
EthabiError(ethabi::Error),
SszDecodeError(SszDecodeError),
MissingField,
UnableToGetBytes,
MissingToken,
InadequateBytes,
}
impl From<ethabi::Error> for DecodeError {
fn from(e: ethabi::Error) -> DecodeError {
DecodeError::EthabiError(e)
}
}
pub const CONTRACT_DEPLOY_GAS: usize = 4_000_000;
pub const DEPOSIT_GAS: usize = 4_000_000;
pub const ABI: &[u8] = include_bytes!("../contracts/v0.10.1_validator_registration.json");
pub const BYTECODE: &[u8] = include_bytes!("../contracts/v0.10.1_validator_registration.bytecode");
pub const ABI: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.json");
pub const BYTECODE: &[u8] = include_bytes!("../contracts/v0.11.1_validator_registration.bytecode");
pub const DEPOSIT_DATA_LEN: usize = 420; // lol
pub mod testnet {
pub const ABI: &[u8] =
include_bytes!("../contracts/v0.10.1_testnet_validator_registration.json");
include_bytes!("../contracts/v0.11.1_testnet_validator_registration.json");
pub const BYTECODE: &[u8] =
include_bytes!("../contracts/v0.10.1_testnet_validator_registration.bytecode");
include_bytes!("../contracts/v0.11.1_testnet_validator_registration.bytecode");
}
pub fn eth1_tx_data(deposit_data: &DepositData) -> Result<Vec<u8>, Error> {
pub fn encode_eth1_tx_data(deposit_data: &DepositData) -> Result<Vec<u8>, Error> {
let params = vec![
Token::Bytes(deposit_data.pubkey.as_ssz_bytes()),
Token::Bytes(deposit_data.withdrawal_credentials.as_ssz_bytes()),
@@ -32,6 +49,40 @@ pub fn eth1_tx_data(deposit_data: &DepositData) -> Result<Vec<u8>, Error> {
function.encode_input(&params)
}
pub fn decode_eth1_tx_data(
bytes: &[u8],
amount: u64,
) -> Result<(DepositData, Hash256), DecodeError> {
let abi = Contract::load(ABI)?;
let function = abi.function("deposit")?;
let mut tokens =
function.decode_input(bytes.get(4..).ok_or_else(|| DecodeError::InadequateBytes)?)?;
macro_rules! decode_token {
($type: ty, $to_fn: ident) => {
<$type>::from_ssz_bytes(
&tokens
.pop()
.ok_or_else(|| DecodeError::MissingToken)?
.$to_fn()
.ok_or_else(|| DecodeError::UnableToGetBytes)?,
)
.map_err(DecodeError::SszDecodeError)?
};
};
let root = decode_token!(Hash256, to_fixed_bytes);
let deposit_data = DepositData {
amount,
signature: decode_token!(SignatureBytes, to_bytes),
withdrawal_credentials: decode_token!(Hash256, to_bytes),
pubkey: decode_token!(PublicKeyBytes, to_bytes),
};
Ok((deposit_data, root))
}
#[cfg(test)]
mod tests {
use super::*;
@@ -54,14 +105,27 @@ mod tests {
}
#[test]
fn basic() {
fn round_trip() {
let spec = &E::default_spec();
let keypair = generate_deterministic_keypair(42);
let deposit = get_deposit(keypair, spec);
let original = get_deposit(keypair, spec);
let data = eth1_tx_data(&deposit).expect("should produce tx data");
let data = encode_eth1_tx_data(&original).expect("should produce tx data");
assert_eq!(data.len(), 420, "bytes should be correct length");
assert_eq!(
data.len(),
DEPOSIT_DATA_LEN,
"bytes should be correct length"
);
let (decoded, root) = decode_eth1_tx_data(&data, original.amount).expect("should decode");
assert_eq!(decoded, original, "decoded should match original");
assert_eq!(
root,
original.tree_hash_root(),
"decode root should match original root"
);
}
}

View File

@@ -8,23 +8,29 @@ use std::path::PathBuf;
const TESTNET_ID: &str = "testnet5";
fn main() {
match get_all_files() {
Ok(()) => (),
Err(e) => panic!(e),
if !base_dir().exists() {
std::fs::create_dir_all(base_dir()).expect(&format!("Unable to create {:?}", base_dir()));
match get_all_files() {
Ok(()) => (),
Err(e) => {
std::fs::remove_dir_all(base_dir()).expect(&format!(
"{}. Failed to remove {:?}, please remove the directory manually because it may contains incomplete testnet data.",
e,
base_dir(),
));
panic!(e);
}
}
}
}
pub fn get_all_files() -> Result<(), String> {
if !base_dir().exists() {
std::fs::create_dir_all(base_dir())
.map_err(|e| format!("Unable to create {:?}: {}", base_dir(), e))?;
get_file("boot_enr.yaml")?;
get_file("config.yaml")?;
get_file("deploy_block.txt")?;
get_file("deposit_contract.txt")?;
get_file("genesis.ssz")?;
}
get_file("boot_enr.yaml")?;
get_file("config.yaml")?;
get_file("deploy_block.txt")?;
get_file("deposit_contract.txt")?;
get_file("genesis.ssz")?;
Ok(())
}

View File

@@ -156,11 +156,11 @@ impl<E: EthSpec> Eth2TestnetConfig<E> {
let yaml_config = optional_load_from_file!(YAML_CONFIG_FILE);
// The genesis state is a special case because it uses SSZ, not YAML.
let file = base_dir.join(GENESIS_STATE_FILE);
let genesis_state = if base_dir.join(&file).exists() {
let genesis_file_path = base_dir.join(GENESIS_STATE_FILE);
let genesis_state = if genesis_file_path.exists() {
Some(
File::open(base_dir.join(&file))
.map_err(|e| format!("Unable to open {:?}: {:?}", file, e))
File::open(&genesis_file_path)
.map_err(|e| format!("Unable to open {:?}: {:?}", genesis_file_path, e))
.and_then(|mut file| {
let mut bytes = vec![];
file.read_to_end(&mut bytes)
@@ -202,6 +202,7 @@ mod tests {
type E = MainnetEthSpec;
/* TODO: disabled until testnet config is updated for v0.11
#[test]
fn hard_coded_works() {
let dir: Eth2TestnetConfig<E> =
@@ -211,6 +212,7 @@ mod tests {
assert!(dir.genesis_state.is_some());
assert!(dir.yaml_config.is_some());
}
*/
#[test]
fn round_trip() {

View File

@@ -32,10 +32,8 @@ pub fn int_to_bytes3(int: u32) -> Option<Vec<u8>> {
}
/// Returns `int` as little-endian bytes with a length of 4.
pub fn int_to_bytes4(int: u32) -> Vec<u8> {
let mut bytes = BytesMut::with_capacity(4);
bytes.put_u32_le(int);
bytes.to_vec()
pub fn int_to_bytes4(int: u32) -> [u8; 4] {
int.to_le_bytes()
}
/// Returns `int` as little-endian bytes with a length of 8.
@@ -128,7 +126,7 @@ mod tests {
1 => assert_eq!(int_to_bytes1(int as u8), bytes),
2 => assert_eq!(int_to_bytes2(int as u16), bytes),
3 => assert_eq!(int_to_bytes3(int as u32), Some(bytes)),
4 => assert_eq!(int_to_bytes4(int as u32), bytes),
4 => assert_eq!(&int_to_bytes4(int as u32)[..], &bytes[..]),
8 => assert_eq!(int_to_bytes8(int), bytes),
32 => assert_eq!(int_to_bytes32(int), bytes),
48 => assert_eq!(int_to_bytes48(int), bytes),

View File

@@ -15,16 +15,17 @@ use std::marker::PhantomData;
use std::time::Duration;
use types::{
Attestation, AttestationData, AttesterSlashing, BeaconBlock, BeaconState, CommitteeIndex,
Epoch, EthSpec, Fork, Hash256, ProposerSlashing, PublicKey, Signature, SignedAggregateAndProof,
SignedBeaconBlock, Slot,
Epoch, EthSpec, Fork, Hash256, ProposerSlashing, PublicKey, PublicKeyBytes, Signature,
SignedAggregateAndProof, SignedBeaconBlock, Slot,
};
use url::Url;
pub use operation_pool::PersistedOperationPool;
pub use proto_array_fork_choice::core::ProtoArray;
pub use rest_types::{
CanonicalHeadResponse, Committee, HeadBeaconBlock, ValidatorDutiesRequest, ValidatorDutyBytes,
ValidatorRequest, ValidatorResponse, ValidatorSubscription,
CanonicalHeadResponse, Committee, HeadBeaconBlock, IndividualVotesRequest,
IndividualVotesResponse, ValidatorDutiesRequest, ValidatorDutyBytes, ValidatorRequest,
ValidatorResponse, ValidatorSubscription,
};
// Setting a long timeout for debug ensures that crypto-heavy operations can still succeed.
@@ -110,6 +111,10 @@ impl<E: EthSpec> HttpClient<E> {
Advanced(self.clone())
}
pub fn consensus(&self) -> Consensus<E> {
Consensus(self.clone())
}
fn url(&self, path: &str) -> Result<Url, Error> {
self.url.join(path).map_err(|e| e.into())
}
@@ -388,6 +393,14 @@ impl<E: EthSpec> Beacon<E> {
.and_then(move |url| client.json_get(url, vec![]))
}
/// Returns the genesis validators root.
pub fn get_genesis_validators_root(&self) -> impl Future<Item = Hash256, Error = Error> {
let client = self.0.clone();
self.url("genesis_validators_root")
.into_future()
.and_then(move |url| client.json_get(url, vec![]))
}
/// Returns the fork at the head of the beacon chain.
pub fn get_fork(&self) -> impl Future<Item = Fork, Error = Error> {
let client = self.0.clone();
@@ -674,6 +687,47 @@ impl<E: EthSpec> Advanced<E> {
}
}
/// Provides the functions on the `/consensus` endpoint of the node.
#[derive(Clone)]
pub struct Consensus<E>(HttpClient<E>);
impl<E: EthSpec> Consensus<E> {
fn url(&self, path: &str) -> Result<Url, Error> {
self.0
.url("consensus/")
.and_then(move |url| url.join(path).map_err(Error::from))
.map_err(Into::into)
}
/// Gets a `IndividualVote` for each of the given `pubkeys`.
pub fn get_individual_votes(
&self,
epoch: Epoch,
pubkeys: Vec<PublicKeyBytes>,
) -> impl Future<Item = IndividualVotesResponse, Error = Error> {
let client = self.0.clone();
let req_body = IndividualVotesRequest { epoch, pubkeys };
self.url("individual_votes")
.into_future()
.and_then(move |url| client.json_post::<_>(url, req_body))
.and_then(|response| error_for_status(response).map_err(Error::from))
.and_then(|mut success| success.json().map_err(Error::from))
}
/// Gets a `VoteCount` for the given `epoch`.
pub fn get_vote_count(
&self,
epoch: Epoch,
) -> impl Future<Item = IndividualVotesResponse, Error = Error> {
let client = self.0.clone();
let query_params = vec![("epoch".into(), format!("{}", epoch.as_u64()))];
self.url("vote_count")
.into_future()
.and_then(move |url| client.json_get(url, query_params))
}
}
#[derive(Deserialize)]
#[serde(bound = "T: EthSpec")]
pub struct BlockResponse<T: EthSpec> {

View File

@@ -10,6 +10,7 @@ eth2_ssz_derive = { path = "../ssz_derive" }
eth2_ssz = { path = "../ssz" }
eth2_hashing = { path = "../eth2_hashing" }
tree_hash = { path = "../tree_hash" }
state_processing = { path = "../../state_processing" }
bls = { path = "../bls" }
serde = { version = "1.0.102", features = ["derive"] }
rayon = "1.3.0"

View File

@@ -0,0 +1,66 @@
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use state_processing::per_epoch_processing::ValidatorStatus;
use types::{Epoch, PublicKeyBytes};
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)]
pub struct IndividualVotesRequest {
pub epoch: Epoch,
pub pubkeys: Vec<PublicKeyBytes>,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)]
pub struct IndividualVote {
/// True if the validator has been slashed, ever.
pub is_slashed: bool,
/// True if the validator can withdraw in the current epoch.
pub is_withdrawable_in_current_epoch: bool,
/// True if the validator was active in the state's _current_ epoch.
pub is_active_in_current_epoch: bool,
/// True if the validator was active in the state's _previous_ epoch.
pub is_active_in_previous_epoch: bool,
/// The validator's effective balance in the _current_ epoch.
pub current_epoch_effective_balance_gwei: u64,
/// True if the validator had an attestation included in the _current_ epoch.
pub is_current_epoch_attester: bool,
/// True if the validator's beacon block root attestation for the first slot of the _current_
/// epoch matches the block root known to the state.
pub is_current_epoch_target_attester: bool,
/// True if the validator had an attestation included in the _previous_ epoch.
pub is_previous_epoch_attester: bool,
/// True if the validator's beacon block root attestation for the first slot of the _previous_
/// epoch matches the block root known to the state.
pub is_previous_epoch_target_attester: bool,
/// True if the validator's beacon block root attestation in the _previous_ epoch at the
/// attestation's slot (`attestation_data.slot`) matches the block root known to the state.
pub is_previous_epoch_head_attester: bool,
}
impl Into<IndividualVote> for ValidatorStatus {
fn into(self) -> IndividualVote {
IndividualVote {
is_slashed: self.is_slashed,
is_withdrawable_in_current_epoch: self.is_withdrawable_in_current_epoch,
is_active_in_current_epoch: self.is_active_in_current_epoch,
is_active_in_previous_epoch: self.is_active_in_previous_epoch,
current_epoch_effective_balance_gwei: self.current_epoch_effective_balance,
is_current_epoch_attester: self.is_current_epoch_attester,
is_current_epoch_target_attester: self.is_current_epoch_target_attester,
is_previous_epoch_attester: self.is_previous_epoch_attester,
is_previous_epoch_target_attester: self.is_previous_epoch_target_attester,
is_previous_epoch_head_attester: self.is_previous_epoch_head_attester,
}
}
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)]
pub struct IndividualVotesResponse {
/// The epoch which is considered the "current" epoch.
pub epoch: Epoch,
/// The validators public key.
pub pubkey: PublicKeyBytes,
/// The index of the validator in state.validators.
pub validator_index: Option<usize>,
/// Voting statistics for the validator, if they voted in the given epoch.
pub vote: Option<IndividualVote>,
}

View File

@@ -3,6 +3,7 @@
//! This is primarily used by the validator client and the beacon node rest API.
mod beacon;
mod consensus;
mod validator;
pub use beacon::{
@@ -13,3 +14,5 @@ pub use beacon::{
pub use validator::{
ValidatorDutiesRequest, ValidatorDuty, ValidatorDutyBytes, ValidatorSubscription,
};
pub use consensus::{IndividualVote, IndividualVotesRequest, IndividualVotesResponse};

View File

@@ -2,8 +2,8 @@
//! format designed for use in Ethereum 2.0.
//!
//! Adheres to the Ethereum 2.0 [SSZ
//! specification](https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/ssz/simple-serialize.md)
//! at v0.10.1.
//! specification](https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/ssz/simple-serialize.md)
//! at v0.11.1.
//!
//! ## Example
//!

View File

@@ -9,8 +9,8 @@
//! for padding and verification.
//!
//! Adheres to the Ethereum 2.0 [SSZ
//! specification](https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/ssz/simple-serialize.md)
//! at v0.10.1.
//! specification](https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/ssz/simple-serialize.md)
//! at v0.11.1.
//!
//! ## Example
//! ```

View File

@@ -1,7 +1,7 @@
//! Provides list-shuffling functions matching the Ethereum 2.0 specification.
//!
//! See
//! [compute_shuffled_index](https://github.com/ethereum/eth2.0-specs/blob/v0.10.1/specs/phase0/beacon-chain.md#compute_shuffled_index)
//! [compute_shuffled_index](https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#compute_shuffled_index)
//! for specifications.
//!
//! There are two functions exported by this crate:

View File

@@ -54,7 +54,7 @@ fn bench_suite<T: EthSpec>(c: &mut Criterion, spec_desc: &str, validator_count:
b.iter_batched_ref(
|| state2.clone(),
|state| {
assert!(!state.tree_hash_cache.is_initialized());
assert!(state.tree_hash_cache.is_none());
black_box(state.update_tree_hash_cache().unwrap())
},
criterion::BatchSize::SmallInput,
@@ -72,7 +72,7 @@ fn bench_suite<T: EthSpec>(c: &mut Criterion, spec_desc: &str, validator_count:
b.iter_batched_ref(
|| state3.clone(),
|state| {
assert!(state.tree_hash_cache.is_initialized());
assert!(state.tree_hash_cache.is_some());
black_box(state.update_tree_hash_cache().unwrap())
},
criterion::BatchSize::SmallInput,