Merge branch 'ef-tests' into v0.6.1
This commit is contained in:
commit
1eeaaaa92b
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal 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
|
@ -29,6 +29,7 @@ members = [
|
|||||||
"beacon_node/rpc",
|
"beacon_node/rpc",
|
||||||
"beacon_node/version",
|
"beacon_node/version",
|
||||||
"beacon_node/beacon_chain",
|
"beacon_node/beacon_chain",
|
||||||
|
"tests/ef_tests",
|
||||||
"protos",
|
"protos",
|
||||||
"validator_client",
|
"validator_client",
|
||||||
"account_manager",
|
"account_manager",
|
||||||
|
34
Jenkinsfile
vendored
34
Jenkinsfile
vendored
@ -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'
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
use crate::test_utils::TestRandom;
|
use crate::test_utils::{graffiti_from_hex_str, TestRandom};
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
@ -24,6 +24,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
pub struct BeaconBlockBody {
|
pub struct BeaconBlockBody {
|
||||||
pub randao_reveal: Signature,
|
pub randao_reveal: Signature,
|
||||||
pub eth1_data: Eth1Data,
|
pub eth1_data: Eth1Data,
|
||||||
|
#[serde(deserialize_with = "graffiti_from_hex_str")]
|
||||||
pub graffiti: [u8; 32],
|
pub graffiti: [u8; 32],
|
||||||
pub proposer_slashings: Vec<ProposerSlashing>,
|
pub proposer_slashings: Vec<ProposerSlashing>,
|
||||||
pub attester_slashings: Vec<AttesterSlashing>,
|
pub attester_slashings: Vec<AttesterSlashing>,
|
||||||
|
@ -81,7 +81,7 @@ pub type AttesterMap = HashMap<(u64, u64), Vec<usize>>;
|
|||||||
pub type ProposerMap = HashMap<u64, usize>;
|
pub type ProposerMap = HashMap<u64, usize>;
|
||||||
|
|
||||||
pub use bls::{AggregatePublicKey, AggregateSignature, Keypair, PublicKey, SecretKey, Signature};
|
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::floodsub::{Topic, TopicBuilder, TopicHash};
|
||||||
pub use libp2p::multiaddr;
|
pub use libp2p::multiaddr;
|
||||||
pub use libp2p::Multiaddr;
|
pub use libp2p::Multiaddr;
|
||||||
|
@ -14,5 +14,5 @@ pub use rand::{
|
|||||||
RngCore,
|
RngCore,
|
||||||
{prng::XorShiftRng, SeedableRng},
|
{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;
|
pub use test_random::TestRandom;
|
||||||
|
@ -2,6 +2,7 @@ use serde::de::Error;
|
|||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
pub const FORK_BYTES_LEN: usize = 4;
|
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>
|
pub fn u8_from_hex_str<'de, D>(deserializer: D) -> Result<u8, D::Error>
|
||||||
where
|
where
|
||||||
@ -32,3 +33,24 @@ where
|
|||||||
}
|
}
|
||||||
Ok(array)
|
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)
|
||||||
|
}
|
||||||
|
@ -5,10 +5,11 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[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" }
|
cached_tree_hash = { path = "../cached_tree_hash" }
|
||||||
hashing = { path = "../hashing" }
|
hashing = { path = "../hashing" }
|
||||||
hex = "0.3"
|
hex = "0.3"
|
||||||
|
rand = "0.5"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_hex = { path = "../serde_hex" }
|
serde_hex = { path = "../serde_hex" }
|
||||||
|
36
eth2/utils/bls/src/fake_aggregate_public_key.rs
Normal file
36
eth2/utils/bls/src/fake_aggregate_public_key.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -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 cached_tree_hash::cached_tree_hash_ssz_encoding_as_vector;
|
||||||
use serde::de::{Deserialize, Deserializer};
|
use serde::de::{Deserialize, Deserializer};
|
||||||
use serde::ser::{Serialize, Serializer};
|
use serde::ser::{Serialize, Serializer};
|
||||||
@ -43,7 +46,7 @@ impl FakeAggregateSignature {
|
|||||||
&self,
|
&self,
|
||||||
_msg: &[u8],
|
_msg: &[u8],
|
||||||
_domain: u64,
|
_domain: u64,
|
||||||
_aggregate_public_key: &AggregatePublicKey,
|
_aggregate_public_key: &FakeAggregatePublicKey,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -53,7 +56,7 @@ impl FakeAggregateSignature {
|
|||||||
&self,
|
&self,
|
||||||
_messages: &[&[u8]],
|
_messages: &[&[u8]],
|
||||||
_domain: u64,
|
_domain: u64,
|
||||||
_aggregate_public_keys: &[&AggregatePublicKey],
|
_aggregate_public_keys: &[&FakeAggregatePublicKey],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
169
eth2/utils/bls/src/fake_public_key.rs
Normal file
169
eth2/utils/bls/src/fake_public_key.rs
Normal 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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,33 +3,50 @@ extern crate ssz;
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
mod aggregate_public_key;
|
|
||||||
mod keypair;
|
mod keypair;
|
||||||
mod public_key;
|
|
||||||
mod secret_key;
|
mod secret_key;
|
||||||
|
|
||||||
#[cfg(not(feature = "fake_crypto"))]
|
pub use crate::keypair::Keypair;
|
||||||
mod aggregate_signature;
|
pub use crate::secret_key::SecretKey;
|
||||||
#[cfg(not(feature = "fake_crypto"))]
|
pub use bls_aggregates::{compress_g2, hash_on_g2};
|
||||||
mod signature;
|
|
||||||
#[cfg(not(feature = "fake_crypto"))]
|
|
||||||
pub use crate::aggregate_signature::AggregateSignature;
|
|
||||||
#[cfg(not(feature = "fake_crypto"))]
|
|
||||||
pub use crate::signature::Signature;
|
|
||||||
|
|
||||||
|
#[cfg(feature = "fake_crypto")]
|
||||||
|
mod fake_aggregate_public_key;
|
||||||
#[cfg(feature = "fake_crypto")]
|
#[cfg(feature = "fake_crypto")]
|
||||||
mod fake_aggregate_signature;
|
mod fake_aggregate_signature;
|
||||||
#[cfg(feature = "fake_crypto")]
|
#[cfg(feature = "fake_crypto")]
|
||||||
|
mod fake_public_key;
|
||||||
|
#[cfg(feature = "fake_crypto")]
|
||||||
mod fake_signature;
|
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;
|
|
||||||
|
|
||||||
|
#[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_public_key::AggregatePublicKey;
|
||||||
pub use crate::keypair::Keypair;
|
pub use crate::aggregate_signature::AggregateSignature;
|
||||||
pub use crate::public_key::PublicKey;
|
pub use crate::public_key::PublicKey;
|
||||||
pub use crate::secret_key::SecretKey;
|
pub use crate::signature::Signature;
|
||||||
|
}
|
||||||
|
|
||||||
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96;
|
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96;
|
||||||
pub const BLS_SIG_BYTE_SIZE: usize = 96;
|
pub const BLS_SIG_BYTE_SIZE: usize = 96;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
extern crate rand;
|
||||||
|
|
||||||
use super::BLS_SECRET_KEY_BYTE_SIZE;
|
use super::BLS_SECRET_KEY_BYTE_SIZE;
|
||||||
use bls_aggregates::SecretKey as RawSecretKey;
|
use bls_aggregates::SecretKey as RawSecretKey;
|
||||||
use hex::encode as hex_encode;
|
use hex::encode as hex_encode;
|
||||||
@ -16,7 +18,7 @@ pub struct SecretKey(RawSecretKey);
|
|||||||
|
|
||||||
impl SecretKey {
|
impl SecretKey {
|
||||||
pub fn random() -> Self {
|
pub fn random() -> Self {
|
||||||
SecretKey(RawSecretKey::random())
|
SecretKey(RawSecretKey::random(&mut rand::thread_rng()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the underlying point as compressed bytes.
|
/// Returns the underlying point as compressed bytes.
|
||||||
|
@ -9,6 +9,7 @@ pub use typenum;
|
|||||||
mod impls;
|
mod impls;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
pub struct FixedLenVec<T, N> {
|
pub struct FixedLenVec<T, N> {
|
||||||
vec: Vec<T>,
|
vec: Vec<T>,
|
||||||
_phantom: PhantomData<N>,
|
_phantom: PhantomData<N>,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use ethereum_types::H256;
|
use ethereum_types::{H256, U128, U256};
|
||||||
|
|
||||||
macro_rules! impl_decodable_for_uint {
|
macro_rules! impl_decodable_for_uint {
|
||||||
($type: ident, $bit_size: expr) => {
|
($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 {
|
macro_rules! impl_decodable_for_u8_array {
|
||||||
($len: expr) => {
|
($len: expr) => {
|
||||||
impl Decode for [u8; $len] {
|
impl Decode for [u8; $len] {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use ethereum_types::H256;
|
use ethereum_types::{H256, U128, U256};
|
||||||
|
|
||||||
macro_rules! impl_encodable_for_uint {
|
macro_rules! impl_encodable_for_uint {
|
||||||
($type: ident, $bit_size: expr) => {
|
($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 {
|
macro_rules! impl_encodable_for_u8_array {
|
||||||
($len: expr) => {
|
($len: expr) => {
|
||||||
impl Encode for [u8; $len] {
|
impl Encode for [u8; $len] {
|
||||||
|
23
tests/ef_tests/Cargo.toml
Normal file
23
tests/ef_tests/Cargo.toml
Normal 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"}
|
1
tests/ef_tests/eth2.0-spec-tests
Submodule
1
tests/ef_tests/eth2.0-spec-tests
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 161a36ee6232d8d251d798c8262638ed0c34c9c6
|
52
tests/ef_tests/src/case_result.rs
Normal file
52
tests/ef_tests/src/case_result.rs
Normal 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
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
tests/ef_tests/src/cases.rs
Normal file
59
tests/ef_tests/src/cases.rs
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
51
tests/ef_tests/src/cases/bls_aggregate_pubkeys.rs
Normal file
51
tests/ef_tests/src/cases/bls_aggregate_pubkeys.rs
Normal 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)
|
||||||
|
}
|
51
tests/ef_tests/src/cases/bls_aggregate_sigs.rs
Normal file
51
tests/ef_tests/src/cases/bls_aggregate_sigs.rs
Normal 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)
|
||||||
|
}
|
71
tests/ef_tests/src/cases/bls_g2_compressed.rs
Normal file
71
tests/ef_tests/src/cases/bls_g2_compressed.rs
Normal 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
|
||||||
|
}
|
85
tests/ef_tests/src/cases/bls_g2_uncompressed.rs
Normal file
85
tests/ef_tests/src/cases/bls_g2_uncompressed.rs
Normal 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
|
||||||
|
}
|
53
tests/ef_tests/src/cases/bls_priv_to_pub.rs
Normal file
53
tests/ef_tests/src/cases/bls_priv_to_pub.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
88
tests/ef_tests/src/cases/bls_sign_msg.rs
Normal file
88
tests/ef_tests/src/cases/bls_sign_msg.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
81
tests/ef_tests/src/cases/ssz_generic.rs
Normal file
81
tests/ef_tests/src/cases/ssz_generic.rs
Normal 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)
|
||||||
|
}
|
137
tests/ef_tests/src/cases/ssz_static.rs
Normal file
137
tests/ef_tests/src/cases/ssz_static.rs
Normal 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
117
tests/ef_tests/src/doc.rs
Normal 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!("");
|
||||||
|
}
|
12
tests/ef_tests/src/doc_header.rs
Normal file
12
tests/ef_tests/src/doc_header.rs
Normal 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,
|
||||||
|
}
|
9
tests/ef_tests/src/error.rs
Normal file
9
tests/ef_tests/src/error.rs
Normal 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),
|
||||||
|
}
|
28
tests/ef_tests/src/eth_specs.rs
Normal file
28
tests/ef_tests/src/eth_specs.rs
Normal 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
21
tests/ef_tests/src/lib.rs
Normal 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>;
|
||||||
|
}
|
59
tests/ef_tests/src/yaml_decode.rs
Normal file
59
tests/ef_tests/src/yaml_decode.rs
Normal 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);
|
35
tests/ef_tests/src/yaml_decode/utils.rs
Normal file
35
tests/ef_tests/src/yaml_decode/utils.rs
Normal 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)
|
||||||
|
}
|
62
tests/ef_tests/tests/tests.rs
Normal file
62
tests/ef_tests/tests/tests.rs
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user