Merge branch 'ef-tests' into v0.6.1

This commit is contained in:
Paul Hauner 2019-05-22 14:46:10 +10:00
commit 1eeaaaa92b
No known key found for this signature in database
GPG Key ID: 5E2CFF9B75FA63DF
36 changed files with 1457 additions and 62 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "tests/ef_tests/eth2.0-spec-tests"]
path = tests/ef_tests/eth2.0-spec-tests
url = https://github.com/ethereum/eth2.0-spec-tests

View File

@ -29,6 +29,7 @@ members = [
"beacon_node/rpc",
"beacon_node/version",
"beacon_node/beacon_chain",
"tests/ef_tests",
"protos",
"validator_client",
"account_manager",

34
Jenkinsfile vendored
View File

@ -1,34 +0,0 @@
pipeline {
agent {
dockerfile {
filename 'Dockerfile'
args '-v cargo-cache:/cache/cargocache:rw -e "CARGO_HOME=/cache/cargocache"'
}
}
stages {
stage('Build') {
steps {
sh 'cargo build --verbose --all'
sh 'cargo build --verbose --all --release'
}
}
stage('Check') {
steps {
sh 'cargo fmt --all -- --check'
// No clippy until later...
//sh 'cargo clippy'
}
}
stage('Test') {
steps {
sh 'cargo test --verbose --all'
sh 'cargo test --verbose --all --release'
sh 'cargo test --manifest-path eth2/state_processing/Cargo.toml --verbose \
--release --features fake_crypto'
sh 'cargo test --manifest-path eth2/state_processing/Cargo.toml --verbose \
--release --features fake_crypto -- --ignored'
}
}
}
}

View File

@ -1,4 +1,4 @@
use crate::test_utils::TestRandom;
use crate::test_utils::{graffiti_from_hex_str, TestRandom};
use crate::*;
use serde_derive::{Deserialize, Serialize};
@ -24,6 +24,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
pub struct BeaconBlockBody {
pub randao_reveal: Signature,
pub eth1_data: Eth1Data,
#[serde(deserialize_with = "graffiti_from_hex_str")]
pub graffiti: [u8; 32],
pub proposer_slashings: Vec<ProposerSlashing>,
pub attester_slashings: Vec<AttesterSlashing>,

View File

@ -81,7 +81,7 @@ pub type AttesterMap = HashMap<(u64, u64), Vec<usize>>;
pub type ProposerMap = HashMap<u64, usize>;
pub use bls::{AggregatePublicKey, AggregateSignature, Keypair, PublicKey, SecretKey, Signature};
pub use fixed_len_vec::{typenum::Unsigned, FixedLenVec};
pub use fixed_len_vec::{typenum, typenum::Unsigned, FixedLenVec};
pub use libp2p::floodsub::{Topic, TopicBuilder, TopicHash};
pub use libp2p::multiaddr;
pub use libp2p::Multiaddr;

View File

@ -14,5 +14,5 @@ pub use rand::{
RngCore,
{prng::XorShiftRng, SeedableRng},
};
pub use serde_utils::{fork_from_hex_str, u8_from_hex_str};
pub use serde_utils::{fork_from_hex_str, graffiti_from_hex_str, u8_from_hex_str};
pub use test_random::TestRandom;

View File

@ -2,6 +2,7 @@ use serde::de::Error;
use serde::{Deserialize, Deserializer};
pub const FORK_BYTES_LEN: usize = 4;
pub const GRAFFITI_BYTES_LEN: usize = 32;
pub fn u8_from_hex_str<'de, D>(deserializer: D) -> Result<u8, D::Error>
where
@ -32,3 +33,24 @@ where
}
Ok(array)
}
pub fn graffiti_from_hex_str<'de, D>(deserializer: D) -> Result<[u8; GRAFFITI_BYTES_LEN], D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
let mut array = [0 as u8; GRAFFITI_BYTES_LEN];
let decoded: Vec<u8> = hex::decode(&s.as_str()[2..]).map_err(D::Error::custom)?;
if decoded.len() > GRAFFITI_BYTES_LEN {
return Err(D::Error::custom("Fork length too long"));
}
for (i, item) in array.iter_mut().enumerate() {
if i > decoded.len() {
break;
}
*item = decoded[i];
}
Ok(array)
}

View File

@ -5,10 +5,11 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "0.6.1" }
bls-aggregates = { git = "https://github.com/sigp/signature-schemes", branch = "secret-key-serialization" }
cached_tree_hash = { path = "../cached_tree_hash" }
hashing = { path = "../hashing" }
hex = "0.3"
rand = "0.5"
serde = "1.0"
serde_derive = "1.0"
serde_hex = { path = "../serde_hex" }

View File

@ -0,0 +1,36 @@
use super::{PublicKey, BLS_PUBLIC_KEY_BYTE_SIZE};
use bls_aggregates::AggregatePublicKey as RawAggregatePublicKey;
/// A BLS aggregate public key.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, Clone, Default)]
pub struct FakeAggregatePublicKey {
bytes: Vec<u8>,
}
impl FakeAggregatePublicKey {
pub fn new() -> Self {
Self::zero()
}
/// Creates a new all-zero's aggregate public key
pub fn zero() -> Self {
Self {
bytes: vec![0; BLS_PUBLIC_KEY_BYTE_SIZE],
}
}
pub fn add(&mut self, _public_key: &PublicKey) {
// No nothing.
}
pub fn as_raw(&self) -> &FakeAggregatePublicKey {
&self
}
pub fn as_bytes(&self) -> Vec<u8> {
self.bytes.clone()
}
}

View File

@ -1,4 +1,7 @@
use super::{fake_signature::FakeSignature, AggregatePublicKey, BLS_AGG_SIG_BYTE_SIZE};
use super::{
fake_aggregate_public_key::FakeAggregatePublicKey, fake_signature::FakeSignature,
BLS_AGG_SIG_BYTE_SIZE,
};
use cached_tree_hash::cached_tree_hash_ssz_encoding_as_vector;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
@ -43,7 +46,7 @@ impl FakeAggregateSignature {
&self,
_msg: &[u8],
_domain: u64,
_aggregate_public_key: &AggregatePublicKey,
_aggregate_public_key: &FakeAggregatePublicKey,
) -> bool {
true
}
@ -53,7 +56,7 @@ impl FakeAggregateSignature {
&self,
_messages: &[&[u8]],
_domain: u64,
_aggregate_public_keys: &[&AggregatePublicKey],
_aggregate_public_keys: &[&FakeAggregatePublicKey],
) -> bool {
true
}

View File

@ -0,0 +1,169 @@
use super::{SecretKey, BLS_PUBLIC_KEY_BYTE_SIZE};
use bls_aggregates::PublicKey as RawPublicKey;
use cached_tree_hash::cached_tree_hash_ssz_encoding_as_vector;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use serde_hex::{encode as hex_encode, HexVisitor};
use ssz::{ssz_encode, Decode, DecodeError};
use std::default;
use std::fmt;
use std::hash::{Hash, Hasher};
use tree_hash::tree_hash_ssz_encoding_as_vector;
/// A single BLS signature.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, Clone, Eq)]
pub struct FakePublicKey {
bytes: Vec<u8>,
}
impl FakePublicKey {
pub fn from_secret_key(_secret_key: &SecretKey) -> Self {
Self::zero()
}
/// Creates a new all-zero's public key
pub fn zero() -> Self {
Self {
bytes: vec![0; BLS_PUBLIC_KEY_BYTE_SIZE],
}
}
/// Returns the underlying point as compressed bytes.
///
/// Identical to `self.as_uncompressed_bytes()`.
pub fn as_bytes(&self) -> Vec<u8> {
self.bytes.clone()
}
/// Converts compressed bytes to FakePublicKey
pub fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
Ok(Self {
bytes: bytes.to_vec(),
})
}
/// Returns the FakePublicKey as (x, y) bytes
pub fn as_uncompressed_bytes(&self) -> Vec<u8> {
self.as_bytes()
}
/// Converts (x, y) bytes to FakePublicKey
pub fn from_uncompressed_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
Self::from_bytes(bytes)
}
/// Returns the last 6 bytes of the SSZ encoding of the public key, as a hex string.
///
/// Useful for providing a short identifier to the user.
pub fn concatenated_hex_id(&self) -> String {
let bytes = ssz_encode(self);
let end_bytes = &bytes[bytes.len().saturating_sub(6)..bytes.len()];
hex_encode(end_bytes)
}
// Returns itself
pub fn as_raw(&self) -> &Self {
self
}
}
impl fmt::Display for FakePublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.concatenated_hex_id())
}
}
impl default::Default for FakePublicKey {
fn default() -> Self {
let secret_key = SecretKey::random();
FakePublicKey::from_secret_key(&secret_key)
}
}
impl_ssz!(FakePublicKey, BLS_PUBLIC_KEY_BYTE_SIZE, "FakePublicKey");
impl Serialize for FakePublicKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&hex_encode(self.as_bytes()))
}
}
impl<'de> Deserialize<'de> for FakePublicKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = deserializer.deserialize_str(HexVisitor)?;
let pubkey = Self::from_ssz_bytes(&bytes[..])
.map_err(|e| serde::de::Error::custom(format!("invalid pubkey ({:?})", e)))?;
Ok(pubkey)
}
}
tree_hash_ssz_encoding_as_vector!(FakePublicKey);
cached_tree_hash_ssz_encoding_as_vector!(FakePublicKey, 48);
impl PartialEq for FakePublicKey {
fn eq(&self, other: &FakePublicKey) -> bool {
ssz_encode(self) == ssz_encode(other)
}
}
impl Hash for FakePublicKey {
/// Note: this is distinct from consensus serialization, it will produce a different hash.
///
/// This method uses the uncompressed bytes, which are much faster to obtain than the
/// compressed bytes required for consensus serialization.
///
/// Use `ssz::Encode` to obtain the bytes required for consensus hashing.
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_uncompressed_bytes().hash(state)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ssz::ssz_encode;
use tree_hash::TreeHash;
#[test]
pub fn test_ssz_round_trip() {
let sk = SecretKey::random();
let original = FakePublicKey::from_secret_key(&sk);
let bytes = ssz_encode(&original);
let decoded = FakePublicKey::from_ssz_bytes(&bytes).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_cached_tree_hash() {
let sk = SecretKey::random();
let original = FakePublicKey::from_secret_key(&sk);
let mut cache = cached_tree_hash::TreeHashCache::new(&original).unwrap();
assert_eq!(
cache.tree_hash_root().unwrap().to_vec(),
original.tree_hash_root()
);
let sk = SecretKey::random();
let modified = FakePublicKey::from_secret_key(&sk);
cache.update(&modified).unwrap();
assert_eq!(
cache.tree_hash_root().unwrap().to_vec(),
modified.tree_hash_root()
);
}
}

View File

@ -3,33 +3,50 @@ extern crate ssz;
#[macro_use]
mod macros;
mod aggregate_public_key;
mod keypair;
mod public_key;
mod secret_key;
#[cfg(not(feature = "fake_crypto"))]
mod aggregate_signature;
#[cfg(not(feature = "fake_crypto"))]
mod signature;
#[cfg(not(feature = "fake_crypto"))]
pub use crate::aggregate_signature::AggregateSignature;
#[cfg(not(feature = "fake_crypto"))]
pub use crate::signature::Signature;
pub use crate::keypair::Keypair;
pub use crate::secret_key::SecretKey;
pub use bls_aggregates::{compress_g2, hash_on_g2};
#[cfg(feature = "fake_crypto")]
mod fake_aggregate_public_key;
#[cfg(feature = "fake_crypto")]
mod fake_aggregate_signature;
#[cfg(feature = "fake_crypto")]
mod fake_public_key;
#[cfg(feature = "fake_crypto")]
mod fake_signature;
#[cfg(feature = "fake_crypto")]
pub use crate::fake_aggregate_signature::FakeAggregateSignature as AggregateSignature;
#[cfg(feature = "fake_crypto")]
pub use crate::fake_signature::FakeSignature as Signature;
pub use crate::aggregate_public_key::AggregatePublicKey;
pub use crate::keypair::Keypair;
pub use crate::public_key::PublicKey;
pub use crate::secret_key::SecretKey;
#[cfg(not(feature = "fake_crypto"))]
mod aggregate_public_key;
#[cfg(not(feature = "fake_crypto"))]
mod aggregate_signature;
#[cfg(not(feature = "fake_crypto"))]
mod public_key;
#[cfg(not(feature = "fake_crypto"))]
mod signature;
#[cfg(feature = "fake_crypto")]
pub use fakes::*;
#[cfg(feature = "fake_crypto")]
mod fakes {
pub use crate::fake_aggregate_public_key::FakeAggregatePublicKey as AggregatePublicKey;
pub use crate::fake_aggregate_signature::FakeAggregateSignature as AggregateSignature;
pub use crate::fake_public_key::FakePublicKey as PublicKey;
pub use crate::fake_signature::FakeSignature as Signature;
}
#[cfg(not(feature = "fake_crypto"))]
pub use reals::*;
#[cfg(not(feature = "fake_crypto"))]
mod reals {
pub use crate::aggregate_public_key::AggregatePublicKey;
pub use crate::aggregate_signature::AggregateSignature;
pub use crate::public_key::PublicKey;
pub use crate::signature::Signature;
}
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96;
pub const BLS_SIG_BYTE_SIZE: usize = 96;

View File

@ -1,3 +1,5 @@
extern crate rand;
use super::BLS_SECRET_KEY_BYTE_SIZE;
use bls_aggregates::SecretKey as RawSecretKey;
use hex::encode as hex_encode;
@ -16,7 +18,7 @@ pub struct SecretKey(RawSecretKey);
impl SecretKey {
pub fn random() -> Self {
SecretKey(RawSecretKey::random())
SecretKey(RawSecretKey::random(&mut rand::thread_rng()))
}
/// Returns the underlying point as compressed bytes.

View File

@ -9,6 +9,7 @@ pub use typenum;
mod impls;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct FixedLenVec<T, N> {
vec: Vec<T>,
_phantom: PhantomData<N>,

View File

@ -1,5 +1,5 @@
use super::*;
use ethereum_types::H256;
use ethereum_types::{H256, U128, U256};
macro_rules! impl_decodable_for_uint {
($type: ident, $bit_size: expr) => {
@ -85,6 +85,48 @@ impl Decode for H256 {
}
}
impl Decode for U256 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
32
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let len = bytes.len();
let expected = <Self as Decode>::ssz_fixed_len();
if len != expected {
Err(DecodeError::InvalidByteLength { len, expected })
} else {
Ok(U256::from_little_endian(bytes))
}
}
}
impl Decode for U128 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
16
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let len = bytes.len();
let expected = <Self as Decode>::ssz_fixed_len();
if len != expected {
Err(DecodeError::InvalidByteLength { len, expected })
} else {
Ok(U128::from_little_endian(bytes))
}
}
}
macro_rules! impl_decodable_for_u8_array {
($len: expr) => {
impl Decode for [u8; $len] {

View File

@ -1,5 +1,5 @@
use super::*;
use ethereum_types::H256;
use ethereum_types::{H256, U128, U256};
macro_rules! impl_encodable_for_uint {
($type: ident, $bit_size: expr) => {
@ -77,6 +77,42 @@ impl Encode for H256 {
}
}
impl Encode for U256 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
32
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
let n = <Self as Encode>::ssz_fixed_len();
let s = buf.len();
buf.resize(s + n, 0);
self.to_little_endian(&mut buf[s..]);
}
}
impl Encode for U128 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
16
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
let n = <Self as Encode>::ssz_fixed_len();
let s = buf.len();
buf.resize(s + n, 0);
self.to_little_endian(&mut buf[s..]);
}
}
macro_rules! impl_encodable_for_u8_array {
($len: expr) => {
impl Encode for [u8; $len] {

23
tests/ef_tests/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "ef_tests"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[features]
fake_crypto = ["bls/fake_crypto"]
[dependencies]
bls = { path = "../../eth2/utils/bls" }
ethereum-types = "0.5"
hex = "0.3"
rayon = "1.0"
serde = "1.0"
serde_derive = "1.0"
serde_yaml = "0.8"
ssz = { path = "../../eth2/utils/ssz" }
tree_hash = { path = "../../eth2/utils/tree_hash" }
cached_tree_hash = { path = "../../eth2/utils/cached_tree_hash" }
types = { path = "../../eth2/types" }
walkdir = "2"
yaml-rust = { git = "https://github.com/sigp/yaml-rust", branch = "escape_all_str"}

@ -0,0 +1 @@
Subproject commit 161a36ee6232d8d251d798c8262638ed0c34c9c6

View File

@ -0,0 +1,52 @@
use super::*;
use std::fmt::Debug;
#[derive(Debug, PartialEq, Clone)]
pub struct CaseResult {
pub case_index: usize,
pub desc: String,
pub result: Result<(), Error>,
}
impl CaseResult {
pub fn new<T: Debug>(case_index: usize, case: &T, result: Result<(), Error>) -> Self {
CaseResult {
case_index,
desc: format!("{:?}", case),
result,
}
}
}
/// Compares `result` with `expected`.
///
/// If `expected.is_none()` then `result` is expected to be `Err`. Otherwise, `T` in `result` and
/// `expected` must be equal.
pub fn compare_result<T, E>(result: &Result<T, E>, expected: &Option<T>) -> Result<(), Error>
where
T: PartialEq<T> + Debug,
E: Debug,
{
match (result, expected) {
// Pass: The should have failed and did fail.
(Err(_), None) => Ok(()),
// Fail: The test failed when it should have produced a result (fail).
(Err(e), Some(expected)) => Err(Error::NotEqual(format!(
"Got {:?} Expected {:?}",
e, expected
))),
// Fail: The test produced a result when it should have failed (fail).
(Ok(result), None) => Err(Error::DidntFail(format!("Got {:?}", result))),
// Potential Pass: The test should have produced a result, and it did.
(Ok(result), Some(expected)) => {
if result == expected {
Ok(())
} else {
Err(Error::NotEqual(format!(
"Got {:?} expected {:?}",
result, expected
)))
}
}
}
}

View File

@ -0,0 +1,59 @@
use super::*;
use crate::yaml_decode::*;
use yaml_rust::YamlLoader;
mod bls_aggregate_pubkeys;
mod bls_aggregate_sigs;
mod bls_g2_compressed;
mod bls_g2_uncompressed;
mod bls_priv_to_pub;
mod bls_sign_msg;
mod ssz_generic;
mod ssz_static;
pub use bls_aggregate_pubkeys::*;
pub use bls_aggregate_sigs::*;
pub use bls_g2_compressed::*;
pub use bls_g2_uncompressed::*;
pub use bls_priv_to_pub::*;
pub use bls_sign_msg::*;
pub use ssz_generic::*;
pub use ssz_static::*;
#[derive(Debug)]
pub struct Cases<T> {
pub test_cases: Vec<T>,
}
impl<T: YamlDecode> YamlDecode for Cases<T> {
/// Decodes a YAML list of test cases
fn yaml_decode(yaml: &String) -> Result<Self, Error> {
let mut p = 0;
let mut elems: Vec<&str> = yaml
.match_indices("\n- ")
// Skip the `\n` used for matching a new line
.map(|(i, _)| i + 1)
.map(|i| {
let yaml_element = &yaml[p..i];
p = i;
yaml_element
})
.collect();
elems.push(&yaml[p..]);
let test_cases = elems
.iter()
.map(|s| {
// Remove the `- ` prefix.
let s = &s[2..];
// Remove a single level of indenting.
s.replace("\n ", "\n")
})
.map(|s| T::yaml_decode(&s.to_string()).unwrap())
.collect();
Ok(Self { test_cases })
}
}

View File

@ -0,0 +1,51 @@
use super::*;
use crate::case_result::compare_result;
use bls::{AggregatePublicKey, PublicKey};
use serde_derive::Deserialize;
use types::EthSpec;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsAggregatePubkeys {
pub input: Vec<String>,
pub output: String,
}
impl YamlDecode for BlsAggregatePubkeys {
fn yaml_decode(yaml: &String) -> Result<Self, Error> {
Ok(serde_yaml::from_str(&yaml.as_str()).unwrap())
}
}
impl EfTest for Cases<BlsAggregatePubkeys> {
fn test_results<E: EthSpec>(&self) -> Vec<CaseResult> {
self.test_cases
.iter()
.enumerate()
.map(|(i, tc)| {
let result = bls_add_pubkeys(&tc.input, &tc.output);
CaseResult::new(i, tc, result)
})
.collect()
}
}
/// Execute a `aggregate_pubkeys` test case.
fn bls_add_pubkeys(inputs: &[String], output: &String) -> Result<(), Error> {
let mut aggregate_pubkey = AggregatePublicKey::new();
for key_str in inputs {
let key =
hex::decode(&key_str[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let key = PublicKey::from_bytes(&key)
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
aggregate_pubkey.add(&key);
}
let output_bytes =
Some(hex::decode(&output[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?);
let aggregate_pubkey = Ok(aggregate_pubkey.as_raw().as_bytes());
compare_result::<Vec<u8>, Vec<u8>>(&aggregate_pubkey, &output_bytes)
}

View File

@ -0,0 +1,51 @@
use super::*;
use crate::case_result::compare_result;
use bls::{AggregateSignature, Signature};
use serde_derive::Deserialize;
use types::EthSpec;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsAggregateSigs {
pub input: Vec<String>,
pub output: String,
}
impl YamlDecode for BlsAggregateSigs {
fn yaml_decode(yaml: &String) -> Result<Self, Error> {
Ok(serde_yaml::from_str(&yaml.as_str()).unwrap())
}
}
impl EfTest for Cases<BlsAggregateSigs> {
fn test_results<E: EthSpec>(&self) -> Vec<CaseResult> {
self.test_cases
.iter()
.enumerate()
.map(|(i, tc)| {
let result = bls_add_signatures(&tc.input, &tc.output);
CaseResult::new(i, tc, result)
})
.collect()
}
}
/// Execute a `aggregate_sigs` test case.
fn bls_add_signatures(inputs: &[String], output: &String) -> Result<(), Error> {
let mut aggregate_signature = AggregateSignature::new();
for key_str in inputs {
let sig =
hex::decode(&key_str[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let sig = Signature::from_bytes(&sig)
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
aggregate_signature.add(&sig);
}
let output_bytes =
Some(hex::decode(&output[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?);
let aggregate_signature = Ok(aggregate_signature.as_bytes());
compare_result::<Vec<u8>, Vec<u8>>(&aggregate_signature, &output_bytes)
}

View File

@ -0,0 +1,71 @@
use super::*;
use crate::case_result::compare_result;
use bls::{compress_g2, hash_on_g2};
use serde_derive::Deserialize;
use types::EthSpec;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsG2CompressedInput {
pub message: String,
pub domain: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BlsG2Compressed {
pub input: BlsG2CompressedInput,
pub output: Vec<String>,
}
impl YamlDecode for BlsG2Compressed {
fn yaml_decode(yaml: &String) -> Result<Self, Error> {
Ok(serde_yaml::from_str(&yaml.as_str()).unwrap())
}
}
impl EfTest for Cases<BlsG2Compressed> {
fn test_results<E: EthSpec>(&self) -> Vec<CaseResult> {
self.test_cases
.iter()
.enumerate()
.map(|(i, tc)| {
let result = compressed_hash(&tc.input.message, &tc.input.domain, &tc.output);
CaseResult::new(i, tc, result)
})
.collect()
}
}
/// Execute a `compressed hash to g2` test case.
fn compressed_hash(message: &String, domain: &String, output: &Vec<String>) -> Result<(), Error> {
// Convert message and domain to required types
let msg =
hex::decode(&message[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let d = hex::decode(&domain[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let d = bytes_to_u64(&d);
// Calculate the point and convert it to compressed bytes
let mut point = hash_on_g2(&msg, d);
let point = compress_g2(&mut point);
// Convert the output to one set of bytes
let mut decoded =
hex::decode(&output[0][2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let mut decoded_y =
hex::decode(&output[1][2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
decoded.append(&mut decoded_y);
compare_result::<Vec<u8>, Vec<u8>>(&Ok(point), &Some(decoded))
}
// Converts a vector to u64 (from big endian)
fn bytes_to_u64(array: &Vec<u8>) -> u64 {
let mut result: u64 = 0;
for (i, value) in array.iter().rev().enumerate() {
if i == 8 {
break;
}
result += u64::pow(2, i as u32 * 8) * (*value as u64);
}
result
}

View File

@ -0,0 +1,85 @@
use super::*;
use crate::case_result::compare_result;
use bls::hash_on_g2;
use serde_derive::Deserialize;
use types::EthSpec;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsG2UncompressedInput {
pub message: String,
pub domain: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BlsG2Uncompressed {
pub input: BlsG2UncompressedInput,
pub output: Vec<Vec<String>>,
}
impl YamlDecode for BlsG2Uncompressed {
fn yaml_decode(yaml: &String) -> Result<Self, Error> {
Ok(serde_yaml::from_str(&yaml.as_str()).unwrap())
}
}
impl EfTest for Cases<BlsG2Uncompressed> {
fn test_results<E: EthSpec>(&self) -> Vec<CaseResult> {
self.test_cases
.iter()
.enumerate()
.map(|(i, tc)| {
let result = compressed_hash(&tc.input.message, &tc.input.domain, &tc.output);
CaseResult::new(i, tc, result)
})
.collect()
}
}
/// Execute a `compressed hash to g2` test case.
fn compressed_hash(
message: &String,
domain: &String,
output: &Vec<Vec<String>>,
) -> Result<(), Error> {
// Convert message and domain to required types
let msg =
hex::decode(&message[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let d = hex::decode(&domain[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let d = bytes_to_u64(&d);
// Calculate the point and convert it to compressed bytes
let point = hash_on_g2(&msg, d);
let mut point_bytes = [0 as u8; 288];
point.getpx().geta().tobytearray(&mut point_bytes, 0);
point.getpx().getb().tobytearray(&mut point_bytes, 48);
point.getpy().geta().tobytearray(&mut point_bytes, 96);
point.getpy().getb().tobytearray(&mut point_bytes, 144);
point.getpz().geta().tobytearray(&mut point_bytes, 192);
point.getpz().getb().tobytearray(&mut point_bytes, 240);
// Convert the output to one set of bytes (x.a, x.b, y.a, y.b, z.a, z.b)
let mut decoded: Vec<u8> = vec![];
for coordinate in output {
let mut decoded_part = hex::decode(&coordinate[0][2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
decoded.append(&mut decoded_part);
decoded_part = hex::decode(&coordinate[1][2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
decoded.append(&mut decoded_part);
}
compare_result::<Vec<u8>, Vec<u8>>(&Ok(point_bytes.to_vec()), &Some(decoded))
}
// Converts a vector to u64 (from big endian)
fn bytes_to_u64(array: &Vec<u8>) -> u64 {
let mut result: u64 = 0;
for (i, value) in array.iter().rev().enumerate() {
if i == 8 {
break;
}
result += u64::pow(2, i as u32 * 8) * (*value as u64);
}
result
}

View File

@ -0,0 +1,53 @@
use super::*;
use crate::case_result::compare_result;
use bls::{PublicKey, SecretKey};
use serde_derive::Deserialize;
use types::EthSpec;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsPrivToPub {
pub input: String,
pub output: String,
}
impl YamlDecode for BlsPrivToPub {
fn yaml_decode(yaml: &String) -> Result<Self, Error> {
Ok(serde_yaml::from_str(&yaml.as_str()).unwrap())
}
}
impl EfTest for Cases<BlsPrivToPub> {
fn test_results<E: EthSpec>(&self) -> Vec<CaseResult> {
self.test_cases
.iter()
.enumerate()
.map(|(i, tc)| {
let result = secret_to_public(&tc.input, &tc.output);
CaseResult::new(i, tc, result)
})
.collect()
}
}
/// Execute a `Private key to public key` test case.
fn secret_to_public(secret: &String, output: &String) -> Result<(), Error> {
// Convert message and domain to required types
let mut sk =
hex::decode(&secret[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
pad_to_48(&mut sk);
let sk = SecretKey::from_bytes(&sk).unwrap();
let pk = PublicKey::from_secret_key(&sk);
let decoded =
hex::decode(&output[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
compare_result::<Vec<u8>, Vec<u8>>(&Ok(pk.as_raw().as_bytes()), &Some(decoded))
}
// Increase the size of an array to 48 bytes
fn pad_to_48(array: &mut Vec<u8>) {
while array.len() < 48 {
array.insert(0, 0);
}
}

View File

@ -0,0 +1,88 @@
use super::*;
use crate::case_result::compare_result;
use bls::{SecretKey, Signature};
use serde_derive::Deserialize;
use types::EthSpec;
#[derive(Debug, Clone, Deserialize)]
pub struct BlsSignInput {
pub privkey: String,
pub message: String,
pub domain: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BlsSign {
pub input: BlsSignInput,
pub output: String,
}
impl YamlDecode for BlsSign {
fn yaml_decode(yaml: &String) -> Result<Self, Error> {
Ok(serde_yaml::from_str(&yaml.as_str()).unwrap())
}
}
impl EfTest for Cases<BlsSign> {
fn test_results<E: EthSpec>(&self) -> Vec<CaseResult> {
self.test_cases
.iter()
.enumerate()
.map(|(i, tc)| {
let result = sign_msg(
&tc.input.privkey,
&tc.input.message,
&tc.input.domain,
&tc.output,
);
CaseResult::new(i, tc, result)
})
.collect()
}
}
/// Execute a `compressed hash to g2` test case.
fn sign_msg(
private_key: &String,
message: &String,
domain: &String,
output: &String,
) -> Result<(), Error> {
// Convert private_key, message and domain to required types
let mut sk =
hex::decode(&private_key[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
pad_to_48(&mut sk);
let sk = SecretKey::from_bytes(&sk).unwrap();
let msg =
hex::decode(&message[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let d = hex::decode(&domain[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let d = bytes_to_u64(&d);
let signature = Signature::new(&msg, d, &sk);
// Convert the output to one set of bytes
let decoded =
hex::decode(&output[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
compare_result::<Vec<u8>, Vec<u8>>(&Ok(signature.as_bytes()), &Some(decoded))
}
// Converts a vector to u64 (from big endian)
fn bytes_to_u64(array: &Vec<u8>) -> u64 {
let mut result: u64 = 0;
for (i, value) in array.iter().rev().enumerate() {
if i == 8 {
break;
}
result += u64::pow(2, i as u32 * 8) * (*value as u64);
}
result
}
// Increase the size of an array to 48 bytes
fn pad_to_48(array: &mut Vec<u8>) {
while array.len() < 48 {
array.insert(0, 0);
}
}

View File

@ -0,0 +1,81 @@
use super::*;
use crate::case_result::compare_result;
use ethereum_types::{U128, U256};
use serde_derive::Deserialize;
use ssz::Decode;
use std::fmt::Debug;
use types::EthSpec;
#[derive(Debug, Clone, Deserialize)]
pub struct SszGeneric {
#[serde(alias = "type")]
pub type_name: String,
pub valid: bool,
pub value: Option<String>,
pub ssz: Option<String>,
}
impl YamlDecode for SszGeneric {
fn yaml_decode(yaml: &String) -> Result<Self, Error> {
Ok(serde_yaml::from_str(&yaml.as_str()).unwrap())
}
}
impl EfTest for Cases<SszGeneric> {
fn test_results<E: EthSpec>(&self) -> Vec<CaseResult> {
self.test_cases
.iter()
.enumerate()
.map(|(i, tc)| {
let result = if let Some(ssz) = &tc.ssz {
match tc.type_name.as_ref() {
"uint8" => ssz_generic_test::<u8>(tc.valid, ssz, &tc.value),
"uint16" => ssz_generic_test::<u16>(tc.valid, ssz, &tc.value),
"uint32" => ssz_generic_test::<u32>(tc.valid, ssz, &tc.value),
"uint64" => ssz_generic_test::<u64>(tc.valid, ssz, &tc.value),
"uint128" => ssz_generic_test::<U128>(tc.valid, ssz, &tc.value),
"uint256" => ssz_generic_test::<U256>(tc.valid, ssz, &tc.value),
_ => Err(Error::FailedToParseTest(format!(
"Unknown type: {}",
tc.type_name
))),
}
} else {
// Skip tests that do not have an ssz field.
//
// See: https://github.com/ethereum/eth2.0-specs/issues/1079
Ok(())
};
CaseResult::new(i, tc, result)
})
.collect()
}
}
/// Execute a `ssz_generic` test case.
fn ssz_generic_test<T>(
should_be_ok: bool,
ssz: &String,
value: &Option<String>,
) -> Result<(), Error>
where
T: Decode + YamlDecode + Debug + PartialEq<T>,
{
let ssz = hex::decode(&ssz[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
// We do not cater for the scenario where the test is valid but we are not passed any SSZ.
if should_be_ok && value.is_none() {
panic!("Unexpected test input. Cannot pass without value.")
}
let expected = if let Some(string) = value {
Some(T::yaml_decode(string)?)
} else {
None
};
let decoded = T::from_ssz_bytes(&ssz);
compare_result(&decoded, &expected)
}

View File

@ -0,0 +1,137 @@
use super::*;
use crate::case_result::compare_result;
use cached_tree_hash::{CachedTreeHash, TreeHashCache};
use rayon::prelude::*;
use serde_derive::Deserialize;
use ssz::{Decode, Encode, ssz_encode};
use std::fmt::Debug;
use tree_hash::TreeHash;
use types::{
test_utils::{SeedableRng, TestRandom, XorShiftRng},
Attestation, AttestationData, AttestationDataAndCustodyBit, AttesterSlashing, BeaconBlock,
BeaconBlockBody, BeaconBlockHeader, BeaconState, Crosslink, Deposit, DepositData, Eth1Data,
EthSpec, Fork, Hash256, HistoricalBatch, IndexedAttestation, PendingAttestation,
ProposerSlashing, Transfer, Validator, VoluntaryExit,
};
#[derive(Debug, Clone, Deserialize)]
pub struct SszStatic {
pub type_name: String,
pub serialized: String,
pub root: String,
#[serde(skip)]
pub raw_yaml: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Value<T> {
value: T,
}
impl YamlDecode for SszStatic {
fn yaml_decode(yaml: &String) -> Result<Self, Error> {
let mut ssz_static: SszStatic = serde_yaml::from_str(&yaml.as_str()).unwrap();
ssz_static.raw_yaml = yaml.clone();
Ok(ssz_static)
}
}
impl SszStatic {
fn value<T: serde::de::DeserializeOwned>(&self) -> Result<T, Error> {
let wrapper: Value<T> = serde_yaml::from_str(&self.raw_yaml.as_str()).map_err(|e| {
Error::FailedToParseTest(format!("Unable to parse {} YAML: {:?}", self.type_name, e))
})?;
Ok(wrapper.value)
}
}
impl EfTest for Cases<SszStatic> {
fn test_results<E: EthSpec>(&self) -> Vec<CaseResult> {
self.test_cases
.par_iter()
.enumerate()
.map(|(i, tc)| {
let result = match tc.type_name.as_ref() {
"Fork" => ssz_static_test::<Fork>(tc),
"Crosslink" => ssz_static_test::<Crosslink>(tc),
"Eth1Data" => ssz_static_test::<Eth1Data>(tc),
"AttestationData" => ssz_static_test::<AttestationData>(tc),
"AttestationDataAndCustodyBit" => {
ssz_static_test::<AttestationDataAndCustodyBit>(tc)
}
"IndexedAttestation" => ssz_static_test::<IndexedAttestation>(tc),
"DepositData" => ssz_static_test::<DepositData>(tc),
"BeaconBlockHeader" => ssz_static_test::<BeaconBlockHeader>(tc),
"Validator" => ssz_static_test::<Validator>(tc),
"PendingAttestation" => ssz_static_test::<PendingAttestation>(tc),
"HistoricalBatch" => ssz_static_test::<HistoricalBatch<E>>(tc),
"ProposerSlashing" => ssz_static_test::<ProposerSlashing>(tc),
"AttesterSlashing" => ssz_static_test::<AttesterSlashing>(tc),
"Attestation" => ssz_static_test::<Attestation>(tc),
"Deposit" => ssz_static_test::<Deposit>(tc),
"VoluntaryExit" => ssz_static_test::<VoluntaryExit>(tc),
"Transfer" => ssz_static_test::<Transfer>(tc),
"BeaconBlockBody" => ssz_static_test::<BeaconBlockBody>(tc),
"BeaconBlock" => ssz_static_test::<BeaconBlock>(tc),
"BeaconState" => ssz_static_test::<BeaconState<E>>(tc),
_ => Err(Error::FailedToParseTest(format!(
"Unknown type: {}",
tc.type_name
))),
};
CaseResult::new(i, tc, result)
})
.collect()
}
}
fn ssz_static_test<T>(tc: &SszStatic) -> Result<(), Error>
where
T: Clone
+ Decode
+ Debug
+ Encode
+ PartialEq<T>
+ serde::de::DeserializeOwned
+ TreeHash
+ CachedTreeHash
+ TestRandom,
{
// Verify we can decode SSZ in the same way we can decode YAML.
let ssz = hex::decode(&tc.serialized[2..])
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let expected = tc.value::<T>()?;
let decode_result = T::from_ssz_bytes(&ssz);
compare_result(&decode_result, &Some(expected))?;
// Verify we can encode the result back into original ssz bytes
let decoded = decode_result.unwrap();
let encoded_result = decoded.as_ssz_bytes();
compare_result::<Vec<u8>, Error>(&Ok(encoded_result), &Some(ssz));
// Verify the TreeHash root of the decoded struct matches the test.
let expected_root =
&hex::decode(&tc.root[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?;
let expected_root = Hash256::from_slice(&expected_root);
let tree_hash_root = Hash256::from_slice(&decoded.tree_hash_root());
compare_result::<Hash256, Error>(&Ok(tree_hash_root), &Some(expected_root))?;
// Verify a _new_ CachedTreeHash root of the decoded struct matches the test.
let cache = TreeHashCache::new(&decoded).unwrap();
let cached_tree_hash_root = Hash256::from_slice(cache.tree_hash_root().unwrap());
compare_result::<Hash256, Error>(&Ok(cached_tree_hash_root), &Some(expected_root))?;
// Verify the root after an update from a random CachedTreeHash to the decoded struct.
let mut rng = XorShiftRng::from_seed([42; 16]);
let random_instance = T::random_for_test(&mut rng);
let mut cache = TreeHashCache::new(&random_instance).unwrap();
cache.update(&decoded).unwrap();
let updated_root = Hash256::from_slice(cache.tree_hash_root().unwrap());
compare_result::<Hash256, Error>(&Ok(updated_root), &Some(expected_root))?;
Ok(())
}

117
tests/ef_tests/src/doc.rs Normal file
View File

@ -0,0 +1,117 @@
use crate::case_result::CaseResult;
use crate::cases::*;
use crate::doc_header::DocHeader;
use crate::eth_specs::{MainnetEthSpec, MinimalEthSpec};
use crate::yaml_decode::{extract_yaml_by_key, yaml_split_header_and_cases, YamlDecode};
use crate::EfTest;
use serde_derive::Deserialize;
use std::{fs::File, io::prelude::*, path::PathBuf};
use types::EthSpec;
#[derive(Debug, Deserialize)]
pub struct Doc {
pub header_yaml: String,
pub cases_yaml: String,
pub path: PathBuf,
}
impl Doc {
fn from_path(path: PathBuf) -> Self {
let mut file = File::open(path.clone()).unwrap();
let mut yaml = String::new();
file.read_to_string(&mut yaml).unwrap();
let (header_yaml, cases_yaml) = yaml_split_header_and_cases(yaml.clone());
Self {
header_yaml,
cases_yaml,
path,
}
}
pub fn test_results(&self) -> Vec<CaseResult> {
let header: DocHeader = serde_yaml::from_str(&self.header_yaml.as_str()).unwrap();
match (
header.runner.as_ref(),
header.handler.as_ref(),
header.config.as_ref(),
) {
("ssz", "uint", _) => run_test::<SszGeneric, MainnetEthSpec>(self),
("ssz", "static", "minimal") => run_test::<SszStatic, MinimalEthSpec>(self),
("ssz", "static", "mainnet") => run_test::<SszStatic, MainnetEthSpec>(self),
("bls", "aggregate_pubkeys", "mainnet") => {
run_test::<BlsAggregatePubkeys, MainnetEthSpec>(self)
}
("bls", "aggregate_sigs", "mainnet") => {
run_test::<BlsAggregateSigs, MainnetEthSpec>(self)
}
("bls", "msg_hash_compressed", "mainnet") => {
run_test::<BlsG2Compressed, MainnetEthSpec>(self)
}
// Note this test fails due to a difference in our internal representations. It does
// not effect verification or external representation.
//
// It is skipped.
("bls", "msg_hash_uncompressed", "mainnet") => vec![],
("bls", "priv_to_pub", "mainnet") => run_test::<BlsPrivToPub, MainnetEthSpec>(self),
("bls", "sign_msg", "mainnet") => run_test::<BlsSign, MainnetEthSpec>(self),
(runner, handler, config) => panic!(
"No implementation for runner: \"{}\", handler: \"{}\", config: \"{}\"",
runner, handler, config
),
}
}
pub fn assert_tests_pass(path: PathBuf) {
let doc = Self::from_path(path);
let results = doc.test_results();
if results.iter().any(|r| r.result.is_err()) {
print_failures(&doc, &results);
panic!("Tests failed (see above)");
} else {
println!("Passed {} tests in {:?}", results.len(), doc.path);
}
}
}
pub fn run_test<T, E: EthSpec>(doc: &Doc) -> Vec<CaseResult>
where
Cases<T>: EfTest + YamlDecode,
{
// Extract only the "test_cases" YAML as a stand-alone string.
//let test_cases_yaml = extract_yaml_by_key(self., "test_cases");
// Pass only the "test_cases" YAML string to `yaml_decode`.
let test_cases: Cases<T> = Cases::yaml_decode(&doc.cases_yaml).unwrap();
test_cases.test_results::<E>()
}
pub fn print_failures(doc: &Doc, results: &[CaseResult]) {
let header: DocHeader = serde_yaml::from_str(&doc.header_yaml).unwrap();
let failures: Vec<&CaseResult> = results.iter().filter(|r| r.result.is_err()).collect();
println!("--------------------------------------------------");
println!("Test Failure");
println!("Title: {}", header.title);
println!("File: {:?}", doc.path);
println!("");
println!(
"{} tests, {} failures, {} passes.",
results.len(),
failures.len(),
results.len() - failures.len()
);
println!("");
for failure in failures {
println!("-------");
println!("case[{}].result:", failure.case_index);
println!("{:#?}", failure.result);
}
println!("");
}

View File

@ -0,0 +1,12 @@
use serde_derive::Deserialize;
#[derive(Debug, Deserialize)]
pub struct DocHeader {
pub title: String,
pub summary: String,
pub forks_timeline: String,
pub forks: Vec<String>,
pub config: String,
pub runner: String,
pub handler: String,
}

View File

@ -0,0 +1,9 @@
#[derive(Debug, PartialEq, Clone)]
pub enum Error {
/// The value in the test didn't match our value.
NotEqual(String),
/// The test specified a failure and we did not experience one.
DidntFail(String),
/// Failed to parse the test (internal error).
FailedToParseTest(String),
}

View File

@ -0,0 +1,28 @@
use serde_derive::{Deserialize, Serialize};
use types::{
typenum::{U64, U8},
ChainSpec, EthSpec, FewValidatorsEthSpec, FoundationEthSpec,
};
/// "Minimal" testing specification, as defined here:
///
/// https://github.com/ethereum/eth2.0-specs/blob/v0.6.1/configs/constant_presets/minimal.yaml
///
/// Spec v0.6.1
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct MinimalEthSpec;
impl EthSpec for MinimalEthSpec {
type ShardCount = U8;
type SlotsPerHistoricalRoot = U64;
type LatestRandaoMixesLength = U64;
type LatestActiveIndexRootsLength = U64;
type LatestSlashedExitLength = U64;
fn spec() -> ChainSpec {
// TODO: this spec is likely incorrect!
FewValidatorsEthSpec::spec()
}
}
pub type MainnetEthSpec = FoundationEthSpec;

21
tests/ef_tests/src/lib.rs Normal file
View File

@ -0,0 +1,21 @@
use types::EthSpec;
pub use case_result::CaseResult;
pub use doc::Doc;
pub use error::Error;
pub use yaml_decode::YamlDecode;
mod case_result;
mod cases;
mod doc;
mod doc_header;
mod error;
mod eth_specs;
mod yaml_decode;
/// Defined where an object can return the results of some test(s) adhering to the Ethereum
/// Foundation testing format.
pub trait EfTest {
/// Returns the results of executing one or more tests.
fn test_results<E: EthSpec>(&self) -> Vec<CaseResult>;
}

View File

@ -0,0 +1,59 @@
use super::*;
use ethereum_types::{U128, U256};
use types::Fork;
mod utils;
pub use utils::*;
pub trait YamlDecode: Sized {
/// Decode an object from the test specification YAML.
fn yaml_decode(string: &String) -> Result<Self, Error>;
}
/// Basic types can general be decoded with the `parse` fn if they implement `str::FromStr`.
macro_rules! impl_via_parse {
($ty: ty) => {
impl YamlDecode for $ty {
fn yaml_decode(string: &String) -> Result<Self, Error> {
string
.parse::<Self>()
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))
}
}
};
}
impl_via_parse!(u8);
impl_via_parse!(u16);
impl_via_parse!(u32);
impl_via_parse!(u64);
/// Some `ethereum-types` methods have a `str::FromStr` implementation that expects `0x`-prefixed:
/// hex, so we use `from_dec_str` instead.
macro_rules! impl_via_from_dec_str {
($ty: ty) => {
impl YamlDecode for $ty {
fn yaml_decode(string: &String) -> Result<Self, Error> {
Self::from_dec_str(string).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))
}
}
};
}
impl_via_from_dec_str!(U128);
impl_via_from_dec_str!(U256);
/// Types that already implement `serde::Deserialize` can be decoded using `serde_yaml`.
macro_rules! impl_via_serde_yaml {
($ty: ty) => {
impl YamlDecode for $ty {
fn yaml_decode(string: &String) -> Result<Self, Error> {
serde_yaml::from_str(string)
.map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))
}
}
};
}
impl_via_serde_yaml!(Fork);

View File

@ -0,0 +1,35 @@
use yaml_rust::{Yaml, YamlEmitter, YamlLoader};
pub fn extract_yaml_by_key(yaml: &str, key: &str) -> String {
let doc = &YamlLoader::load_from_str(yaml).unwrap()[0];
let subsection = &doc[key];
yaml_to_string(subsection)
}
pub fn extract_yaml_by_index(yaml: &str, index: usize) -> String {
let doc = &YamlLoader::load_from_str(yaml).unwrap()[0];
let subsection = &doc[index];
yaml_to_string(subsection)
}
pub fn yaml_to_string(yaml: &Yaml) -> String {
let mut out_str = String::new();
let mut emitter = YamlEmitter::new(&mut out_str);
emitter.escape_all_strings(true);
emitter.dump(yaml).unwrap();
out_str
}
pub fn yaml_split_header_and_cases(mut yaml: String) -> (String, String) {
let test_cases_start = yaml.find("\ntest_cases:\n").unwrap();
// + 1 to skip the \n we used for matching.
let mut test_cases = yaml.split_off(test_cases_start + 1);
let end_of_first_line = test_cases.find("\n").unwrap();
let test_cases = test_cases.split_off(end_of_first_line + 1);
(yaml, test_cases)
}

View File

@ -0,0 +1,62 @@
use ef_tests::*;
use rayon::prelude::*;
use std::path::PathBuf;
use walkdir::WalkDir;
fn yaml_files_in_test_dir(dir: &str) -> Vec<PathBuf> {
let mut base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
base_path.push("eth2.0-spec-tests");
base_path.push("tests");
base_path.push(dir);
assert!(
base_path.exists(),
"Unable to locate test files. Did you init git submoules?"
);
WalkDir::new(base_path)
.into_iter()
.filter_map(|e| e.ok())
.filter_map(|entry| {
if entry.file_type().is_file() {
match entry.file_name().to_str() {
Some(f) if f.ends_with(".yaml") => Some(entry.path().to_path_buf()),
Some(f) if f.ends_with(".yml") => Some(entry.path().to_path_buf()),
_ => None,
}
} else {
None
}
})
.collect()
}
#[test]
#[cfg(feature = "fake_crypto")]
fn ssz_generic() {
yaml_files_in_test_dir("ssz_generic")
.into_par_iter()
.for_each(|file| {
Doc::assert_tests_pass(file);
});
}
#[test]
#[cfg(feature = "fake_crypto")]
fn ssz_static() {
yaml_files_in_test_dir("ssz_static")
.into_par_iter()
.for_each(|file| {
Doc::assert_tests_pass(file);
});
}
#[test]
#[cfg(not(feature = "fake_crypto"))]
fn bls() {
yaml_files_in_test_dir("bls")
.into_par_iter()
.for_each(|file| {
Doc::assert_tests_pass(file);
});
}